microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/language_service/src/code_action/auto_import.rs
105lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | // Auto-import code action logic: for unresolved names, offers quick fixes that insert an |
| 5 | // `import {namespace}.{name};` statement at the start of the enclosing namespace. |
| 6 | |
| 7 | #[cfg(test)] |
| 8 | mod tests; |
| 9 | |
| 10 | use qsc::{Span, compile::ErrorKind, line_column::Encoding, resolve::NameKind}; |
| 11 | use rustc_hash::FxHashSet; |
| 12 | |
| 13 | use super::is_error_relevant; |
| 14 | use crate::{ |
| 15 | compilation::Compilation, |
| 16 | completion::text_edits::TextEditRange, |
| 17 | protocol::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit}, |
| 18 | }; |
| 19 | |
| 20 | /// Produces auto-import quick fixes for unresolved names within `span`. |
| 21 | /// |
| 22 | /// For each unresolved-name diagnostic that overlaps the requested range, the unresolved |
| 23 | /// (unqualified) name is looked up in the global term and type tables. Every namespace that |
| 24 | /// exports a matching name yields a separate `QuickFix` code action that inserts an |
| 25 | /// `import {namespace}.{name};` statement at the start of the enclosing namespace. |
| 26 | pub(super) fn auto_import_fixes( |
| 27 | compilation: &Compilation, |
| 28 | source_name: &str, |
| 29 | span: Span, |
| 30 | encoding: Encoding, |
| 31 | ) -> Vec<CodeAction> { |
| 32 | let mut code_actions = Vec::new(); |
| 33 | // Dedupe by title, since the same name may be unresolved at multiple offsets in range. |
| 34 | let mut seen = FxHashSet::default(); |
| 35 | |
| 36 | let unresolved_names = compilation |
| 37 | .compile_errors |
| 38 | .iter() |
| 39 | .filter(|error| is_error_relevant(error, span)) |
| 40 | .filter_map(|error| match error.error() { |
| 41 | ErrorKind::Frontend(frontend_error) => frontend_error.unresolved_name(), |
| 42 | _ => None, |
| 43 | }); |
| 44 | |
| 45 | for (name, name_span) in unresolved_names { |
| 46 | // v1 only handles unqualified names; partial paths are deferred. |
| 47 | if name.is_empty() || name.contains('.') { |
| 48 | continue; |
| 49 | } |
| 50 | |
| 51 | // Determine where an import would be inserted for the enclosing namespace. |
| 52 | let edit_range = TextEditRange::init(name_span.lo, compilation, encoding); |
| 53 | let Some(insert_at) = edit_range.insert_import_at else { |
| 54 | continue; |
| 55 | }; |
| 56 | |
| 57 | for namespace_name in matching_namespaces(compilation, name) { |
| 58 | let title = format!("Import {namespace_name}.{name}"); |
| 59 | if !seen.insert(title.clone()) { |
| 60 | continue; |
| 61 | } |
| 62 | |
| 63 | let new_text = format!("import {namespace_name}.{name};{}", edit_range.indent); |
| 64 | code_actions.push(CodeAction { |
| 65 | title, |
| 66 | edit: Some(WorkspaceEdit { |
| 67 | changes: vec![( |
| 68 | source_name.to_string(), |
| 69 | vec![TextEdit { |
| 70 | new_text, |
| 71 | range: insert_at, |
| 72 | }], |
| 73 | )], |
| 74 | }), |
| 75 | kind: Some(CodeActionKind::QuickFix), |
| 76 | is_preferred: None, |
| 77 | }); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | code_actions |
| 82 | } |
| 83 | |
| 84 | /// Returns the fully-qualified names of all namespaces that export an item named `name` |
| 85 | /// in either an expression (term) or type context. Results are sorted for determinism. |
| 86 | fn matching_namespaces(compilation: &Compilation, name: &str) -> Vec<String> { |
| 87 | let global_scope = &compilation.user_unit().ast.globals; |
| 88 | let mut namespaces = FxHashSet::default(); |
| 89 | |
| 90 | for name_kind in [NameKind::Term, NameKind::Ty] { |
| 91 | for (namespace_id, names) in global_scope.table(name_kind).iter() { |
| 92 | if names.contains_key(name) { |
| 93 | let namespace_name = global_scope.format_namespace_name(namespace_id); |
| 94 | // Don't suggest auto-imports for OpenQASM namespaces (mirrors completions). |
| 95 | if !namespace_name.starts_with("Std.OpenQASM") { |
| 96 | namespaces.insert(namespace_name); |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | let mut namespaces: Vec<String> = namespaces.into_iter().collect(); |
| 103 | namespaces.sort(); |
| 104 | namespaces |
| 105 | } |
| 106 | |