Unnamed repository; edit this file 'description' to name the repository.
Import serde derives on converting json to struct
hkalbasi 2022-08-06
parent c1a175f · commit 851f6db
-rw-r--r--crates/ide-assists/src/assist_context.rs157
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs10
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs10
-rw-r--r--crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs4
-rw-r--r--crates/ide-assists/src/handlers/generate_deref.rs4
-rw-r--r--crates/ide-assists/src/handlers/introduce_named_lifetime.rs4
-rw-r--r--crates/ide-assists/src/handlers/remove_unused_param.rs5
-rw-r--r--crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs4
-rw-r--r--crates/ide-assists/src/utils.rs4
-rw-r--r--crates/ide-db/src/source_change.rs136
-rw-r--r--crates/ide-diagnostics/src/handlers/inactive_code.rs2
-rw-r--r--crates/ide-diagnostics/src/handlers/json_is_not_rust.rs191
-rw-r--r--crates/ide-diagnostics/src/handlers/macro_error.rs2
-rw-r--r--crates/ide-diagnostics/src/lib.rs33
-rw-r--r--crates/ide-diagnostics/src/tests.rs12
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs1
17 files changed, 357 insertions, 224 deletions
diff --git a/crates/ide-assists/src/assist_context.rs b/crates/ide-assists/src/assist_context.rs
index f9b4266142..8c7670e0cb 100644
--- a/crates/ide-assists/src/assist_context.rs
+++ b/crates/ide-assists/src/assist_context.rs
@@ -1,28 +1,20 @@
//! See [`AssistContext`].
-use std::mem;
-
use hir::Semantics;
-use ide_db::{
- base_db::{AnchoredPathBuf, FileId, FileRange},
- SnippetCap,
-};
-use ide_db::{
- label::Label,
- source_change::{FileSystemEdit, SourceChange},
- RootDatabase,
-};
+use ide_db::base_db::{FileId, FileRange};
+use ide_db::{label::Label, RootDatabase};
use syntax::{
algo::{self, find_node_at_offset, find_node_at_range},
- AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
- SyntaxToken, TextRange, TextSize, TokenAtOffset,
+ AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange,
+ TextSize, TokenAtOffset,
};
-use text_edit::{TextEdit, TextEditBuilder};
use crate::{
assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel,
};
+pub(crate) use ide_db::source_change::{SourceChangeBuilder, TreeMutator};
+
/// `AssistContext` allows to apply an assist or check if it could be applied.
///
/// Assists use a somewhat over-engineered approach, given the current needs.
@@ -163,7 +155,7 @@ impl Assists {
id: AssistId,
label: impl Into<String>,
target: TextRange,
- f: impl FnOnce(&mut AssistBuilder),
+ f: impl FnOnce(&mut SourceChangeBuilder),
) -> Option<()> {
let mut f = Some(f);
self.add_impl(None, id, label.into(), target, &mut |it| f.take().unwrap()(it))
@@ -175,7 +167,7 @@ impl Assists {
id: AssistId,
label: impl Into<String>,
target: TextRange,
- f: impl FnOnce(&mut AssistBuilder),
+ f: impl FnOnce(&mut SourceChangeBuilder),
) -> Option<()> {
let mut f = Some(f);
self.add_impl(Some(group), id, label.into(), target, &mut |it| f.take().unwrap()(it))
@@ -187,7 +179,7 @@ impl Assists {
id: AssistId,
label: String,
target: TextRange,
- f: &mut dyn FnMut(&mut AssistBuilder),
+ f: &mut dyn FnMut(&mut SourceChangeBuilder),
) -> Option<()> {
if !self.is_allowed(&id) {
return None;
@@ -195,7 +187,7 @@ impl Assists {
let mut trigger_signature_help = false;
let source_change = if self.resolve.should_resolve(&id) {
- let mut builder = AssistBuilder::new(self.file);
+ let mut builder = SourceChangeBuilder::new(self.file);
f(&mut builder);
trigger_signature_help = builder.trigger_signature_help;
Some(builder.finish())
@@ -216,132 +208,3 @@ impl Assists {
}
}
}
-
-pub(crate) struct AssistBuilder {
- edit: TextEditBuilder,
- file_id: FileId,
- source_change: SourceChange,
- trigger_signature_help: bool,
-
- /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
- mutated_tree: Option<TreeMutator>,
-}
-
-pub(crate) struct TreeMutator {
- immutable: SyntaxNode,
- mutable_clone: SyntaxNode,
-}
-
-impl TreeMutator {
- pub(crate) fn new(immutable: &SyntaxNode) -> TreeMutator {
- let immutable = immutable.ancestors().last().unwrap();
- let mutable_clone = immutable.clone_for_update();
- TreeMutator { immutable, mutable_clone }
- }
-
- pub(crate) fn make_mut<N: AstNode>(&self, node: &N) -> N {
- N::cast(self.make_syntax_mut(node.syntax())).unwrap()
- }
-
- pub(crate) fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
- let ptr = SyntaxNodePtr::new(node);
- ptr.to_node(&self.mutable_clone)
- }
-}
-
-impl AssistBuilder {
- pub(crate) fn new(file_id: FileId) -> AssistBuilder {
- AssistBuilder {
- edit: TextEdit::builder(),
- file_id,
- source_change: SourceChange::default(),
- trigger_signature_help: false,
- mutated_tree: None,
- }
- }
-
- pub(crate) fn edit_file(&mut self, file_id: FileId) {
- self.commit();
- self.file_id = file_id;
- }
-
- fn commit(&mut self) {
- if let Some(tm) = self.mutated_tree.take() {
- algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
- }
-
- let edit = mem::take(&mut self.edit).finish();
- if !edit.is_empty() {
- self.source_change.insert_source_edit(self.file_id, edit);
- }
- }
-
- pub(crate) fn make_mut<N: AstNode>(&mut self, node: N) -> N {
- self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
- }
- /// Returns a copy of the `node`, suitable for mutation.
- ///
- /// Syntax trees in rust-analyzer are typically immutable, and mutating
- /// operations panic at runtime. However, it is possible to make a copy of
- /// the tree and mutate the copy freely. Mutation is based on interior
- /// mutability, and different nodes in the same tree see the same mutations.
- ///
- /// The typical pattern for an assist is to find specific nodes in the read
- /// phase, and then get their mutable couterparts using `make_mut` in the
- /// mutable state.
- pub(crate) fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
- self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
- }
-
- /// Remove specified `range` of text.
- pub(crate) fn delete(&mut self, range: TextRange) {
- self.edit.delete(range)
- }
- /// Append specified `text` at the given `offset`
- pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
- self.edit.insert(offset, text.into())
- }
- /// Append specified `snippet` at the given `offset`
- pub(crate) fn insert_snippet(
- &mut self,
- _cap: SnippetCap,
- offset: TextSize,
- snippet: impl Into<String>,
- ) {
- self.source_change.is_snippet = true;
- self.insert(offset, snippet);
- }
- /// Replaces specified `range` of text with a given string.
- pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
- self.edit.replace(range, replace_with.into())
- }
- /// Replaces specified `range` of text with a given `snippet`.
- pub(crate) fn replace_snippet(
- &mut self,
- _cap: SnippetCap,
- range: TextRange,
- snippet: impl Into<String>,
- ) {
- self.source_change.is_snippet = true;
- self.replace(range, snippet);
- }
- pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
- algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
- }
- pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
- let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
- self.source_change.push_file_system_edit(file_system_edit);
- }
- pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
- let file_system_edit = FileSystemEdit::MoveFile { src, dst };
- self.source_change.push_file_system_edit(file_system_edit);
- }
- pub(crate) fn trigger_signature_help(&mut self) {
- self.trigger_signature_help = true;
- }
-
- fn finish(mut self) -> SourceChange {
- self.commit();
- mem::take(&mut self.source_change)
- }
-}
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 4ab8e93a29..d8f5227084 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -5,7 +5,7 @@ use syntax::{
match_ast, SyntaxNode,
};
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
// Assist: convert_tuple_struct_to_named_struct
//
@@ -80,7 +80,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
fn edit_struct_def(
ctx: &AssistContext<'_>,
- edit: &mut AssistBuilder,
+ edit: &mut SourceChangeBuilder,
strukt: &Either<ast::Struct, ast::Variant>,
tuple_fields: ast::TupleFieldList,
names: Vec<ast::Name>,
@@ -122,7 +122,7 @@ fn edit_struct_def(
fn edit_struct_references(
ctx: &AssistContext<'_>,
- edit: &mut AssistBuilder,
+ edit: &mut SourceChangeBuilder,
strukt: Either<hir::Struct, hir::Variant>,
names: &[ast::Name],
) {
@@ -132,7 +132,7 @@ fn edit_struct_references(
};
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
- let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
+ let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> {
match_ast! {
match node {
ast::TupleStructPat(tuple_struct_pat) => {
@@ -203,7 +203,7 @@ fn edit_struct_references(
fn edit_field_references(
ctx: &AssistContext<'_>,
- edit: &mut AssistBuilder,
+ edit: &mut SourceChangeBuilder,
fields: impl Iterator<Item = ast::TupleField>,
names: &[ast::Name],
) {
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index c1f57532bb..dc581ff3bd 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -8,7 +8,7 @@ use syntax::{
TextRange,
};
-use crate::assist_context::{AssistBuilder, AssistContext, Assists};
+use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
// Assist: destructure_tuple_binding
//
@@ -151,7 +151,7 @@ struct TupleData {
}
fn edit_tuple_assignment(
ctx: &AssistContext<'_>,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
data: &TupleData,
in_sub_pattern: bool,
) {
@@ -195,7 +195,7 @@ fn edit_tuple_assignment(
fn edit_tuple_usages(
data: &TupleData,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
ctx: &AssistContext<'_>,
in_sub_pattern: bool,
) {
@@ -211,7 +211,7 @@ fn edit_tuple_usages(
}
fn edit_tuple_usage(
ctx: &AssistContext<'_>,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
usage: &FileReference,
data: &TupleData,
in_sub_pattern: bool,
@@ -239,7 +239,7 @@ fn edit_tuple_usage(
fn edit_tuple_field_usage(
ctx: &AssistContext<'_>,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
data: &TupleData,
index: TupleIndex,
) {
diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
index a93648f2d3..dfb5652126 100644
--- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -20,7 +20,7 @@ use syntax::{
SyntaxNode, T,
};
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
// Assist: extract_struct_from_enum_variant
//
@@ -374,7 +374,7 @@ fn apply_references(
fn process_references(
ctx: &AssistContext<'_>,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
visited_modules: &mut FxHashSet<Module>,
enum_module_def: &ModuleDef,
variant_hir_name: &Name,
diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs
index b9637ee8d7..b484635121 100644
--- a/crates/ide-assists/src/handlers/generate_deref.rs
+++ b/crates/ide-assists/src/handlers/generate_deref.rs
@@ -8,7 +8,7 @@ use syntax::{
};
use crate::{
- assist_context::{AssistBuilder, AssistContext, Assists},
+ assist_context::{AssistContext, Assists, SourceChangeBuilder},
utils::generate_trait_impl_text,
AssistId, AssistKind,
};
@@ -120,7 +120,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
}
fn generate_edit(
- edit: &mut AssistBuilder,
+ edit: &mut SourceChangeBuilder,
strukt: ast::Struct,
field_type_syntax: &SyntaxNode,
field_name: impl Display,
diff --git a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
index ce91dd2370..2fc754e3e5 100644
--- a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
@@ -5,7 +5,7 @@ use syntax::{
AstNode, TextRange,
};
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
static ASSIST_NAME: &str = "introduce_named_lifetime";
static ASSIST_LABEL: &str = "Introduce named lifetime";
@@ -140,7 +140,7 @@ enum NeedsLifetime {
}
impl NeedsLifetime {
- fn make_mut(self, builder: &mut AssistBuilder) -> Self {
+ fn make_mut(self, builder: &mut SourceChangeBuilder) -> Self {
match self {
Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)),
Self::RefType(it) => Self::RefType(builder.make_mut(it)),
diff --git a/crates/ide-assists/src/handlers/remove_unused_param.rs b/crates/ide-assists/src/handlers/remove_unused_param.rs
index 59ea94ea1f..bd2e8fbe38 100644
--- a/crates/ide-assists/src/handlers/remove_unused_param.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_param.rs
@@ -8,7 +8,8 @@ use syntax::{
use SyntaxKind::WHITESPACE;
use crate::{
- assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
+ assist_context::SourceChangeBuilder, utils::next_prev, AssistContext, AssistId, AssistKind,
+ Assists,
};
// Assist: remove_unused_param
@@ -88,7 +89,7 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) ->
fn process_usages(
ctx: &AssistContext<'_>,
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
file_id: FileId,
references: Vec<FileReference>,
arg_to_remove: usize,
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index bd50208da5..d139f78a6f 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -10,7 +10,7 @@ use syntax::{
};
use crate::{
- assist_context::{AssistBuilder, AssistContext, Assists},
+ assist_context::{AssistContext, Assists, SourceChangeBuilder},
utils::{
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
generate_trait_impl_text, render_snippet, Cursor, DefaultMethods,
@@ -224,7 +224,7 @@ fn impl_def_from_trait(
}
fn update_attribute(
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
old_derives: &[ast::Path],
old_tree: &ast::TokenTree,
old_trait_path: &ast::Path,
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 3e61d0741d..103e3259fa 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -20,7 +20,7 @@ use syntax::{
SyntaxNode, TextRange, TextSize, T,
};
-use crate::assist_context::{AssistBuilder, AssistContext};
+use crate::assist_context::{AssistContext, SourceChangeBuilder};
pub(crate) mod suggest_name;
mod gen_trait_fn_body;
@@ -484,7 +484,7 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
}
pub(crate) fn add_method_to_adt(
- builder: &mut AssistBuilder,
+ builder: &mut SourceChangeBuilder,
adt: &ast::Adt,
impl_def: Option<ast::Impl>,
method: &str,
diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs
index 8132c73ef2..21314ad74e 100644
--- a/crates/ide-db/src/source_change.rs
+++ b/crates/ide-db/src/source_change.rs
@@ -3,12 +3,15 @@
//!
//! It can be viewed as a dual for `Change`.
-use std::{collections::hash_map::Entry, iter};
+use std::{collections::hash_map::Entry, iter, mem};
use base_db::{AnchoredPathBuf, FileId};
use rustc_hash::FxHashMap;
use stdx::never;
-use text_edit::TextEdit;
+use syntax::{algo, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
+use text_edit::{TextEdit, TextEditBuilder};
+
+use crate::SnippetCap;
#[derive(Default, Debug, Clone)]
pub struct SourceChange {
@@ -81,6 +84,135 @@ impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
}
}
+pub struct SourceChangeBuilder {
+ pub edit: TextEditBuilder,
+ pub file_id: FileId,
+ pub source_change: SourceChange,
+ pub trigger_signature_help: bool,
+
+ /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
+ pub mutated_tree: Option<TreeMutator>,
+}
+
+pub struct TreeMutator {
+ immutable: SyntaxNode,
+ mutable_clone: SyntaxNode,
+}
+
+impl TreeMutator {
+ pub fn new(immutable: &SyntaxNode) -> TreeMutator {
+ let immutable = immutable.ancestors().last().unwrap();
+ let mutable_clone = immutable.clone_for_update();
+ TreeMutator { immutable, mutable_clone }
+ }
+
+ pub fn make_mut<N: AstNode>(&self, node: &N) -> N {
+ N::cast(self.make_syntax_mut(node.syntax())).unwrap()
+ }
+
+ pub fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
+ let ptr = SyntaxNodePtr::new(node);
+ ptr.to_node(&self.mutable_clone)
+ }
+}
+
+impl SourceChangeBuilder {
+ pub fn new(file_id: FileId) -> SourceChangeBuilder {
+ SourceChangeBuilder {
+ edit: TextEdit::builder(),
+ file_id,
+ source_change: SourceChange::default(),
+ trigger_signature_help: false,
+ mutated_tree: None,
+ }
+ }
+
+ pub fn edit_file(&mut self, file_id: FileId) {
+ self.commit();
+ self.file_id = file_id;
+ }
+
+ fn commit(&mut self) {
+ if let Some(tm) = self.mutated_tree.take() {
+ algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
+ }
+
+ let edit = mem::take(&mut self.edit).finish();
+ if !edit.is_empty() {
+ self.source_change.insert_source_edit(self.file_id, edit);
+ }
+ }
+
+ pub fn make_mut<N: AstNode>(&mut self, node: N) -> N {
+ self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
+ }
+ /// Returns a copy of the `node`, suitable for mutation.
+ ///
+ /// Syntax trees in rust-analyzer are typically immutable, and mutating
+ /// operations panic at runtime. However, it is possible to make a copy of
+ /// the tree and mutate the copy freely. Mutation is based on interior
+ /// mutability, and different nodes in the same tree see the same mutations.
+ ///
+ /// The typical pattern for an assist is to find specific nodes in the read
+ /// phase, and then get their mutable couterparts using `make_mut` in the
+ /// mutable state.
+ pub fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
+ self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
+ }
+
+ /// Remove specified `range` of text.
+ pub fn delete(&mut self, range: TextRange) {
+ self.edit.delete(range)
+ }
+ /// Append specified `text` at the given `offset`
+ pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
+ self.edit.insert(offset, text.into())
+ }
+ /// Append specified `snippet` at the given `offset`
+ pub fn insert_snippet(
+ &mut self,
+ _cap: SnippetCap,
+ offset: TextSize,
+ snippet: impl Into<String>,
+ ) {
+ self.source_change.is_snippet = true;
+ self.insert(offset, snippet);
+ }
+ /// Replaces specified `range` of text with a given string.
+ pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
+ self.edit.replace(range, replace_with.into())
+ }
+ /// Replaces specified `range` of text with a given `snippet`.
+ pub fn replace_snippet(
+ &mut self,
+ _cap: SnippetCap,
+ range: TextRange,
+ snippet: impl Into<String>,
+ ) {
+ self.source_change.is_snippet = true;
+ self.replace(range, snippet);
+ }
+ pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
+ algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
+ }
+ pub fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
+ let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
+ self.source_change.push_file_system_edit(file_system_edit);
+ }
+ pub fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
+ let file_system_edit = FileSystemEdit::MoveFile { src, dst };
+ self.source_change.push_file_system_edit(file_system_edit);
+ }
+ pub fn trigger_signature_help(&mut self) {
+ self.trigger_signature_help = true;
+ }
+
+ pub fn finish(mut self) -> SourceChange {
+ self.commit();
+ mem::take(&mut self.source_change)
+ }
+}
+
#[derive(Debug, Clone)]
pub enum FileSystemEdit {
CreateFile { dst: AnchoredPathBuf, initial_contents: String },
diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs
index 97ea5c456a..5694f33525 100644
--- a/crates/ide-diagnostics/src/handlers/inactive_code.rs
+++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs
@@ -43,7 +43,7 @@ mod tests {
use crate::{tests::check_diagnostics_with_config, DiagnosticsConfig};
pub(crate) fn check(ra_fixture: &str) {
- let config = DiagnosticsConfig::default();
+ let config = DiagnosticsConfig::test_sample();
check_diagnostics_with_config(config, ra_fixture)
}
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)]
diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs
index d6a66dc150..43ff4ed5a6 100644
--- a/crates/ide-diagnostics/src/handlers/macro_error.rs
+++ b/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -79,7 +79,7 @@ pub macro panic {
#[test]
fn include_macro_should_allow_empty_content() {
- let mut config = DiagnosticsConfig::default();
+ let mut config = DiagnosticsConfig::test_sample();
// FIXME: This is a false-positive, the file is actually linked in via
// `include!` macro
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 7034f010e1..61e63ea7a9 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -60,6 +60,7 @@ use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
use ide_db::{
assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
base_db::{FileId, FileRange, SourceDatabase},
+ imports::insert_use::InsertUseConfig,
label::Label,
source_change::SourceChange,
FxHashSet, RootDatabase,
@@ -140,13 +141,37 @@ impl Default for ExprFillDefaultMode {
}
}
-#[derive(Default, Debug, Clone)]
+#[derive(Debug, Clone)]
pub struct DiagnosticsConfig {
pub proc_macros_enabled: bool,
pub proc_attr_macros_enabled: bool,
pub disable_experimental: bool,
pub disabled: FxHashSet<String>,
pub expr_fill_default: ExprFillDefaultMode,
+ // FIXME: We may want to include a whole `AssistConfig` here
+ pub insert_use: InsertUseConfig,
+}
+
+impl DiagnosticsConfig {
+ pub fn test_sample() -> Self {
+ use hir::PrefixKind;
+ use ide_db::imports::insert_use::ImportGranularity;
+
+ Self {
+ proc_macros_enabled: Default::default(),
+ proc_attr_macros_enabled: Default::default(),
+ disable_experimental: Default::default(),
+ disabled: Default::default(),
+ expr_fill_default: Default::default(),
+ insert_use: InsertUseConfig {
+ granularity: ImportGranularity::Preserve,
+ enforce_granularity: false,
+ prefix_kind: PrefixKind::Plain,
+ group: false,
+ skip_glob_imports: false,
+ },
+ }
+ }
}
struct DiagnosticsContext<'a> {
@@ -173,10 +198,12 @@ pub fn diagnostics(
}),
);
- for node in parse.tree().syntax().descendants() {
+ let parse = sema.parse(file_id);
+
+ for node in parse.syntax().descendants() {
handlers::useless_braces::useless_braces(&mut res, file_id, &node);
handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
- handlers::json_is_not_rust::json_in_items(&mut res, file_id, &node);
+ handlers::json_is_not_rust::json_in_items(&sema, &mut res, file_id, &node, &config);
}
let module = sema.to_module_def(file_id);
diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs
index 7312bca32f..729619cfde 100644
--- a/crates/ide-diagnostics/src/tests.rs
+++ b/crates/ide-diagnostics/src/tests.rs
@@ -37,7 +37,7 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
let after = trim_indent(ra_fixture_after);
let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
- let mut conf = DiagnosticsConfig::default();
+ let mut conf = DiagnosticsConfig::test_sample();
conf.expr_fill_default = ExprFillDefaultMode::Default;
let diagnostic =
super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
@@ -69,7 +69,7 @@ pub(crate) fn check_no_fix(ra_fixture: &str) {
let (db, file_position) = RootDatabase::with_position(ra_fixture);
let diagnostic = super::diagnostics(
&db,
- &DiagnosticsConfig::default(),
+ &DiagnosticsConfig::test_sample(),
&AssistResolveStrategy::All,
file_position.file_id,
)
@@ -82,7 +82,7 @@ pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
let (db, file_id) = RootDatabase::with_single_file(ra_fixture);
let diagnostics = super::diagnostics(
&db,
- &DiagnosticsConfig::default(),
+ &DiagnosticsConfig::test_sample(),
&AssistResolveStrategy::All,
file_id,
);
@@ -91,7 +91,7 @@ pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
#[track_caller]
pub(crate) fn check_diagnostics(ra_fixture: &str) {
- let mut config = DiagnosticsConfig::default();
+ let mut config = DiagnosticsConfig::test_sample();
config.disabled.insert("inactive-code".to_string());
check_diagnostics_with_config(config, ra_fixture)
}
@@ -127,7 +127,7 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
#[test]
fn test_disabled_diagnostics() {
- let mut config = DiagnosticsConfig::default();
+ let mut config = DiagnosticsConfig::test_sample();
config.disabled.insert("unresolved-module".into());
let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
@@ -137,7 +137,7 @@ fn test_disabled_diagnostics() {
let diagnostics = super::diagnostics(
&db,
- &DiagnosticsConfig::default(),
+ &DiagnosticsConfig::test_sample(),
&AssistResolveStrategy::All,
file_id,
);
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 52511ceb58..247007db0a 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -43,7 +43,7 @@ impl flags::Diagnostics {
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
for diagnostic in analysis
.diagnostics(
- &DiagnosticsConfig::default(),
+ &DiagnosticsConfig::test_sample(),
AssistResolveStrategy::None,
file_id,
)
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index ac0fdf85a7..1629c1dd32 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -881,6 +881,7 @@ impl Config {
ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
},
+ insert_use: self.insert_use_config(),
}
}