ide_diagnostics/handlers/
json_is_not_rust.rs1use 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}