Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-diagnostics/src/handlers/json_is_not_rust.rs')
| -rw-r--r-- | crates/ide-diagnostics/src/handlers/json_is_not_rust.rs | 191 |
1 files changed, 150 insertions, 41 deletions
diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index aa7fcffb48..a21db5b2ce 100644 --- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -1,21 +1,30 @@ //! This diagnostic provides an assist for creating a struct definition from a JSON //! example. -use ide_db::{base_db::FileId, source_change::SourceChange}; +use hir::{PathResolution, Semantics}; +use ide_db::{ + base_db::FileId, + helpers::mod_path_to_ast, + imports::insert_use::{insert_use, ImportScope}, + source_change::SourceChangeBuilder, + RootDatabase, +}; use itertools::Itertools; -use stdx::format_to; +use stdx::{format_to, never}; use syntax::{ ast::{self, make}, SyntaxKind, SyntaxNode, }; use text_edit::TextEdit; -use crate::{fix, Diagnostic, Severity}; +use crate::{fix, Diagnostic, DiagnosticsConfig, Severity}; #[derive(Default)] struct State { result: String, struct_counts: usize, + has_serialize: bool, + has_deserialize: bool, } impl State { @@ -24,6 +33,25 @@ impl State { make::name(&format!("Struct{}", self.struct_counts)) } + fn serde_derive(&self) -> String { + let mut v = vec![]; + if self.has_serialize { + v.push("Serialize"); + } + if self.has_deserialize { + v.push("Deserialize"); + } + match v.as_slice() { + [] => "".to_string(), + [x] => format!("#[derive({x})]\n"), + [x, y] => format!("#[derive({x}, {y})]\n"), + _ => { + never!(); + "".to_string() + } + } + } + fn build_struct(&mut self, value: &serde_json::Map<String, serde_json::Value>) -> ast::Type { let name = self.generate_new_name(); let ty = make::ty(&name.to_string()); @@ -36,7 +64,7 @@ impl State { )) .into(), ); - format_to!(self.result, "#[derive(Serialize, Deserialize)]\n{}\n", strukt); + format_to!(self.result, "{}{}\n", self.serde_derive(), strukt); ty } @@ -44,10 +72,10 @@ impl State { match value { serde_json::Value::Null => make::ty_unit(), serde_json::Value::Bool(_) => make::ty("bool"), - serde_json::Value::Number(x) => make::ty(if x.is_i64() { "i64" } else { "f64" }), + serde_json::Value::Number(it) => make::ty(if it.is_i64() { "i64" } else { "f64" }), serde_json::Value::String(_) => make::ty("String"), - serde_json::Value::Array(x) => { - let ty = match x.iter().next() { + serde_json::Value::Array(it) => { + let ty = match it.iter().next() { Some(x) => self.type_of(x), None => make::ty_placeholder(), }; @@ -58,37 +86,91 @@ impl State { } } -pub(crate) fn json_in_items(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { - if node.kind() == SyntaxKind::ERROR - && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY) - && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY) - { - let node_string = node.to_string(); - if let Ok(x) = serde_json::from_str(&node_string) { - if let serde_json::Value::Object(x) = x { - let range = node.text_range(); - let mut edit = TextEdit::builder(); - edit.delete(range); - let mut state = State::default(); - state.build_struct(&x); - edit.insert(range.start(), state.result); - acc.push( - Diagnostic::new( - "json-is-not-rust", - "JSON syntax is not valid as a Rust item", - range, - ) - .severity(Severity::WeakWarning) - .with_fixes(Some(vec![fix( - "convert_json_to_struct", - "Convert JSON to struct", - SourceChange::from_text_edit(file_id, edit.finish()), - range, - )])), - ); +pub(crate) fn json_in_items( + sema: &Semantics<'_, RootDatabase>, + acc: &mut Vec<Diagnostic>, + file_id: FileId, + node: &SyntaxNode, + config: &DiagnosticsConfig, +) { + (|| { + if node.kind() == SyntaxKind::ERROR + && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY) + && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY) + { + let node_string = node.to_string(); + if let Ok(it) = serde_json::from_str(&node_string) { + if let serde_json::Value::Object(it) = it { + let import_scope = ImportScope::find_insert_use_container(node, sema)?; + let range = node.text_range(); + let mut edit = TextEdit::builder(); + edit.delete(range); + let mut state = State::default(); + let semantics_scope = sema.scope(node)?; + let scope_resolve = + |it| semantics_scope.speculative_resolve(&make::path_from_text(it)); + let scope_has = |it| scope_resolve(it).is_some(); + let deserialize_resolved = scope_resolve("::serde::Deserialize"); + let serialize_resolved = scope_resolve("::serde::Serialize"); + state.has_deserialize = deserialize_resolved.is_some(); + state.has_serialize = serialize_resolved.is_some(); + state.build_struct(&it); + edit.insert(range.start(), state.result); + acc.push( + Diagnostic::new( + "json-is-not-rust", + "JSON syntax is not valid as a Rust item", + range, + ) + .severity(Severity::WeakWarning) + .with_fixes(Some(vec![{ + let mut scb = SourceChangeBuilder::new(file_id); + let scope = match import_scope.clone() { + ImportScope::File(it) => ImportScope::File(scb.make_mut(it)), + ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)), + ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)), + }; + let current_module = semantics_scope.module(); + if !scope_has("Serialize") { + if let Some(PathResolution::Def(it)) = serialize_resolved { + if let Some(it) = current_module.find_use_path_prefixed( + sema.db, + it, + config.insert_use.prefix_kind, + ) { + insert_use( + &scope, + mod_path_to_ast(&it), + &config.insert_use, + ); + } + } + } + if !scope_has("Deserialize") { + if let Some(PathResolution::Def(it)) = deserialize_resolved { + if let Some(it) = current_module.find_use_path_prefixed( + sema.db, + it, + config.insert_use.prefix_kind, + ) { + insert_use( + &scope, + mod_path_to_ast(&it), + &config.insert_use, + ); + } + } + } + let mut sc = scb.finish(); + sc.insert_source_edit(file_id, edit.finish()); + fix("convert_json_to_struct", "Convert JSON to struct", sc, range) + }])), + ); + } } } - } + Some(()) + })(); } #[cfg(test)] @@ -100,7 +182,7 @@ mod tests { #[test] fn diagnostic_for_simple_case() { - let mut config = DiagnosticsConfig::default(); + let mut config = DiagnosticsConfig::test_sample(); config.disabled.insert("syntax-error".to_string()); check_diagnostics_with_config( config, @@ -115,6 +197,13 @@ mod tests { fn types_of_primitives() { check_fix( r#" + //- /lib.rs crate:lib deps:serde + use serde::Serialize; + + fn some_garbage() { + + } + {$0 "foo": "bar", "bar": 2.3, @@ -122,9 +211,20 @@ mod tests { "bay": 57, "box": true } + //- /serde.rs crate:serde + + pub trait Serialize { + fn serialize() -> u8; + } "#, r#" - #[derive(Serialize, Deserialize)] + use serde::Serialize; + + fn some_garbage() { + + } + + #[derive(Serialize)] struct Struct1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String } "#, @@ -144,11 +244,8 @@ mod tests { } "#, r#" - #[derive(Serialize, Deserialize)] struct Struct3{ } - #[derive(Serialize, Deserialize)] struct Struct2{ kind: String, value: Struct3 } - #[derive(Serialize, Deserialize)] struct Struct1{ bar: Struct2, foo: String } "#, @@ -159,6 +256,7 @@ mod tests { fn arrays() { check_fix( r#" + //- /lib.rs crate:lib deps:serde { "of_string": ["foo", "2", "x"], $0 "of_object": [{ @@ -171,8 +269,19 @@ mod tests { "nested": [[[2]]], "empty": [] } + //- /serde.rs crate:serde + + pub trait Serialize { + fn serialize() -> u8; + } + pub trait Deserialize { + fn deserialize() -> u8; + } "#, r#" + use serde::Serialize; + use serde::Deserialize; + #[derive(Serialize, Deserialize)] struct Struct2{ x: i64, y: i64 } #[derive(Serialize, Deserialize)] |