microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
main

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

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)]
8mod tests;
9
10use qsc::{Span, compile::ErrorKind, line_column::Encoding, resolve::NameKind};
11use rustc_hash::FxHashSet;
12
13use super::is_error_relevant;
14use 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.
26pub(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.
86fn 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