From eb81731600d1bc50efc00e32b9fc2244a2af52ad Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 18 Aug 2020 16:22:01 +0200 Subject: Minor --- crates/ide/src/diagnostics.rs | 35 ++++- crates/ide/src/diagnostics/diagnostics_with_fix.rs | 171 -------------------- crates/ide/src/diagnostics/fixes.rs | 175 +++++++++++++++++++++ crates/ide/src/lib.rs | 31 +--- 4 files changed, 208 insertions(+), 204 deletions(-) delete mode 100644 crates/ide/src/diagnostics/diagnostics_with_fix.rs create mode 100644 crates/ide/src/diagnostics/fixes.rs (limited to 'crates/ide/src') diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 89ff55490..1f85805d2 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -4,7 +4,7 @@ //! macro-expanded files, but we need to present them to the users in terms of //! original files. So we need to map the ranges. -mod diagnostics_with_fix; +mod fixes; use std::cell::RefCell; @@ -19,9 +19,38 @@ use syntax::{ }; use text_edit::TextEdit; -use crate::{Diagnostic, FileId, Fix, SourceFileEdit}; +use crate::{FileId, SourceChange, SourceFileEdit}; -use self::diagnostics_with_fix::DiagnosticWithFix; +use self::fixes::DiagnosticWithFix; + +#[derive(Debug)] +pub struct Diagnostic { + pub name: Option, + pub message: String, + pub range: TextRange, + pub severity: Severity, + pub fix: Option, +} + +#[derive(Debug)] +pub struct Fix { + pub label: String, + pub source_change: SourceChange, + /// Allows to trigger the fix only when the caret is in the range given + pub fix_trigger_range: TextRange, +} + +impl Fix { + fn new( + label: impl Into, + source_change: SourceChange, + fix_trigger_range: TextRange, + ) -> Self { + let label = label.into(); + assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); + Self { label, source_change, fix_trigger_range } + } +} #[derive(Debug, Copy, Clone)] pub enum Severity { diff --git a/crates/ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ide/src/diagnostics/diagnostics_with_fix.rs deleted file mode 100644 index 85b46c995..000000000 --- a/crates/ide/src/diagnostics/diagnostics_with_fix.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Provides a way to attach fixes to the diagnostics. -//! The same module also has all curret custom fixes for the diagnostics implemented. -use crate::Fix; -use ast::{edit::IndentLevel, make}; -use base_db::FileId; -use hir::{ - db::AstDatabase, - diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, - HasSource, HirDisplay, Semantics, VariantDef, -}; -use ide_db::{ - source_change::{FileSystemEdit, SourceFileEdit}, - RootDatabase, -}; -use syntax::{algo, ast, AstNode}; -use text_edit::TextEdit; - -/// A [Diagnostic] that potentially has a fix available. -/// -/// [Diagnostic]: hir::diagnostics::Diagnostic -pub trait DiagnosticWithFix: Diagnostic { - fn fix(&self, sema: &Semantics) -> Option; -} - -impl DiagnosticWithFix for UnresolvedModule { - fn fix(&self, sema: &Semantics) -> Option { - let root = sema.db.parse_or_expand(self.file)?; - let unresolved_module = self.decl.to_node(&root); - Some(Fix::new( - "Create module", - FileSystemEdit::CreateFile { - anchor: self.file.original_file(sema.db), - dst: self.candidate.clone(), - } - .into(), - unresolved_module.syntax().text_range(), - )) - } -} - -impl DiagnosticWithFix for NoSuchField { - fn fix(&self, sema: &Semantics) -> Option { - let root = sema.db.parse_or_expand(self.file)?; - missing_record_expr_field_fix( - &sema, - self.file.original_file(sema.db), - &self.field.to_node(&root), - ) - } -} - -impl DiagnosticWithFix for MissingFields { - fn fix(&self, sema: &Semantics) -> Option { - // Note that although we could add a diagnostics to - // fill the missing tuple field, e.g : - // `struct A(usize);` - // `let a = A { 0: () }` - // but it is uncommon usage and it should not be encouraged. - if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { - return None; - } - - let root = sema.db.parse_or_expand(self.file)?; - let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; - let mut new_field_list = old_field_list.clone(); - for f in self.missed_fields.iter() { - let field = - make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); - new_field_list = new_field_list.append_field(&field); - } - - let edit = { - let mut builder = TextEdit::builder(); - algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) - .into_text_edit(&mut builder); - builder.finish() - }; - Some(Fix::new( - "Fill struct fields", - SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), - sema.original_range(&old_field_list.syntax()).range, - )) - } -} - -impl DiagnosticWithFix for MissingOkInTailExpr { - fn fix(&self, sema: &Semantics) -> Option { - let root = sema.db.parse_or_expand(self.file)?; - let tail_expr = self.expr.to_node(&root); - let tail_expr_range = tail_expr.syntax().text_range(); - let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); - let source_change = - SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); - Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) - } -} - -fn missing_record_expr_field_fix( - sema: &Semantics, - usage_file_id: FileId, - record_expr_field: &ast::RecordExprField, -) -> Option { - let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; - let def_id = sema.resolve_variant(record_lit)?; - let module; - let def_file_id; - let record_fields = match VariantDef::from(def_id) { - VariantDef::Struct(s) => { - module = s.module(sema.db); - let source = s.source(sema.db); - def_file_id = source.file_id; - let fields = source.value.field_list()?; - record_field_list(fields)? - } - VariantDef::Union(u) => { - module = u.module(sema.db); - let source = u.source(sema.db); - def_file_id = source.file_id; - source.value.record_field_list()? - } - VariantDef::EnumVariant(e) => { - module = e.module(sema.db); - let source = e.source(sema.db); - def_file_id = source.file_id; - let fields = source.value.field_list()?; - record_field_list(fields)? - } - }; - let def_file_id = def_file_id.original_file(sema.db); - - let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; - if new_field_type.is_unknown() { - return None; - } - let new_field = make::record_field( - record_expr_field.field_name()?, - make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), - ); - - let last_field = record_fields.fields().last()?; - let last_field_syntax = last_field.syntax(); - let indent = IndentLevel::from_node(last_field_syntax); - - let mut new_field = new_field.to_string(); - if usage_file_id != def_file_id { - new_field = format!("pub(crate) {}", new_field); - } - new_field = format!("\n{}{}", indent, new_field); - - let needs_comma = !last_field_syntax.to_string().ends_with(','); - if needs_comma { - new_field = format!(",{}", new_field); - } - - let source_change = SourceFileEdit { - file_id: def_file_id, - edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), - }; - return Some(Fix::new( - "Create field", - source_change.into(), - record_expr_field.syntax().text_range(), - )); - - fn record_field_list(field_def_list: ast::FieldList) -> Option { - match field_def_list { - ast::FieldList::RecordFieldList(it) => Some(it), - ast::FieldList::TupleFieldList(_) => None, - } - } -} diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs new file mode 100644 index 000000000..68ae1c239 --- /dev/null +++ b/crates/ide/src/diagnostics/fixes.rs @@ -0,0 +1,175 @@ +//! Provides a way to attach fixes to the diagnostics. +//! The same module also has all curret custom fixes for the diagnostics implemented. +use base_db::FileId; +use hir::{ + db::AstDatabase, + diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, + HasSource, HirDisplay, Semantics, VariantDef, +}; +use ide_db::{ + source_change::{FileSystemEdit, SourceFileEdit}, + RootDatabase, +}; +use syntax::{ + algo, + ast::{self, edit::IndentLevel, make}, + AstNode, +}; +use text_edit::TextEdit; + +use crate::diagnostics::Fix; + +/// A [Diagnostic] that potentially has a fix available. +/// +/// [Diagnostic]: hir::diagnostics::Diagnostic +pub trait DiagnosticWithFix: Diagnostic { + fn fix(&self, sema: &Semantics) -> Option; +} + +impl DiagnosticWithFix for UnresolvedModule { + fn fix(&self, sema: &Semantics) -> Option { + let root = sema.db.parse_or_expand(self.file)?; + let unresolved_module = self.decl.to_node(&root); + Some(Fix::new( + "Create module", + FileSystemEdit::CreateFile { + anchor: self.file.original_file(sema.db), + dst: self.candidate.clone(), + } + .into(), + unresolved_module.syntax().text_range(), + )) + } +} + +impl DiagnosticWithFix for NoSuchField { + fn fix(&self, sema: &Semantics) -> Option { + let root = sema.db.parse_or_expand(self.file)?; + missing_record_expr_field_fix( + &sema, + self.file.original_file(sema.db), + &self.field.to_node(&root), + ) + } +} + +impl DiagnosticWithFix for MissingFields { + fn fix(&self, sema: &Semantics) -> Option { + // Note that although we could add a diagnostics to + // fill the missing tuple field, e.g : + // `struct A(usize);` + // `let a = A { 0: () }` + // but it is uncommon usage and it should not be encouraged. + if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { + return None; + } + + let root = sema.db.parse_or_expand(self.file)?; + let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; + let mut new_field_list = old_field_list.clone(); + for f in self.missed_fields.iter() { + let field = + make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); + new_field_list = new_field_list.append_field(&field); + } + + let edit = { + let mut builder = TextEdit::builder(); + algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) + .into_text_edit(&mut builder); + builder.finish() + }; + Some(Fix::new( + "Fill struct fields", + SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), + sema.original_range(&old_field_list.syntax()).range, + )) + } +} + +impl DiagnosticWithFix for MissingOkInTailExpr { + fn fix(&self, sema: &Semantics) -> Option { + let root = sema.db.parse_or_expand(self.file)?; + let tail_expr = self.expr.to_node(&root); + let tail_expr_range = tail_expr.syntax().text_range(); + let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); + let source_change = + SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); + Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) + } +} + +fn missing_record_expr_field_fix( + sema: &Semantics, + usage_file_id: FileId, + record_expr_field: &ast::RecordExprField, +) -> Option { + let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; + let def_id = sema.resolve_variant(record_lit)?; + let module; + let def_file_id; + let record_fields = match VariantDef::from(def_id) { + VariantDef::Struct(s) => { + module = s.module(sema.db); + let source = s.source(sema.db); + def_file_id = source.file_id; + let fields = source.value.field_list()?; + record_field_list(fields)? + } + VariantDef::Union(u) => { + module = u.module(sema.db); + let source = u.source(sema.db); + def_file_id = source.file_id; + source.value.record_field_list()? + } + VariantDef::EnumVariant(e) => { + module = e.module(sema.db); + let source = e.source(sema.db); + def_file_id = source.file_id; + let fields = source.value.field_list()?; + record_field_list(fields)? + } + }; + let def_file_id = def_file_id.original_file(sema.db); + + let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; + if new_field_type.is_unknown() { + return None; + } + let new_field = make::record_field( + record_expr_field.field_name()?, + make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), + ); + + let last_field = record_fields.fields().last()?; + let last_field_syntax = last_field.syntax(); + let indent = IndentLevel::from_node(last_field_syntax); + + let mut new_field = new_field.to_string(); + if usage_file_id != def_file_id { + new_field = format!("pub(crate) {}", new_field); + } + new_field = format!("\n{}{}", indent, new_field); + + let needs_comma = !last_field_syntax.to_string().ends_with(','); + if needs_comma { + new_field = format!(",{}", new_field); + } + + let source_change = SourceFileEdit { + file_id: def_file_id, + edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), + }; + return Some(Fix::new( + "Create field", + source_change.into(), + record_expr_field.syntax().text_range(), + )); + + fn record_field_list(field_def_list: ast::FieldList) -> Option { + match field_def_list { + ast::FieldList::RecordFieldList(it) => Some(it), + ast::FieldList::TupleFieldList(_) => None, + } + } +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 2a73abba2..f37119e28 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -65,7 +65,7 @@ pub use crate::{ completion::{ CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, }, - diagnostics::{DiagnosticsConfig, Severity}, + diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, display::NavigationTarget, expand_macro::ExpandedMacro, file_structure::StructureNode, @@ -99,35 +99,6 @@ pub use text_edit::{Indel, TextEdit}; pub type Cancelable = Result; -#[derive(Debug)] -pub struct Diagnostic { - pub name: Option, - pub message: String, - pub range: TextRange, - pub severity: Severity, - pub fix: Option, -} - -#[derive(Debug)] -pub struct Fix { - pub label: String, - pub source_change: SourceChange, - /// Allows to trigger the fix only when the caret is in the range given - pub fix_trigger_range: TextRange, -} - -impl Fix { - pub fn new( - label: impl Into, - source_change: SourceChange, - fix_trigger_range: TextRange, - ) -> Self { - let label = label.into(); - assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); - Self { label, source_change, fix_trigger_range } - } -} - /// Info associated with a text range. #[derive(Debug)] pub struct RangeInfo { -- cgit v1.2.3