1 //! This diagnostic provides an assist for creating a struct definition from a JSON
2 //! example.
3
4 use hir::{PathResolution, Semantics};
5 use 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 };
12 use itertools::Itertools;
13 use stdx::{format_to, never};
14 use syntax::{
15 ast::{self, make},
16 SyntaxKind, SyntaxNode,
17 };
18 use text_edit::TextEdit;
19
20 use crate::{fix, Diagnostic, DiagnosticsConfig, Severity};
21
22 #[derive(Default)]
23 struct State {
24 result: String,
25 struct_counts: usize,
26 has_serialize: bool,
27 has_deserialize: bool,
28 }
29
30 impl State {
generate_new_name(&mut self) -> ast::Name31 fn generate_new_name(&mut self) -> ast::Name {
32 self.struct_counts += 1;
33 make::name(&format!("Struct{}", self.struct_counts))
34 }
35
serde_derive(&self) -> String36 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
build_struct(&mut self, value: &serde_json::Map<String, serde_json::Value>) -> ast::Type55 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
type_of(&mut self, value: &serde_json::Value) -> ast::Type71 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
json_in_items( sema: &Semantics<'_, RootDatabase>, acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode, config: &DiagnosticsConfig, )89 pub(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(serde_json::Value::Object(it)) = serde_json::from_str(&node_string) {
103 let import_scope = ImportScope::find_insert_use_container(node, sema)?;
104 let range = node.text_range();
105 let mut edit = TextEdit::builder();
106 edit.delete(range);
107 let mut state = State::default();
108 let semantics_scope = sema.scope(node)?;
109 let scope_resolve =
110 |it| semantics_scope.speculative_resolve(&make::path_from_text(it));
111 let scope_has = |it| scope_resolve(it).is_some();
112 let deserialize_resolved = scope_resolve("::serde::Deserialize");
113 let serialize_resolved = scope_resolve("::serde::Serialize");
114 state.has_deserialize = deserialize_resolved.is_some();
115 state.has_serialize = serialize_resolved.is_some();
116 state.build_struct(&it);
117 edit.insert(range.start(), state.result);
118 acc.push(
119 Diagnostic::new(
120 "json-is-not-rust",
121 "JSON syntax is not valid as a Rust item",
122 range,
123 )
124 .severity(Severity::WeakWarning)
125 .with_fixes(Some(vec![{
126 let mut scb = SourceChangeBuilder::new(file_id);
127 let scope = match import_scope {
128 ImportScope::File(it) => ImportScope::File(scb.make_mut(it)),
129 ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)),
130 ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)),
131 };
132 let current_module = semantics_scope.module();
133 if !scope_has("Serialize") {
134 if let Some(PathResolution::Def(it)) = serialize_resolved {
135 if let Some(it) = current_module.find_use_path_prefixed(
136 sema.db,
137 it,
138 config.insert_use.prefix_kind,
139 config.prefer_no_std,
140 ) {
141 insert_use(&scope, mod_path_to_ast(&it), &config.insert_use);
142 }
143 }
144 }
145 if !scope_has("Deserialize") {
146 if let Some(PathResolution::Def(it)) = deserialize_resolved {
147 if let Some(it) = current_module.find_use_path_prefixed(
148 sema.db,
149 it,
150 config.insert_use.prefix_kind,
151 config.prefer_no_std,
152 ) {
153 insert_use(&scope, mod_path_to_ast(&it), &config.insert_use);
154 }
155 }
156 }
157 let mut sc = scb.finish();
158 sc.insert_source_edit(file_id, edit.finish());
159 fix("convert_json_to_struct", "Convert JSON to struct", sc, range)
160 }])),
161 );
162 }
163 }
164 Some(())
165 })();
166 }
167
168 #[cfg(test)]
169 mod tests {
170 use crate::{
171 tests::{check_diagnostics_with_config, check_fix, check_no_fix},
172 DiagnosticsConfig,
173 };
174
175 #[test]
diagnostic_for_simple_case()176 fn diagnostic_for_simple_case() {
177 let mut config = DiagnosticsConfig::test_sample();
178 config.disabled.insert("syntax-error".to_string());
179 check_diagnostics_with_config(
180 config,
181 r#"
182 { "foo": "bar" }
183 // ^^^^^^^^^^^^^^^^ weak: JSON syntax is not valid as a Rust item
184 "#,
185 );
186 }
187
188 #[test]
types_of_primitives()189 fn types_of_primitives() {
190 check_fix(
191 r#"
192 //- /lib.rs crate:lib deps:serde
193 use serde::Serialize;
194
195 fn some_garbage() {
196
197 }
198
199 {$0
200 "foo": "bar",
201 "bar": 2.3,
202 "baz": null,
203 "bay": 57,
204 "box": true
205 }
206 //- /serde.rs crate:serde
207
208 pub trait Serialize {
209 fn serialize() -> u8;
210 }
211 "#,
212 r#"
213 use serde::Serialize;
214
215 fn some_garbage() {
216
217 }
218
219 #[derive(Serialize)]
220 struct Struct1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
221
222 "#,
223 );
224 }
225
226 #[test]
nested_structs()227 fn nested_structs() {
228 check_fix(
229 r#"
230 {$0
231 "foo": "bar",
232 "bar": {
233 "kind": "Object",
234 "value": {}
235 }
236 }
237 "#,
238 r#"
239 struct Struct3{ }
240 struct Struct2{ kind: String, value: Struct3 }
241 struct Struct1{ bar: Struct2, foo: String }
242
243 "#,
244 );
245 }
246
247 #[test]
arrays()248 fn arrays() {
249 check_fix(
250 r#"
251 //- /lib.rs crate:lib deps:serde
252 {
253 "of_string": ["foo", "2", "x"], $0
254 "of_object": [{
255 "x": 10,
256 "y": 20
257 }, {
258 "x": 10,
259 "y": 20
260 }],
261 "nested": [[[2]]],
262 "empty": []
263 }
264 //- /serde.rs crate:serde
265
266 pub trait Serialize {
267 fn serialize() -> u8;
268 }
269 pub trait Deserialize {
270 fn deserialize() -> u8;
271 }
272 "#,
273 r#"
274 use serde::Serialize;
275 use serde::Deserialize;
276
277 #[derive(Serialize, Deserialize)]
278 struct Struct2{ x: i64, y: i64 }
279 #[derive(Serialize, Deserialize)]
280 struct Struct1{ empty: Vec<_>, nested: Vec<Vec<Vec<i64>>>, of_object: Vec<Struct2>, of_string: Vec<String> }
281
282 "#,
283 );
284 }
285
286 #[test]
no_emit_outside_of_item_position()287 fn no_emit_outside_of_item_position() {
288 check_no_fix(
289 r#"
290 fn foo() {
291 let json = {$0
292 "foo": "bar",
293 "bar": {
294 "kind": "Object",
295 "value": {}
296 }
297 };
298 }
299 "#,
300 );
301 }
302 }
303