]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-diagnostics / src / handlers / json_is_not_rust.rs
CommitLineData
f2b60f7d
FG
1//! This diagnostic provides an assist for creating a struct definition from a JSON
2//! example.
3
4use hir::{PathResolution, Semantics};
5use ide_db::{
6 base_db::FileId,
7 helpers::mod_path_to_ast,
8 imports::insert_use::{insert_use, ImportScope},
9 source_change::SourceChangeBuilder,
10 RootDatabase,
11};
12use itertools::Itertools;
13use stdx::{format_to, never};
14use syntax::{
15 ast::{self, make},
16 SyntaxKind, SyntaxNode,
17};
18use text_edit::TextEdit;
19
20use crate::{fix, Diagnostic, DiagnosticsConfig, Severity};
21
22#[derive(Default)]
23struct State {
24 result: String,
25 struct_counts: usize,
26 has_serialize: bool,
27 has_deserialize: bool,
28}
29
30impl State {
31 fn generate_new_name(&mut self) -> ast::Name {
32 self.struct_counts += 1;
33 make::name(&format!("Struct{}", self.struct_counts))
34 }
35
36 fn serde_derive(&self) -> String {
37 let mut v = vec![];
38 if self.has_serialize {
39 v.push("Serialize");
40 }
41 if self.has_deserialize {
42 v.push("Deserialize");
43 }
44 match v.as_slice() {
45 [] => "".to_string(),
46 [x] => format!("#[derive({x})]\n"),
47 [x, y] => format!("#[derive({x}, {y})]\n"),
48 _ => {
49 never!();
50 "".to_string()
51 }
52 }
53 }
54
55 fn build_struct(&mut self, value: &serde_json::Map<String, serde_json::Value>) -> ast::Type {
56 let name = self.generate_new_name();
57 let ty = make::ty(&name.to_string());
58 let strukt = make::struct_(
59 None,
60 name,
61 None,
62 make::record_field_list(value.iter().sorted_unstable_by_key(|x| x.0).map(
63 |(name, value)| make::record_field(None, make::name(name), self.type_of(value)),
64 ))
65 .into(),
66 );
67 format_to!(self.result, "{}{}\n", self.serde_derive(), strukt);
68 ty
69 }
70
71 fn type_of(&mut self, value: &serde_json::Value) -> ast::Type {
72 match value {
73 serde_json::Value::Null => make::ty_unit(),
74 serde_json::Value::Bool(_) => make::ty("bool"),
75 serde_json::Value::Number(it) => make::ty(if it.is_i64() { "i64" } else { "f64" }),
76 serde_json::Value::String(_) => make::ty("String"),
77 serde_json::Value::Array(it) => {
78 let ty = match it.iter().next() {
79 Some(x) => self.type_of(x),
80 None => make::ty_placeholder(),
81 };
82 make::ty(&format!("Vec<{ty}>"))
83 }
84 serde_json::Value::Object(x) => self.build_struct(x),
85 }
86 }
87}
88
89pub(crate) fn json_in_items(
90 sema: &Semantics<'_, RootDatabase>,
91 acc: &mut Vec<Diagnostic>,
92 file_id: FileId,
93 node: &SyntaxNode,
94 config: &DiagnosticsConfig,
95) {
96 (|| {
97 if node.kind() == SyntaxKind::ERROR
98 && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY)
99 && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY)
100 {
101 let node_string = node.to_string();
102 if let Ok(it) = serde_json::from_str(&node_string) {
103 if let serde_json::Value::Object(it) = it {
104 let import_scope = ImportScope::find_insert_use_container(node, sema)?;
105 let range = node.text_range();
106 let mut edit = TextEdit::builder();
107 edit.delete(range);
108 let mut state = State::default();
109 let semantics_scope = sema.scope(node)?;
110 let scope_resolve =
111 |it| semantics_scope.speculative_resolve(&make::path_from_text(it));
112 let scope_has = |it| scope_resolve(it).is_some();
113 let deserialize_resolved = scope_resolve("::serde::Deserialize");
114 let serialize_resolved = scope_resolve("::serde::Serialize");
115 state.has_deserialize = deserialize_resolved.is_some();
116 state.has_serialize = serialize_resolved.is_some();
117 state.build_struct(&it);
118 edit.insert(range.start(), state.result);
119 acc.push(
120 Diagnostic::new(
121 "json-is-not-rust",
122 "JSON syntax is not valid as a Rust item",
123 range,
124 )
125 .severity(Severity::WeakWarning)
126 .with_fixes(Some(vec![{
127 let mut scb = SourceChangeBuilder::new(file_id);
f25598a0 128 let scope = match import_scope {
f2b60f7d
FG
129 ImportScope::File(it) => ImportScope::File(scb.make_mut(it)),
130 ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)),
131 ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)),
132 };
133 let current_module = semantics_scope.module();
134 if !scope_has("Serialize") {
135 if let Some(PathResolution::Def(it)) = serialize_resolved {
136 if let Some(it) = current_module.find_use_path_prefixed(
137 sema.db,
138 it,
139 config.insert_use.prefix_kind,
2b03887a 140 config.prefer_no_std,
f2b60f7d
FG
141 ) {
142 insert_use(
143 &scope,
144 mod_path_to_ast(&it),
145 &config.insert_use,
146 );
147 }
148 }
149 }
150 if !scope_has("Deserialize") {
151 if let Some(PathResolution::Def(it)) = deserialize_resolved {
152 if let Some(it) = current_module.find_use_path_prefixed(
153 sema.db,
154 it,
155 config.insert_use.prefix_kind,
2b03887a 156 config.prefer_no_std,
f2b60f7d
FG
157 ) {
158 insert_use(
159 &scope,
160 mod_path_to_ast(&it),
161 &config.insert_use,
162 );
163 }
164 }
165 }
166 let mut sc = scb.finish();
167 sc.insert_source_edit(file_id, edit.finish());
168 fix("convert_json_to_struct", "Convert JSON to struct", sc, range)
169 }])),
170 );
171 }
172 }
173 }
174 Some(())
175 })();
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::{
181 tests::{check_diagnostics_with_config, check_fix, check_no_fix},
182 DiagnosticsConfig,
183 };
184
185 #[test]
186 fn diagnostic_for_simple_case() {
187 let mut config = DiagnosticsConfig::test_sample();
188 config.disabled.insert("syntax-error".to_string());
189 check_diagnostics_with_config(
190 config,
191 r#"
192 { "foo": "bar" }
193 // ^^^^^^^^^^^^^^^^ 💡 weak: JSON syntax is not valid as a Rust item
194"#,
195 );
196 }
197
198 #[test]
199 fn types_of_primitives() {
200 check_fix(
201 r#"
202 //- /lib.rs crate:lib deps:serde
203 use serde::Serialize;
204
205 fn some_garbage() {
206
207 }
208
209 {$0
210 "foo": "bar",
211 "bar": 2.3,
212 "baz": null,
213 "bay": 57,
214 "box": true
215 }
216 //- /serde.rs crate:serde
217
218 pub trait Serialize {
219 fn serialize() -> u8;
220 }
221 "#,
222 r#"
223 use serde::Serialize;
224
225 fn some_garbage() {
226
227 }
228
229 #[derive(Serialize)]
230 struct Struct1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
231
232 "#,
233 );
234 }
235
236 #[test]
237 fn nested_structs() {
238 check_fix(
239 r#"
240 {$0
241 "foo": "bar",
242 "bar": {
243 "kind": "Object",
244 "value": {}
245 }
246 }
247 "#,
248 r#"
249 struct Struct3{ }
250 struct Struct2{ kind: String, value: Struct3 }
251 struct Struct1{ bar: Struct2, foo: String }
252
253 "#,
254 );
255 }
256
257 #[test]
258 fn arrays() {
259 check_fix(
260 r#"
261 //- /lib.rs crate:lib deps:serde
262 {
263 "of_string": ["foo", "2", "x"], $0
264 "of_object": [{
265 "x": 10,
266 "y": 20
267 }, {
268 "x": 10,
269 "y": 20
270 }],
271 "nested": [[[2]]],
272 "empty": []
273 }
274 //- /serde.rs crate:serde
275
276 pub trait Serialize {
277 fn serialize() -> u8;
278 }
279 pub trait Deserialize {
280 fn deserialize() -> u8;
281 }
282 "#,
283 r#"
284 use serde::Serialize;
285 use serde::Deserialize;
286
287 #[derive(Serialize, Deserialize)]
288 struct Struct2{ x: i64, y: i64 }
289 #[derive(Serialize, Deserialize)]
290 struct Struct1{ empty: Vec<_>, nested: Vec<Vec<Vec<i64>>>, of_object: Vec<Struct2>, of_string: Vec<String> }
291
292 "#,
293 );
294 }
295
296 #[test]
297 fn no_emit_outside_of_item_position() {
298 check_no_fix(
299 r#"
300 fn foo() {
301 let json = {$0
302 "foo": "bar",
303 "bar": {
304 "kind": "Object",
305 "value": {}
306 }
307 };
308 }
309 "#,
310 );
311 }
312}