Add a way to resolve certain assists

This commit is contained in:
Kirill Bulatov 2021-05-03 17:08:09 +03:00
parent eb741e895f
commit e5cdcb8b12
10 changed files with 178 additions and 92 deletions

View file

@ -15,6 +15,7 @@ use hir::{
diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
InFile, Semantics, InFile, Semantics,
}; };
use ide_assists::AssistResolveStrategy;
use ide_db::{base_db::SourceDatabase, RootDatabase}; use ide_db::{base_db::SourceDatabase, RootDatabase};
use itertools::Itertools; use itertools::Itertools;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@ -84,7 +85,7 @@ pub struct DiagnosticsConfig {
pub(crate) fn diagnostics( pub(crate) fn diagnostics(
db: &RootDatabase, db: &RootDatabase,
config: &DiagnosticsConfig, config: &DiagnosticsConfig,
resolve: bool, resolve: AssistResolveStrategy,
file_id: FileId, file_id: FileId,
) -> Vec<Diagnostic> { ) -> Vec<Diagnostic> {
let _p = profile::span("diagnostics"); let _p = profile::span("diagnostics");
@ -212,7 +213,7 @@ pub(crate) fn diagnostics(
fn diagnostic_with_fix<D: DiagnosticWithFix>( fn diagnostic_with_fix<D: DiagnosticWithFix>(
d: &D, d: &D,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
resolve: bool, resolve: AssistResolveStrategy,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
.with_fix(d.fix(&sema, resolve)) .with_fix(d.fix(&sema, resolve))
@ -222,7 +223,7 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>(
fn warning_with_fix<D: DiagnosticWithFix>( fn warning_with_fix<D: DiagnosticWithFix>(
d: &D, d: &D,
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
resolve: bool, resolve: AssistResolveStrategy,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
.with_fix(d.fix(&sema, resolve)) .with_fix(d.fix(&sema, resolve))
@ -299,6 +300,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use ide_assists::AssistResolveStrategy;
use stdx::trim_indent; use stdx::trim_indent;
use test_utils::assert_eq_text; use test_utils::assert_eq_text;
@ -314,7 +316,11 @@ mod tests {
let (analysis, file_position) = fixture::position(ra_fixture_before); let (analysis, file_position) = fixture::position(ra_fixture_before);
let diagnostic = analysis let diagnostic = analysis
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) .diagnostics(
&DiagnosticsConfig::default(),
AssistResolveStrategy::All,
file_position.file_id,
)
.unwrap() .unwrap()
.pop() .pop()
.unwrap(); .unwrap();
@ -343,7 +349,11 @@ mod tests {
fn check_no_fix(ra_fixture: &str) { fn check_no_fix(ra_fixture: &str) {
let (analysis, file_position) = fixture::position(ra_fixture); let (analysis, file_position) = fixture::position(ra_fixture);
let diagnostic = analysis let diagnostic = analysis
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) .diagnostics(
&DiagnosticsConfig::default(),
AssistResolveStrategy::All,
file_position.file_id,
)
.unwrap() .unwrap()
.pop() .pop()
.unwrap(); .unwrap();
@ -357,7 +367,9 @@ mod tests {
let diagnostics = files let diagnostics = files
.into_iter() .into_iter()
.flat_map(|file_id| { .flat_map(|file_id| {
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() analysis
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
.unwrap()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
@ -365,8 +377,9 @@ mod tests {
fn check_expect(ra_fixture: &str, expect: Expect) { fn check_expect(ra_fixture: &str, expect: Expect) {
let (analysis, file_id) = fixture::file(ra_fixture); let (analysis, file_id) = fixture::file(ra_fixture);
let diagnostics = let diagnostics = analysis
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
.unwrap();
expect.assert_debug_eq(&diagnostics) expect.assert_debug_eq(&diagnostics)
} }
@ -911,11 +924,13 @@ struct Foo {
let (analysis, file_id) = fixture::file(r#"mod foo;"#); let (analysis, file_id) = fixture::file(r#"mod foo;"#);
let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); let diagnostics =
analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap();
assert!(diagnostics.is_empty()); assert!(diagnostics.is_empty());
let diagnostics = let diagnostics = analysis
analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
.unwrap();
assert!(!diagnostics.is_empty()); assert!(!diagnostics.is_empty());
} }
@ -1022,7 +1037,11 @@ impl TestStruct {
let (analysis, file_position) = fixture::position(input); let (analysis, file_position) = fixture::position(input);
let diagnostics = analysis let diagnostics = analysis
.diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) .diagnostics(
&DiagnosticsConfig::default(),
AssistResolveStrategy::All,
file_position.file_id,
)
.unwrap(); .unwrap();
assert_eq!(diagnostics.len(), 1); assert_eq!(diagnostics.len(), 1);

View file

@ -8,6 +8,7 @@ use hir::{
}, },
HasSource, HirDisplay, InFile, Semantics, VariantDef, HasSource, HirDisplay, InFile, Semantics, VariantDef,
}; };
use ide_assists::AssistResolveStrategy;
use ide_db::{ use ide_db::{
base_db::{AnchoredPathBuf, FileId}, base_db::{AnchoredPathBuf, FileId},
source_change::{FileSystemEdit, SourceChange}, source_change::{FileSystemEdit, SourceChange},
@ -35,11 +36,19 @@ pub(crate) trait DiagnosticWithFix: Diagnostic {
/// ///
/// If `resolve` is false, the edit will be computed later, on demand, and /// If `resolve` is false, the edit will be computed later, on demand, and
/// can be omitted. /// can be omitted.
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist>;
} }
impl DiagnosticWithFix for UnresolvedModule { impl DiagnosticWithFix for UnresolvedModule {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
let unresolved_module = self.decl.to_node(&root); let unresolved_module = self.decl.to_node(&root);
Some(fix( Some(fix(
@ -59,7 +68,11 @@ impl DiagnosticWithFix for UnresolvedModule {
} }
impl DiagnosticWithFix for NoSuchField { impl DiagnosticWithFix for NoSuchField {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
missing_record_expr_field_fix( missing_record_expr_field_fix(
&sema, &sema,
@ -70,7 +83,11 @@ impl DiagnosticWithFix for NoSuchField {
} }
impl DiagnosticWithFix for MissingFields { impl DiagnosticWithFix for MissingFields {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
// Note that although we could add a diagnostics to // Note that although we could add a diagnostics to
// fill the missing tuple field, e.g : // fill the missing tuple field, e.g :
// `struct A(usize);` // `struct A(usize);`
@ -106,7 +123,11 @@ impl DiagnosticWithFix for MissingFields {
} }
impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
let tail_expr = self.expr.to_node(&root); let tail_expr = self.expr.to_node(&root);
let tail_expr_range = tail_expr.syntax().text_range(); let tail_expr_range = tail_expr.syntax().text_range();
@ -119,7 +140,11 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
} }
impl DiagnosticWithFix for RemoveThisSemicolon { impl DiagnosticWithFix for RemoveThisSemicolon {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
let semicolon = self let semicolon = self
@ -139,7 +164,11 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
} }
impl DiagnosticWithFix for IncorrectCase { impl DiagnosticWithFix for IncorrectCase {
fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
let name_node = self.ident.to_node(&root); let name_node = self.ident.to_node(&root);
@ -149,7 +178,7 @@ impl DiagnosticWithFix for IncorrectCase {
let label = format!("Rename to {}", self.suggested_text); let label = format!("Rename to {}", self.suggested_text);
let mut res = unresolved_fix("change_case", &label, frange.range); let mut res = unresolved_fix("change_case", &label, frange.range);
if resolve { if resolve.should_resolve(&res.id) {
let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
res.source_change = Some(source_change.ok().unwrap_or_default()); res.source_change = Some(source_change.ok().unwrap_or_default());
} }
@ -159,7 +188,11 @@ impl DiagnosticWithFix for IncorrectCase {
} }
impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
let root = sema.db.parse_or_expand(self.file)?; let root = sema.db.parse_or_expand(self.file)?;
let next_expr = self.next_expr.to_node(&root); let next_expr = self.next_expr.to_node(&root);
let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;

View file

@ -5,6 +5,7 @@ use hir::{
diagnostics::{Diagnostic, DiagnosticCode}, diagnostics::{Diagnostic, DiagnosticCode},
InFile, InFile,
}; };
use ide_assists::AssistResolveStrategy;
use ide_db::{ use ide_db::{
base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
source_change::SourceChange, source_change::SourceChange,
@ -50,7 +51,11 @@ impl Diagnostic for UnlinkedFile {
} }
impl DiagnosticWithFix for UnlinkedFile { impl DiagnosticWithFix for UnlinkedFile {
fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { fn fix(
&self,
sema: &hir::Semantics<RootDatabase>,
_resolve: AssistResolveStrategy,
) -> Option<Assist> {
// If there's an existing module that could add a `mod` item to include the unlinked file, // If there's an existing module that could add a `mod` item to include the unlinked file,
// suggest that as a fix. // suggest that as a fix.

View file

@ -87,7 +87,7 @@ pub use crate::{
}, },
}; };
pub use hir::{Documentation, Semantics}; pub use hir::{Documentation, Semantics};
pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy};
pub use ide_completion::{ pub use ide_completion::{
CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit,
InsertTextFormat, InsertTextFormat,
@ -518,12 +518,13 @@ impl Analysis {
pub fn assists( pub fn assists(
&self, &self,
config: &AssistConfig, config: &AssistConfig,
resolve: bool, resolve: AssistResolveStrategy,
frange: FileRange, frange: FileRange,
) -> Cancelable<Vec<Assist>> { ) -> Cancelable<Vec<Assist>> {
self.with_db(|db| { self.with_db(|db| {
let ssr_assists = ssr::ssr_assists(db, resolve, frange);
let mut acc = Assist::get(db, config, resolve, frange); let mut acc = Assist::get(db, config, resolve, frange);
ssr::add_ssr_assist(db, &mut acc, resolve, frange); acc.extend(ssr_assists.into_iter());
acc acc
}) })
} }
@ -532,7 +533,7 @@ impl Analysis {
pub fn diagnostics( pub fn diagnostics(
&self, &self,
config: &DiagnosticsConfig, config: &DiagnosticsConfig,
resolve: bool, resolve: AssistResolveStrategy,
file_id: FileId, file_id: FileId,
) -> Cancelable<Vec<Diagnostic>> { ) -> Cancelable<Vec<Diagnostic>> {
self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id))
@ -543,7 +544,7 @@ impl Analysis {
&self, &self,
assist_config: &AssistConfig, assist_config: &AssistConfig,
diagnostics_config: &DiagnosticsConfig, diagnostics_config: &DiagnosticsConfig,
resolve: bool, resolve: AssistResolveStrategy,
frange: FileRange, frange: FileRange,
) -> Cancelable<Vec<Assist>> { ) -> Cancelable<Vec<Assist>> {
let include_fixes = match &assist_config.allowed { let include_fixes = match &assist_config.allowed {
@ -552,17 +553,21 @@ impl Analysis {
}; };
self.with_db(|db| { self.with_db(|db| {
let mut res = Assist::get(db, assist_config, resolve, frange); let ssr_assists = ssr::ssr_assists(db, resolve, frange);
ssr::add_ssr_assist(db, &mut res, resolve, frange); let diagnostic_assists = if include_fixes {
diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id)
.into_iter()
.filter_map(|it| it.fix)
.filter(|it| it.target.intersect(frange.range).is_some())
.collect()
} else {
Vec::new()
};
let mut res = Assist::get(db, assist_config, resolve, frange);
res.extend(ssr_assists.into_iter());
res.extend(diagnostic_assists.into_iter());
if include_fixes {
res.extend(
diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id)
.into_iter()
.filter_map(|it| it.fix)
.filter(|it| it.target.intersect(frange.range).is_some()),
);
}
res res
}) })
} }

View file

@ -2,18 +2,23 @@
//! assist in ide_assists because that would require the ide_assists crate //! assist in ide_assists because that would require the ide_assists crate
//! depend on the ide_ssr crate. //! depend on the ide_ssr crate.
use ide_assists::{Assist, AssistId, AssistKind, GroupLabel}; use ide_assists::{Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel};
use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
pub(crate) fn add_ssr_assist( pub(crate) fn ssr_assists(
db: &RootDatabase, db: &RootDatabase,
base: &mut Vec<Assist>, resolve: AssistResolveStrategy,
resolve: bool,
frange: FileRange, frange: FileRange,
) -> Option<()> { ) -> Vec<Assist> {
let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?; let mut ssr_assists = Vec::with_capacity(2);
let (source_change_for_file, source_change_for_workspace) = if resolve { let (match_finder, comment_range) = match ide_ssr::ssr_from_comment(db, frange) {
Some((match_finder, comment_range)) => (match_finder, comment_range),
None => return ssr_assists,
};
let id = AssistId("ssr", AssistKind::RefactorRewrite);
let (source_change_for_file, source_change_for_workspace) = if resolve.should_resolve(&id) {
let edits = match_finder.edits(); let edits = match_finder.edits();
let source_change_for_file = { let source_change_for_file = {
@ -35,16 +40,17 @@ pub(crate) fn add_ssr_assist(
for (label, source_change) in assists.into_iter() { for (label, source_change) in assists.into_iter() {
let assist = Assist { let assist = Assist {
id: AssistId("ssr", AssistKind::RefactorRewrite), id,
label: Label::new(label), label: Label::new(label),
group: Some(GroupLabel("Apply SSR".into())), group: Some(GroupLabel("Apply SSR".into())),
target: comment_range, target: comment_range,
source_change, source_change,
}; };
base.push(assist); ssr_assists.push(assist);
} }
Some(())
ssr_assists
} }
#[cfg(test)] #[cfg(test)]
@ -52,7 +58,7 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use expect_test::expect; use expect_test::expect;
use ide_assists::Assist; use ide_assists::{Assist, AssistResolveStrategy};
use ide_db::{ use ide_db::{
base_db::{fixture::WithFixture, salsa::Durability, FileRange}, base_db::{fixture::WithFixture, salsa::Durability, FileRange},
symbol_index::SymbolsDatabase, symbol_index::SymbolsDatabase,
@ -60,24 +66,14 @@ mod tests {
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use super::add_ssr_assist; use super::ssr_assists;
fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> { fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec<Assist> {
let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
let mut local_roots = FxHashSet::default(); let mut local_roots = FxHashSet::default();
local_roots.insert(ide_db::base_db::fixture::WORKSPACE); local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
ssr_assists(&db, resolve, FileRange { file_id, range: range_or_offset.into() })
let mut assists = vec![];
add_ssr_assist(
&db,
&mut assists,
resolve,
FileRange { file_id, range: range_or_offset.into() },
);
assists
} }
#[test] #[test]
@ -88,16 +84,14 @@ mod tests {
// This is foo $0 // This is foo $0
fn foo() {} fn foo() {}
"#; "#;
let resolve = true; let assists = get_assists(ra_fixture, AssistResolveStrategy::All);
let assists = get_assists(ra_fixture, resolve);
assert_eq!(0, assists.len()); assert_eq!(0, assists.len());
} }
// TODO kb add partial resolve test
#[test] #[test]
fn resolve_edits_true() { fn resolve_edits_true() {
let resolve = true;
let assists = get_assists( let assists = get_assists(
r#" r#"
//- /lib.rs //- /lib.rs
@ -109,7 +103,7 @@ mod tests {
//- /bar.rs //- /bar.rs
fn bar() { 2 } fn bar() { 2 }
"#, "#,
resolve, AssistResolveStrategy::All,
); );
assert_eq!(2, assists.len()); assert_eq!(2, assists.len());
@ -200,7 +194,6 @@ mod tests {
#[test] #[test]
fn resolve_edits_false() { fn resolve_edits_false() {
let resolve = false;
let assists = get_assists( let assists = get_assists(
r#" r#"
//- /lib.rs //- /lib.rs
@ -212,7 +205,7 @@ mod tests {
//- /bar.rs //- /bar.rs
fn bar() { 2 } fn bar() { 2 }
"#, "#,
resolve, AssistResolveStrategy::None,
); );
assert_eq!(2, assists.len()); assert_eq!(2, assists.len());

View file

@ -19,7 +19,9 @@ use syntax::{
}; };
use text_edit::{TextEdit, TextEditBuilder}; use text_edit::{TextEdit, TextEditBuilder};
use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel}; use crate::{
assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel,
};
/// `AssistContext` allows to apply an assist or check if it could be applied. /// `AssistContext` allows to apply an assist or check if it could be applied.
/// ///
@ -105,14 +107,14 @@ impl<'a> AssistContext<'a> {
} }
pub(crate) struct Assists { pub(crate) struct Assists {
resolve: bool,
file: FileId, file: FileId,
resolve: AssistResolveStrategy,
buf: Vec<Assist>, buf: Vec<Assist>,
allowed: Option<Vec<AssistKind>>, allowed: Option<Vec<AssistKind>>,
} }
impl Assists { impl Assists {
pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists { pub(crate) fn new(ctx: &AssistContext, resolve: AssistResolveStrategy) -> Assists {
Assists { Assists {
resolve, resolve,
file: ctx.frange.file_id, file: ctx.frange.file_id,
@ -158,7 +160,7 @@ impl Assists {
} }
fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
let source_change = if self.resolve { let source_change = if self.resolve.should_resolve(&assist.id) {
let mut builder = AssistBuilder::new(self.file); let mut builder = AssistBuilder::new(self.file);
f(&mut builder); f(&mut builder);
Some(builder.finish()) Some(builder.finish())

View file

@ -26,7 +26,7 @@ pub(crate) use crate::assist_context::{AssistContext, Assists};
pub use assist_config::AssistConfig; pub use assist_config::AssistConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssistKind { pub enum AssistKind {
// FIXME: does the None variant make sense? Probably not. // FIXME: does the None variant make sense? Probably not.
None, None,
@ -60,9 +60,27 @@ impl AssistKind {
/// Unique identifier of the assist, should not be shown to the user /// Unique identifier of the assist, should not be shown to the user
/// directly. /// directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AssistId(pub &'static str, pub AssistKind); pub struct AssistId(pub &'static str, pub AssistKind);
// TODO kb docs
#[derive(Debug, Clone, Copy)]
pub enum AssistResolveStrategy {
None,
All,
Single(AssistId),
}
impl AssistResolveStrategy {
pub fn should_resolve(&self, id: &AssistId) -> bool {
match self {
AssistResolveStrategy::None => false,
AssistResolveStrategy::All => true,
AssistResolveStrategy::Single(id_to_resolve) => id_to_resolve == id,
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GroupLabel(pub String); pub struct GroupLabel(pub String);
@ -91,7 +109,7 @@ impl Assist {
pub fn get( pub fn get(
db: &RootDatabase, db: &RootDatabase,
config: &AssistConfig, config: &AssistConfig,
resolve: bool, resolve: AssistResolveStrategy,
range: FileRange, range: FileRange,
) -> Vec<Assist> { ) -> Vec<Assist> {
let sema = Semantics::new(db); let sema = Semantics::new(db);

View file

@ -12,7 +12,10 @@ use stdx::{format_to, trim_indent};
use syntax::TextRange; use syntax::TextRange;
use test_utils::{assert_eq_text, extract_offset}; use test_utils::{assert_eq_text, extract_offset};
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; use crate::{
handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, AssistResolveStrategy,
Assists,
};
pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
snippet_cap: SnippetCap::new(true), snippet_cap: SnippetCap::new(true),
@ -65,14 +68,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
let before = db.file_text(file_id).to_string(); let before = db.file_text(file_id).to_string();
let frange = FileRange { file_id, range: selection.into() }; let frange = FileRange { file_id, range: selection.into() };
let assist = Assist::get(&db, &TEST_CONFIG, true, frange) let assist = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::All, frange)
.into_iter() .into_iter()
.find(|assist| assist.id.0 == assist_id) .find(|assist| assist.id.0 == assist_id)
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"\n\nAssist is not applicable: {}\nAvailable assists: {}", "\n\nAssist is not applicable: {}\nAvailable assists: {}",
assist_id, assist_id,
Assist::get(&db, &TEST_CONFIG, false, frange) Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange)
.into_iter() .into_iter()
.map(|assist| assist.id.0) .map(|assist| assist.id.0)
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -108,7 +111,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label:
let sema = Semantics::new(&db); let sema = Semantics::new(&db);
let config = TEST_CONFIG; let config = TEST_CONFIG;
let ctx = AssistContext::new(sema, &config, frange); let ctx = AssistContext::new(sema, &config, frange);
let mut acc = Assists::new(&ctx, true); let mut acc = Assists::new(&ctx, AssistResolveStrategy::All);
handler(&mut acc, &ctx); handler(&mut acc, &ctx);
let mut res = acc.finish(); let mut res = acc.finish();
@ -186,7 +189,7 @@ fn assist_order_field_struct() {
let (before_cursor_pos, before) = extract_offset(before); let (before_cursor_pos, before) = extract_offset(before);
let (db, file_id) = with_single_file(&before); let (db, file_id) = with_single_file(&before);
let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
let assists = Assist::get(&db, &TEST_CONFIG, false, frange); let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
let mut assists = assists.iter(); let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
@ -211,7 +214,7 @@ pub fn test_some_range(a: int) -> bool {
"#, "#,
); );
let assists = Assist::get(&db, &TEST_CONFIG, false, frange); let assists = Assist::get(&db, &TEST_CONFIG, AssistResolveStrategy::None, frange);
let expected = labels(&assists); let expected = labels(&assists);
expect![[r#" expect![[r#"
@ -240,7 +243,7 @@ pub fn test_some_range(a: int) -> bool {
let mut cfg = TEST_CONFIG; let mut cfg = TEST_CONFIG;
cfg.allowed = Some(vec![AssistKind::Refactor]); cfg.allowed = Some(vec![AssistKind::Refactor]);
let assists = Assist::get(&db, &cfg, false, frange); let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange);
let expected = labels(&assists); let expected = labels(&assists);
expect![[r#" expect![[r#"
@ -255,7 +258,7 @@ pub fn test_some_range(a: int) -> bool {
{ {
let mut cfg = TEST_CONFIG; let mut cfg = TEST_CONFIG;
cfg.allowed = Some(vec![AssistKind::RefactorExtract]); cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
let assists = Assist::get(&db, &cfg, false, frange); let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange);
let expected = labels(&assists); let expected = labels(&assists);
expect![[r#" expect![[r#"
@ -268,7 +271,7 @@ pub fn test_some_range(a: int) -> bool {
{ {
let mut cfg = TEST_CONFIG; let mut cfg = TEST_CONFIG;
cfg.allowed = Some(vec![AssistKind::QuickFix]); cfg.allowed = Some(vec![AssistKind::QuickFix]);
let assists = Assist::get(&db, &cfg, false, frange); let assists = Assist::get(&db, &cfg, AssistResolveStrategy::None, frange);
let expected = labels(&assists); let expected = labels(&assists);
expect![[r#""#]].assert_eq(&expected); expect![[r#""#]].assert_eq(&expected);

View file

@ -7,7 +7,7 @@ use anyhow::anyhow;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use hir::{db::HirDatabase, Crate, Module}; use hir::{db::HirDatabase, Crate, Module};
use ide::{DiagnosticsConfig, Severity}; use ide::{AssistResolveStrategy, DiagnosticsConfig, Severity};
use ide_db::base_db::SourceDatabaseExt; use ide_db::base_db::SourceDatabaseExt;
use crate::cli::{ use crate::cli::{
@ -57,8 +57,9 @@ pub fn diagnostics(
let crate_name = let crate_name =
module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string(); module.krate().display_name(db).as_deref().unwrap_or("unknown").to_string();
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
for diagnostic in for diagnostic in analysis
analysis.diagnostics(&DiagnosticsConfig::default(), false, file_id).unwrap() .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::None, file_id)
.unwrap()
{ {
if matches!(diagnostic.severity, Severity::Error) { if matches!(diagnostic.severity, Severity::Error) {
found_error = true; found_error = true;

View file

@ -8,8 +8,9 @@ use std::{
}; };
use ide::{ use ide::{
AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, Query, AnnotationConfig, AssistResolveStrategy, FileId, FilePosition, FileRange, HoverAction,
RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, HoverGotoTypeData, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange,
TextEdit,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use itertools::Itertools; use itertools::Itertools;
@ -1004,10 +1005,15 @@ pub(crate) fn handle_code_action(
let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
let code_action_resolve_cap = snap.config.code_action_resolve(); let code_action_resolve_cap = snap.config.code_action_resolve();
let resolve = if code_action_resolve_cap {
AssistResolveStrategy::None
} else {
AssistResolveStrategy::All
};
let assists = snap.analysis.assists_with_fixes( let assists = snap.analysis.assists_with_fixes(
&assists_config, &assists_config,
&snap.config.diagnostics(), &snap.config.diagnostics(),
!code_action_resolve_cap, resolve,
frange, frange,
)?; )?;
for (index, assist) in assists.into_iter().enumerate() { for (index, assist) in assists.into_iter().enumerate() {
@ -1055,7 +1061,8 @@ pub(crate) fn handle_code_action_resolve(
let assists = snap.analysis.assists_with_fixes( let assists = snap.analysis.assists_with_fixes(
&assists_config, &assists_config,
&snap.config.diagnostics(), &snap.config.diagnostics(),
true, // TODO kb pass a certain id
AssistResolveStrategy::All,
frange, frange,
)?; )?;
@ -1182,7 +1189,7 @@ pub(crate) fn publish_diagnostics(
let diagnostics: Vec<Diagnostic> = snap let diagnostics: Vec<Diagnostic> = snap
.analysis .analysis
.diagnostics(&snap.config.diagnostics(), false, file_id)? .diagnostics(&snap.config.diagnostics(), AssistResolveStrategy::None, file_id)?
.into_iter() .into_iter()
.map(|d| Diagnostic { .map(|d| Diagnostic {
range: to_proto::range(&line_index, d.range), range: to_proto::range(&line_index, d.range),