ability to set suggestions on lints (#6535)

* fix the lint tests which were not compiling

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* apply sugggestions for offsetplanes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* diagnostics and suggestions for offset planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-04-28 12:07:10 -07:00
committed by GitHub
parent 2e754f2a11
commit 94452cce88
10 changed files with 339 additions and 266 deletions

View File

@ -17,30 +17,31 @@ use tokio::sync::RwLock;
use tower_lsp::{
jsonrpc::Result as RpcResult,
lsp_types::{
CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CompletionItem,
CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, CreateFilesParams,
DeleteFilesParams, Diagnostic, DiagnosticOptions, DiagnosticServerCapabilities, DiagnosticSeverity,
DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability, FullDocumentDiagnosticReport,
Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
InitializedParams, InlayHint, InlayHintParams, InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf,
Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken,
SemanticTokenModifier, SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
CodeActionProviderCapability, CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions,
CompletionParams, CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport,
DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams,
DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability,
FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability,
InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams, InsertTextFormat,
MarkupContent, MarkupKind, MessageType, OneOf, Position, RelatedFullDocumentDiagnosticReport,
RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens,
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams,
SemanticTokensRegistrationOptions, SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities,
SignatureHelp, SignatureHelpOptions, SignatureHelpParams, StaticRegistrationOptions, TextDocumentItem,
TextDocumentRegistrationOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextEdit, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
},
Client, LanguageServer,
};
use crate::{
docs::kcl_doc::DocData,
errors::Suggestion,
errors::LspSuggestion,
exec::KclValue,
execution::{cache, kcl_value::FunctionSource},
lsp::{
@ -837,6 +838,11 @@ impl LanguageServer for Backend {
Ok(InitializeResult {
capabilities: ServerCapabilities {
code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
resolve_provider: Some(false),
work_done_progress_options: WorkDoneProgressOptions::default(),
})),
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![".".to_string()]),
@ -1452,14 +1458,18 @@ impl LanguageServer for Backend {
.diagnostics
.into_iter()
.filter_map(|diagnostic| {
let (suggestion, range) = diagnostic.data.as_ref().and_then(|data| {
serde_json::from_value::<(Suggestion, tower_lsp::lsp_types::Range)>(data.clone()).ok()
})?;
let (suggestion, range) = diagnostic
.data
.as_ref()
.and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
let edit = TextEdit {
range,
new_text: suggestion.insert,
};
let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
// If you add more code action kinds, make sure you add it to the server
// capabilities on initialization!
Some(CodeActionOrCommand::CodeAction(CodeAction {
title: suggestion.title,
kind: Some(CodeActionKind::QUICKFIX),

View File

@ -19,10 +19,7 @@ use crate::{
impl IntoDiagnostic for CompilationError {
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
let edit = self.suggestion.as_ref().map(|s| {
let range = s.source_range.to_lsp_range(code);
serde_json::to_value((s, range)).unwrap()
});
let edit = self.suggestion.as_ref().map(|s| s.to_lsp_edit(code));
vec![Diagnostic {
range: self.source_range.to_lsp_range(code),
@ -33,7 +30,7 @@ impl IntoDiagnostic for CompilationError {
message: self.message.clone(),
related_information: None,
tags: self.tag.to_lsp_tags(),
data: edit,
data: edit.map(|e| serde_json::to_value(e).unwrap()),
}]
}

View File

@ -1,14 +1,19 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use pretty_assertions::assert_eq;
use tower_lsp::{
lsp_types::{Diagnostic, SemanticTokenModifier, SemanticTokenType},
lsp_types::{
CodeActionKind, CodeActionOrCommand, Diagnostic, SemanticTokenModifier, SemanticTokenType, TextEdit,
WorkspaceEdit,
},
LanguageServer,
};
use crate::{
errors::{LspSuggestion, Suggestion},
lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
parsing::ast::types::{Node, Program},
SourceRange,
};
#[track_caller]
@ -3549,3 +3554,129 @@ startSketchOn(XY)
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_kcl_lsp_code_actions_lint_offset_planes() {
let server = kcl_lsp_server(false).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "file:///testlint.kcl".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"startSketchOn({
origin = { x = 0, y = -14.3, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
})
|> startProfile(at = [0, 0])"#
.to_string(),
},
})
.await;
// Send diagnostics request.
let diagnostics = server
.diagnostic(tower_lsp::lsp_types::DocumentDiagnosticParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///testlint.kcl".try_into().unwrap(),
},
partial_result_params: Default::default(),
work_done_progress_params: Default::default(),
identifier: None,
previous_result_id: None,
})
.await
.unwrap();
// Check the diagnostics.
let tower_lsp::lsp_types::DocumentDiagnosticReportResult::Report(diagnostics) = diagnostics else {
panic!("Expected diagnostics");
};
let tower_lsp::lsp_types::DocumentDiagnosticReport::Full(diagnostics) = diagnostics else {
panic!("Expected full diagnostics");
};
assert_eq!(diagnostics.full_document_diagnostic_report.items.len(), 1);
assert_eq!(
diagnostics.full_document_diagnostic_report.items[0].message,
"offsetPlane should be used to define a new plane offset from the origin"
);
// Make sure we get the suggestion data.
assert_eq!(
diagnostics.full_document_diagnostic_report.items[0]
.data
.clone()
.map(|d| serde_json::from_value::<LspSuggestion>(d).unwrap()),
Some((
Suggestion {
insert: "offsetPlane(XZ, offset = -14.3)".to_string(),
source_range: SourceRange::new(14, 133, Default::default()),
title: "use offsetPlane instead".to_string(),
},
tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 0, character: 14 },
end: tower_lsp::lsp_types::Position { line: 4, character: 1 },
}
))
);
let diagnostic = diagnostics.full_document_diagnostic_report.items[0].clone();
// Run a code action.
let code_action = server
.code_action(tower_lsp::lsp_types::CodeActionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///testlint.kcl".try_into().unwrap(),
},
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 0, character: 14 },
end: tower_lsp::lsp_types::Position { line: 4, character: 1 },
},
context: tower_lsp::lsp_types::CodeActionContext {
diagnostics: vec![diagnostic.clone()],
only: None,
trigger_kind: Default::default(),
},
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
})
.await
.unwrap();
assert!(code_action.is_some());
let code_action = code_action.unwrap();
assert_eq!(code_action.len(), 1);
assert_eq!(
code_action[0],
CodeActionOrCommand::CodeAction(tower_lsp::lsp_types::CodeAction {
title: "use offsetPlane instead".to_string(),
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diagnostic]),
edit: Some(WorkspaceEdit {
changes: Some(HashMap::from_iter(vec![(
"file:///testlint.kcl".try_into().unwrap(),
vec![TextEdit {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 0, character: 14 },
end: tower_lsp::lsp_types::Position { line: 4, character: 1 },
},
new_text: "offsetPlane(XZ, offset = -14.3)".to_string(),
}],
)])),
document_changes: None,
change_annotations: None,
}),
command: None,
is_preferred: Some(true),
disabled: None,
data: None,
})
);
}