microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/language_service/src/code_action.rs
97lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use miette::Diagnostic; |
| 5 | use qsc::{ |
| 6 | Span, |
| 7 | compile::ErrorKind, |
| 8 | error::WithSource, |
| 9 | line_column::{Encoding, Range}, |
| 10 | }; |
| 11 | |
| 12 | use crate::{ |
| 13 | compilation::Compilation, |
| 14 | protocol::{CodeAction, CodeActionKind, TextEdit, WorkspaceEdit}, |
| 15 | }; |
| 16 | |
| 17 | pub(crate) fn get_code_actions( |
| 18 | compilation: &Compilation, |
| 19 | source_name: &str, |
| 20 | range: Range, |
| 21 | position_encoding: Encoding, |
| 22 | ) -> Vec<CodeAction> { |
| 23 | // Compute quick_fixes and other code_actions, and then merge them together |
| 24 | let span = compilation.source_range_to_package_span(source_name, range, position_encoding); |
| 25 | quick_fixes(compilation, source_name, span, position_encoding) |
| 26 | } |
| 27 | |
| 28 | fn quick_fixes( |
| 29 | compilation: &Compilation, |
| 30 | source_name: &str, |
| 31 | span: Span, |
| 32 | encoding: Encoding, |
| 33 | ) -> Vec<CodeAction> { |
| 34 | let mut code_actions = Vec::new(); |
| 35 | |
| 36 | // get relevant diagnostics |
| 37 | let diagnostics = compilation |
| 38 | .compile_errors |
| 39 | .iter() |
| 40 | .filter(|error| is_error_relevant(error, span)); |
| 41 | |
| 42 | // For all diagnostics that are lints, we extract the code action edits from them. |
| 43 | for diagnostic in diagnostics { |
| 44 | if let ErrorKind::Lint(lint) = diagnostic.error() { |
| 45 | if !lint.code_action_edits.is_empty() { |
| 46 | let source = compilation |
| 47 | .user_unit() |
| 48 | .sources |
| 49 | .find_by_name(source_name) |
| 50 | .expect("source should exist"); |
| 51 | let text_edits: Vec<TextEdit> = lint |
| 52 | .code_action_edits |
| 53 | .iter() |
| 54 | .map(|(new_text, span)| TextEdit { |
| 55 | new_text: new_text.clone(), |
| 56 | range: qsc::line_column::Range::from_span(encoding, &source.contents, span), |
| 57 | }) |
| 58 | .collect(); |
| 59 | code_actions.push(CodeAction { |
| 60 | title: diagnostic.to_string(), |
| 61 | edit: Some(WorkspaceEdit { |
| 62 | changes: vec![(source_name.to_string(), text_edits)], |
| 63 | }), |
| 64 | kind: Some(CodeActionKind::QuickFix), |
| 65 | is_preferred: None, |
| 66 | }); |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | code_actions |
| 72 | } |
| 73 | |
| 74 | /// Returns true if the error has a `Range` and it overlaps |
| 75 | /// with the code action's range. |
| 76 | fn is_error_relevant(error: &WithSource<ErrorKind>, span: Span) -> bool { |
| 77 | let Some(error_span) = resolve_span(error) else { |
| 78 | return false; |
| 79 | }; |
| 80 | span.intersection(&error_span).is_some() |
| 81 | } |
| 82 | |
| 83 | /// Extracts the uri and `Span` from an error. |
| 84 | fn resolve_span(e: &WithSource<ErrorKind>) -> Option<Span> { |
| 85 | e.labels() |
| 86 | .into_iter() |
| 87 | .flatten() |
| 88 | .map(|labeled_span| { |
| 89 | let start = u32::try_from(labeled_span.offset()).expect("offset should fit in u32"); |
| 90 | let len = u32::try_from(labeled_span.len()).expect("length should fit in u32"); |
| 91 | qsc::Span { |
| 92 | lo: start, |
| 93 | hi: start + len, |
| 94 | } |
| 95 | }) |
| 96 | .next() |
| 97 | } |
| 98 | |