Skip to main content

ide_diagnostics/handlers/
json_is_not_rust.rs

1//! This diagnostic provides an assist for creating a struct definition from a JSON
2//! example.
3
4use hir::{FindPathConfig, PathResolution, Semantics};
5use ide_db::imports::insert_use::insert_uses_with_editor;
6use ide_db::text_edit::TextEdit;
7use ide_db::{
8    EditionedFileId, FileRange, FxHashMap, RootDatabase, helpers::mod_path_to_ast,
9    imports::insert_use::ImportScope, source_change::SourceChangeBuilder,
10};
11use itertools::Itertools;
12use stdx::{format_to, never};
13use syntax::{
14    Edition, SyntaxKind, SyntaxNode,
15    ast::{self, make},
16};
17
18use crate::{Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity, fix};
19
20#[derive(Default)]
21struct State {
22    result: String,
23    has_serialize: bool,
24    has_deserialize: bool,
25    names: FxHashMap<String, usize>,
26    edition: Option<Edition>,
27}
28
29impl State {
30    fn make_name(&self, name: &str) -> ast::Name {
31        let edition = self.edition.unwrap();
32        if syntax::utils::is_identifier(name, edition)
33            || syntax::utils::is_raw_identifier(name, edition)
34        {
35            make::name(name)
36        } else {
37            make::name("INVALID")
38        }
39    }
40
41    fn generate_new_name(&mut self, name: &str) -> ast::Name {
42        let name = stdx::to_camel_case(name);
43        let count = if let Some(count) = self.names.get_mut(&name) {
44            *count += 1;
45            *count
46        } else {
47            self.names.insert(name.clone(), 1);
48            1
49        };
50        self.make_name(&format!("{name}{count}"))
51    }
52
53    fn serde_derive(&self) -> String {
54        let mut v = vec![];
55        if self.has_serialize {
56            v.push("Serialize");
57        }
58        if self.has_deserialize {
59            v.push("Deserialize");
60        }
61        match v.as_slice() {
62            [] => "".to_owned(),
63            [x] => format!("#[derive({x})]\n"),
64            [x, y] => format!("#[derive({x}, {y})]\n"),
65            _ => {
66                never!();
67                "".to_owned()
68            }
69        }
70    }
71
72    fn build_struct(
73        &mut self,
74        name: &str,
75        value: &serde_json::Map<String, serde_json::Value>,
76    ) -> ast::Type {
77        let name = self.generate_new_name(name);
78        let ty = make::ty(&name.to_string());
79        let strukt = make::struct_(
80            None,
81            name,
82            None,
83            make::record_field_list(value.iter().sorted_unstable_by_key(|x| x.0).map(
84                |(name, value)| {
85                    make::record_field(None, self.make_name(name), self.type_of(name, value))
86                },
87            ))
88            .into(),
89        );
90        format_to!(self.result, "{}{}\n", self.serde_derive(), strukt);
91        ty
92    }
93
94    fn type_of(&mut self, name: &str, value: &serde_json::Value) -> ast::Type {
95        match value {
96            serde_json::Value::Null => make::ty_unit(),
97            serde_json::Value::Bool(_) => make::ty("bool"),
98            serde_json::Value::Number(it) => make::ty(if it.is_i64() { "i64" } else { "f64" }),
99            serde_json::Value::String(_) => make::ty("String"),
100            serde_json::Value::Array(it) => {
101                let ty = match it.iter().next() {
102                    Some(x) => self.type_of(name, x),
103                    None => make::ty_placeholder(),
104                };
105                make::ty(&format!("Vec<{ty}>"))
106            }
107            serde_json::Value::Object(x) => self.build_struct(name, x),
108        }
109    }
110}
111
112pub(crate) fn json_in_items(
113    sema: &Semantics<'_, RootDatabase>,
114    acc: &mut Vec<Diagnostic>,
115    file_id: EditionedFileId,
116    node: &SyntaxNode,
117    config: &DiagnosticsConfig,
118    edition: Edition,
119) {
120    (|| {
121        if node.kind() == SyntaxKind::ERROR
122            && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY)
123            && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY)
124        {
125            let node_string = node.to_string();
126            if let Ok(serde_json::Value::Object(it)) = serde_json::from_str(&node_string) {
127                let import_scope = ImportScope::find_insert_use_container(node, sema)?;
128                let range = node.text_range();
129                let mut edit = TextEdit::builder();
130                edit.delete(range);
131                let mut state = State::default();
132                let semantics_scope = sema.scope(node)?;
133                let scope_resolve =
134                    |it| semantics_scope.speculative_resolve(&make::path_from_text(it));
135                let scope_has = |it| scope_resolve(it).is_some();
136                let deserialize_resolved = scope_resolve("::serde::Deserialize");
137                let serialize_resolved = scope_resolve("::serde::Serialize");
138                state.has_deserialize = deserialize_resolved.is_some();
139                state.has_serialize = serialize_resolved.is_some();
140                state.edition = Some(edition);
141                state.build_struct("Root", &it);
142                edit.insert(range.start(), state.result);
143                let vfs_file_id = file_id.file_id(sema.db);
144                acc.push(
145                    Diagnostic::new(
146                        DiagnosticCode::Ra("json-is-not-rust", Severity::WeakWarning),
147                        "JSON syntax is not valid as a Rust item",
148                        FileRange { file_id: vfs_file_id, range },
149                    )
150                    .stable()
151                    .with_fixes(Some(vec![{
152                        let mut scb = SourceChangeBuilder::new(vfs_file_id);
153                        let editor = scb.make_editor(import_scope.as_syntax_node());
154                        let current_module = semantics_scope.module();
155
156                        let cfg = FindPathConfig {
157                            prefer_no_std: config.prefer_no_std,
158                            prefer_prelude: config.prefer_prelude,
159                            prefer_absolute: config.prefer_absolute,
160                            allow_unstable: true,
161                        };
162
163                        let mut imports_to_insert = Vec::new();
164                        if !scope_has("Serialize")
165                            && let Some(PathResolution::Def(it)) = serialize_resolved
166                            && let Some(it) = current_module.find_use_path(
167                                sema.db,
168                                it,
169                                config.insert_use.prefix_kind,
170                                cfg,
171                            )
172                        {
173                            imports_to_insert.push(mod_path_to_ast(&it, edition));
174                        }
175                        if !scope_has("Deserialize")
176                            && let Some(PathResolution::Def(it)) = deserialize_resolved
177                            && let Some(it) = current_module.find_use_path(
178                                sema.db,
179                                it,
180                                config.insert_use.prefix_kind,
181                                cfg,
182                            )
183                        {
184                            imports_to_insert.push(mod_path_to_ast(&it, edition));
185                        }
186
187                        insert_uses_with_editor(
188                            &import_scope,
189                            imports_to_insert,
190                            &config.insert_use,
191                            &editor,
192                        );
193                        scb.add_file_edits(vfs_file_id, editor);
194                        let mut sc = scb.finish();
195                        sc.insert_source_edit(vfs_file_id, edit.finish());
196                        fix("convert_json_to_struct", "Convert JSON to struct", sc, range)
197                    }])),
198                );
199            }
200        }
201        Some(())
202    })();
203}
204
205#[cfg(test)]
206mod tests {
207    use crate::{
208        DiagnosticsConfig,
209        tests::{check_diagnostics_with_config, check_fix, check_no_fix},
210    };
211
212    #[test]
213    fn diagnostic_for_simple_case() {
214        let mut config = DiagnosticsConfig::test_sample();
215        config.disabled.insert("syntax-error".to_owned());
216        check_diagnostics_with_config(
217            config,
218            r#"
219            { "foo": "bar" }
220         // ^^^^^^^^^^^^^^^^ 💡 weak: JSON syntax is not valid as a Rust item
221"#,
222        );
223    }
224
225    #[test]
226    fn types_of_primitives() {
227        check_fix(
228            r#"
229            //- /lib.rs crate:lib deps:serde
230            use serde::Serialize;
231
232            fn some_garbage() {
233
234            }
235
236            {$0
237                "foo": "bar",
238                "bar": 2.3,
239                "baz": null,
240                "bay": 57,
241                "box": true
242            }
243            //- /serde.rs crate:serde
244
245            pub trait Serialize {
246                fn serialize() -> u8;
247            }
248            "#,
249            r#"
250            use serde::Serialize;
251
252            fn some_garbage() {
253
254            }
255
256            #[derive(Serialize)]
257            struct Root1 { bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
258
259            "#,
260        );
261    }
262
263    #[test]
264    fn nested_structs() {
265        check_fix(
266            r#"
267            {$0
268                "foo": "bar",
269                "bar": {
270                    "kind": "Object",
271                    "value": {}
272                }
273            }
274            "#,
275            r#"
276            struct Value1 {  }
277            struct Bar1 { kind: String, value: Value1 }
278            struct Root1 { bar: Bar1, foo: String }
279
280            "#,
281        );
282    }
283
284    #[test]
285    fn naming() {
286        check_fix(
287            r#"
288            {$0
289                "user": {
290                    "address": {
291                        "street": "Main St",
292                        "house": 3
293                    },
294                    "email": "example@example.com"
295                },
296                "another_user": {
297                    "user": {
298                        "address": {
299                            "street": "Main St",
300                            "house": 3
301                        },
302                        "email": "example@example.com"
303                    }
304                }
305            }
306            "#,
307            r#"
308            struct Address1 { house: i64, street: String }
309            struct User1 { address: Address1, email: String }
310            struct AnotherUser1 { user: User1 }
311            struct Address2 { house: i64, street: String }
312            struct User2 { address: Address2, email: String }
313            struct Root1 { another_user: AnotherUser1, user: User2 }
314
315            "#,
316        );
317    }
318
319    #[test]
320    fn arrays() {
321        check_fix(
322            r#"
323            //- /lib.rs crate:lib deps:serde
324            {
325                "of_string": ["foo", "2", "x"], $0
326                "of_object": [{
327                    "x": 10,
328                    "y": 20
329                }, {
330                    "x": 10,
331                    "y": 20
332                }],
333                "nested": [[[2]]],
334                "empty": []
335            }
336            //- /serde.rs crate:serde
337
338            pub trait Serialize {
339                fn serialize() -> u8;
340            }
341            pub trait Deserialize {
342                fn deserialize() -> u8;
343            }
344            "#,
345            r#"
346            use serde::Serialize;
347            use serde::Deserialize;
348
349            #[derive(Serialize, Deserialize)]
350            struct OfObject1 { x: i64, y: i64 }
351            #[derive(Serialize, Deserialize)]
352            struct Root1 { empty: Vec<_>, nested: Vec<Vec<Vec<i64>>>, of_object: Vec<OfObject1>, of_string: Vec<String> }
353
354            "#,
355        );
356    }
357
358    #[test]
359    fn invalid_fields() {
360        check_fix(
361            r#"
362            //- /lib.rs crate:lib deps:serde
363            {$0
364                "$": "",
365                "self": "",
366                "valided": ""
367            }
368            //- /serde.rs crate:serde
369
370            pub trait Serialize {
371                fn serialize() -> u8;
372            }
373            pub trait Deserialize {
374                fn deserialize() -> u8;
375            }
376            "#,
377            r#"
378            use serde::Serialize;
379            use serde::Deserialize;
380
381            #[derive(Serialize, Deserialize)]
382            struct Root1 { INVALID: String, INVALID: String, valided: String }
383
384            "#,
385        );
386    }
387
388    #[test]
389    fn no_emit_outside_of_item_position() {
390        check_no_fix(
391            r#"
392            fn foo() {
393                let json = {$0
394                    "foo": "bar",
395                    "bar": {
396                        "kind": "Object",
397                        "value": {}
398                    }
399                };
400            }
401            "#,
402        );
403    }
404}