Also suggest qualified imports as quickfix when relevant.
Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
parent
c3f571334c
commit
3c8bc7ebb6
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## v1.1.15 - UNRELEASED
|
## v1.1.15 - UNRELEASED
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **aiken-lsp**: an additional code action to use constructors or identifiers from qualified imports is now offered on missing constructor or identifier. @KtorZ
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- **aiken-lang**: fixed `UnknownTypeConstructor` wrongly reported as `UnknownVariable` (then messing up with LSP quickfix suggestions). @KtorZ
|
- **aiken-lang**: fixed `UnknownTypeConstructor` wrongly reported as `UnknownVariable` (then messing up with LSP quickfix suggestions). @KtorZ
|
||||||
|
|
|
@ -14,7 +14,10 @@ pub struct ParsedDocument {
|
||||||
source_code: String,
|
source_code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AnnotatedEdit = (String, lsp_types::TextEdit);
|
pub enum AnnotatedEdit {
|
||||||
|
SimpleEdit(String, lsp_types::TextEdit),
|
||||||
|
CombinedEdits(String, Vec<lsp_types::TextEdit>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the target document as an 'UntypedModule' alongside its line numbers. This is useful in
|
/// Parse the target document as an 'UntypedModule' alongside its line numbers. This is useful in
|
||||||
/// case we need to manipulate the AST for a quickfix.
|
/// case we need to manipulate the AST for a quickfix.
|
||||||
|
@ -166,20 +169,36 @@ impl ParsedDocument {
|
||||||
|
|
||||||
let new_text = String::new();
|
let new_text = String::new();
|
||||||
|
|
||||||
(
|
AnnotatedEdit::SimpleEdit(
|
||||||
"Remove redundant import".to_string(),
|
"Remove redundant import".to_string(),
|
||||||
lsp_types::TextEdit { range, new_text },
|
lsp_types::TextEdit { range, new_text },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn use_qualified(
|
||||||
|
module: &str,
|
||||||
|
unqualified: &str,
|
||||||
|
range: &lsp_types::Range,
|
||||||
|
) -> Option<AnnotatedEdit> {
|
||||||
|
let title = format!("Use qualified from {}", module);
|
||||||
|
let suffix = module.split("/").last()?;
|
||||||
|
Some(AnnotatedEdit::SimpleEdit(
|
||||||
|
title,
|
||||||
|
lsp_types::TextEdit {
|
||||||
|
range: *range,
|
||||||
|
new_text: format!("{suffix}.{unqualified}"),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_qualified_before(
|
fn insert_qualified_before(
|
||||||
&self,
|
&self,
|
||||||
import: &CheckedModule,
|
import: &CheckedModule,
|
||||||
unqualified: &str,
|
unqualified: &str,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> AnnotatedEdit {
|
) -> AnnotatedEdit {
|
||||||
let title = format!("Use '{}' from {}", unqualified, import.name);
|
let title = format!("Import '{}' from {}", unqualified, import.name);
|
||||||
(
|
AnnotatedEdit::SimpleEdit(
|
||||||
title,
|
title,
|
||||||
insert_text(
|
insert_text(
|
||||||
location.start,
|
location.start,
|
||||||
|
@ -195,8 +214,8 @@ impl ParsedDocument {
|
||||||
unqualified: &str,
|
unqualified: &str,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> AnnotatedEdit {
|
) -> AnnotatedEdit {
|
||||||
let title = format!("Use '{}' from {}", unqualified, import.name);
|
let title = format!("Import '{}' from {}", unqualified, import.name);
|
||||||
(
|
AnnotatedEdit::SimpleEdit(
|
||||||
title,
|
title,
|
||||||
insert_text(
|
insert_text(
|
||||||
location.end,
|
location.end,
|
||||||
|
@ -212,8 +231,8 @@ impl ParsedDocument {
|
||||||
unqualified: &str,
|
unqualified: &str,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> AnnotatedEdit {
|
) -> AnnotatedEdit {
|
||||||
let title = format!("Use '{}' from {}", unqualified, import.name);
|
let title = format!("Import '{}' from {}", unqualified, import.name);
|
||||||
(
|
AnnotatedEdit::SimpleEdit(
|
||||||
title,
|
title,
|
||||||
insert_text(
|
insert_text(
|
||||||
location.end,
|
location.end,
|
||||||
|
@ -238,9 +257,9 @@ impl ParsedDocument {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let title = format!("Add new import line: {import_line}");
|
let title = format!("Add line: '{import_line}'");
|
||||||
|
|
||||||
(
|
AnnotatedEdit::SimpleEdit(
|
||||||
title,
|
title,
|
||||||
match location {
|
match location {
|
||||||
None => insert_text(0, &self.line_numbers, format!("{import_line}\n")),
|
None => insert_text(0, &self.line_numbers, format!("{import_line}\n")),
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
edits::{self, AnnotatedEdit, ParsedDocument},
|
edits::{self, AnnotatedEdit, ParsedDocument},
|
||||||
server::lsp_project::LspProject,
|
server::lsp_project::LspProject,
|
||||||
};
|
};
|
||||||
|
use aiken_project::module::CheckedModule;
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
const UNKNOWN_VARIABLE: &str = "aiken::check::unknown::variable";
|
const UNKNOWN_VARIABLE: &str = "aiken::check::unknown::variable";
|
||||||
|
@ -94,7 +95,12 @@ pub fn quickfix(
|
||||||
&mut actions,
|
&mut actions,
|
||||||
text_document,
|
text_document,
|
||||||
diagnostic,
|
diagnostic,
|
||||||
unknown_identifier(compiler, parsed_document, diagnostic.data.as_ref()),
|
unknown_identifier(
|
||||||
|
compiler,
|
||||||
|
parsed_document,
|
||||||
|
&diagnostic.range,
|
||||||
|
diagnostic.data.as_ref(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Quickfix::UnknownModule(diagnostic) => each_as_distinct_action(
|
Quickfix::UnknownModule(diagnostic) => each_as_distinct_action(
|
||||||
|
@ -107,7 +113,12 @@ pub fn quickfix(
|
||||||
&mut actions,
|
&mut actions,
|
||||||
text_document,
|
text_document,
|
||||||
diagnostic,
|
diagnostic,
|
||||||
unknown_constructor(compiler, parsed_document, diagnostic.data.as_ref()),
|
unknown_constructor(
|
||||||
|
compiler,
|
||||||
|
parsed_document,
|
||||||
|
&diagnostic.range,
|
||||||
|
diagnostic.data.as_ref(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Quickfix::UnusedImports(diagnostics) => as_single_action(
|
Quickfix::UnusedImports(diagnostics) => as_single_action(
|
||||||
&mut actions,
|
&mut actions,
|
||||||
|
@ -152,10 +163,19 @@ fn each_as_distinct_action(
|
||||||
diagnostic: &lsp_types::Diagnostic,
|
diagnostic: &lsp_types::Diagnostic,
|
||||||
edits: Vec<AnnotatedEdit>,
|
edits: Vec<AnnotatedEdit>,
|
||||||
) {
|
) {
|
||||||
for (title, edit) in edits.into_iter() {
|
for edit in edits.into_iter() {
|
||||||
let mut changes = HashMap::new();
|
let mut changes = HashMap::new();
|
||||||
|
|
||||||
changes.insert(text_document.uri.clone(), vec![edit]);
|
let title = match edit {
|
||||||
|
AnnotatedEdit::SimpleEdit(title, one) => {
|
||||||
|
changes.insert(text_document.uri.clone(), vec![one]);
|
||||||
|
title
|
||||||
|
}
|
||||||
|
AnnotatedEdit::CombinedEdits(title, many) => {
|
||||||
|
changes.insert(text_document.uri.clone(), many);
|
||||||
|
title
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
actions.push(lsp_types::CodeAction {
|
actions.push(lsp_types::CodeAction {
|
||||||
title,
|
title,
|
||||||
|
@ -185,7 +205,13 @@ fn as_single_action(
|
||||||
|
|
||||||
changes.insert(
|
changes.insert(
|
||||||
text_document.uri.clone(),
|
text_document.uri.clone(),
|
||||||
edits.into_iter().map(|(_, b)| b).collect(),
|
edits
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|edit| match edit {
|
||||||
|
AnnotatedEdit::SimpleEdit(_, one) => vec![one],
|
||||||
|
AnnotatedEdit::CombinedEdits(_, many) => many,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
actions.push(lsp_types::CodeAction {
|
actions.push(lsp_types::CodeAction {
|
||||||
|
@ -207,6 +233,7 @@ fn as_single_action(
|
||||||
fn unknown_identifier(
|
fn unknown_identifier(
|
||||||
compiler: &LspProject,
|
compiler: &LspProject,
|
||||||
parsed_document: &ParsedDocument,
|
parsed_document: &ParsedDocument,
|
||||||
|
range: &lsp_types::Range,
|
||||||
data: Option<&serde_json::Value>,
|
data: Option<&serde_json::Value>,
|
||||||
) -> Vec<AnnotatedEdit> {
|
) -> Vec<AnnotatedEdit> {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
@ -217,6 +244,10 @@ fn unknown_identifier(
|
||||||
if let Some(edit) = parsed_document.import(&module, Some(var_name)) {
|
if let Some(edit) = parsed_document.import(&module, Some(var_name)) {
|
||||||
edits.push(edit)
|
edits.push(edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(edit) = suggest_qualified(parsed_document, &module, var_name, range) {
|
||||||
|
edits.push(edit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,6 +258,7 @@ fn unknown_identifier(
|
||||||
fn unknown_constructor(
|
fn unknown_constructor(
|
||||||
compiler: &LspProject,
|
compiler: &LspProject,
|
||||||
parsed_document: &ParsedDocument,
|
parsed_document: &ParsedDocument,
|
||||||
|
range: &lsp_types::Range,
|
||||||
data: Option<&serde_json::Value>,
|
data: Option<&serde_json::Value>,
|
||||||
) -> Vec<AnnotatedEdit> {
|
) -> Vec<AnnotatedEdit> {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
@ -237,6 +269,12 @@ fn unknown_constructor(
|
||||||
if let Some(edit) = parsed_document.import(&module, Some(constructor_name)) {
|
if let Some(edit) = parsed_document.import(&module, Some(constructor_name)) {
|
||||||
edits.push(edit)
|
edits.push(edit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(edit) =
|
||||||
|
suggest_qualified(parsed_document, &module, constructor_name, range)
|
||||||
|
{
|
||||||
|
edits.push(edit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,6 +282,33 @@ fn unknown_constructor(
|
||||||
edits
|
edits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn suggest_qualified(
|
||||||
|
parsed_document: &ParsedDocument,
|
||||||
|
module: &CheckedModule,
|
||||||
|
identifier: &str,
|
||||||
|
range: &lsp_types::Range,
|
||||||
|
) -> Option<AnnotatedEdit> {
|
||||||
|
if let Some(AnnotatedEdit::SimpleEdit(use_qualified_title, use_qualified)) =
|
||||||
|
ParsedDocument::use_qualified(&module.name, identifier, range)
|
||||||
|
{
|
||||||
|
if let Some(AnnotatedEdit::SimpleEdit(_, add_new_line)) =
|
||||||
|
parsed_document.import(module, None)
|
||||||
|
{
|
||||||
|
return Some(AnnotatedEdit::CombinedEdits(
|
||||||
|
use_qualified_title,
|
||||||
|
vec![add_new_line, use_qualified],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Some(AnnotatedEdit::SimpleEdit(
|
||||||
|
use_qualified_title,
|
||||||
|
use_qualified,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn unknown_module(
|
fn unknown_module(
|
||||||
compiler: &LspProject,
|
compiler: &LspProject,
|
||||||
parsed_document: &ParsedDocument,
|
parsed_document: &ParsedDocument,
|
||||||
|
@ -298,7 +363,7 @@ fn utf8_byte_array_is_hex_string(diagnostic: &lsp_types::Diagnostic) -> Vec<Anno
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
|
||||||
if let Some(serde_json::Value::String(ref value)) = diagnostic.data.as_ref() {
|
if let Some(serde_json::Value::String(ref value)) = diagnostic.data.as_ref() {
|
||||||
edits.push((
|
edits.push(AnnotatedEdit::SimpleEdit(
|
||||||
"Prefix with #".to_string(),
|
"Prefix with #".to_string(),
|
||||||
lsp_types::TextEdit {
|
lsp_types::TextEdit {
|
||||||
range: diagnostic.range,
|
range: diagnostic.range,
|
||||||
|
@ -311,7 +376,7 @@ fn utf8_byte_array_is_hex_string(diagnostic: &lsp_types::Diagnostic) -> Vec<Anno
|
||||||
}
|
}
|
||||||
|
|
||||||
fn use_let(diagnostic: &lsp_types::Diagnostic) -> Vec<AnnotatedEdit> {
|
fn use_let(diagnostic: &lsp_types::Diagnostic) -> Vec<AnnotatedEdit> {
|
||||||
vec![(
|
vec![AnnotatedEdit::SimpleEdit(
|
||||||
"Use 'let' instead of 'expect'".to_string(),
|
"Use 'let' instead of 'expect'".to_string(),
|
||||||
lsp_types::TextEdit {
|
lsp_types::TextEdit {
|
||||||
range: diagnostic.range,
|
range: diagnostic.range,
|
||||||
|
@ -324,7 +389,7 @@ fn unused_record_fields(diagnostic: &lsp_types::Diagnostic) -> Vec<AnnotatedEdit
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
|
||||||
if let Some(serde_json::Value::String(new_text)) = diagnostic.data.as_ref() {
|
if let Some(serde_json::Value::String(new_text)) = diagnostic.data.as_ref() {
|
||||||
edits.push((
|
edits.push(AnnotatedEdit::SimpleEdit(
|
||||||
"Destructure using named fields".to_string(),
|
"Destructure using named fields".to_string(),
|
||||||
lsp_types::TextEdit {
|
lsp_types::TextEdit {
|
||||||
range: diagnostic.range,
|
range: diagnostic.range,
|
||||||
|
|
Loading…
Reference in New Issue