From 72ef51b7bc562f130f966afdcb68a824fed0ebcf Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Sat, 13 Dec 2025 16:43:22 -0500 Subject: [PATCH] add qualify code-action --- crates/ty_ide/src/code_action.rs | 25 +++++++++++++++++ crates/ty_ide/src/completion.rs | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/crates/ty_ide/src/code_action.rs b/crates/ty_ide/src/code_action.rs index 2b6703afac..ee21d81f2c 100644 --- a/crates/ty_ide/src/code_action.rs +++ b/crates/ty_ide/src/code_action.rs @@ -38,6 +38,11 @@ pub fn code_actions( { actions.extend(import_quick_fix); } + if is_unresolved_reference + && let Some(import_quick_fix) = create_qualify_symbol_quick_fix(db, file, diagnostic_range) + { + actions.extend(import_quick_fix); + } // Suggest just suppressing the lint (always a valid option, but never ideal) actions.push(QuickFix { @@ -69,6 +74,26 @@ fn create_import_symbol_quick_fix( ) } +fn create_qualify_symbol_quick_fix( + db: &dyn Db, + file: File, + diagnostic_range: TextRange, +) -> Option> { + let parsed = parsed_module(db, file).load(db); + let node = covering_node(parsed.syntax().into(), diagnostic_range).node(); + let symbol = &node.expr_name()?.id; + + Some( + completion::missing_qualifications(db, file, &parsed, symbol, node) + .into_iter() + .map(|import| QuickFix { + title: import.label, + edits: vec![import.edit], + preferred: true, + }), + ) +} + #[cfg(test)] mod tests { diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs index ca2305df0c..e06e76faad 100644 --- a/crates/ty_ide/src/completion.rs +++ b/crates/ty_ide/src/completion.rs @@ -82,6 +82,29 @@ impl<'db> Completions<'db> { .collect() } + fn into_qualified(mut self, range: TextRange) -> Vec { + self.items.sort_by(compare_suggestions); + self.items + .dedup_by(|c1, c2| (&c1.name, c1.module_name) == (&c2.name, c2.module_name)); + self.items + .into_iter() + .filter_map(|item| { + if item.import.is_none() { + Some(ImportEdit { + label: format!("qualify {}", item.insert.as_ref()?), + edit: Edit::replacement( + item.insert?.into_string(), + range.start(), + range.end(), + ), + }) + } else { + None + } + }) + .collect() + } + /// Attempts to adds the given completion to this collection. /// /// When added, `true` is returned. @@ -578,6 +601,29 @@ pub(crate) fn missing_imports( completions.into_imports() } +pub(crate) fn missing_qualifications( + db: &dyn Db, + file: File, + parsed: &ParsedModuleRef, + symbol: &str, + node: AnyNodeRef, +) -> Vec { + let mut completions = Completions::exactly(db, symbol); + let scoped = ScopedTarget { node }; + add_unimported_completions( + db, + file, + parsed, + scoped, + |module_name: &ModuleName, symbol: &str| { + ImportRequest::import(module_name.as_str(), symbol).force() + }, + &mut completions, + ); + + completions.into_qualified(node.range()) +} + /// Adds completions derived from keywords. /// /// This should generally only be used when offering "scoped" completions.