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