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