From ced674449a1e908301115f015b35c7a365aff3f3 Mon Sep 17 00:00:00 2001 From: He1pa <18012015693@163.com> Date: Tue, 8 Aug 2023 16:44:16 +0800 Subject: [PATCH] feat: diagnostic related info. Add diagnostic related information to display multiple position diagnostic --- .../tools/src/LSP/src/test_data/diagnostics.k | 5 +- kclvm/tools/src/LSP/src/tests.rs | 57 ++++++++++++++++++- kclvm/tools/src/LSP/src/to_lsp.rs | 47 ++++++++++++--- 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/kclvm/tools/src/LSP/src/test_data/diagnostics.k b/kclvm/tools/src/LSP/src/test_data/diagnostics.k index e9e013c8f..a08fe38ed 100644 --- a/kclvm/tools/src/LSP/src/test_data/diagnostics.k +++ b/kclvm/tools/src/LSP/src/test_data/diagnostics.k @@ -3,4 +3,7 @@ a = b: str = 1 c: Person = Person { age: 1 -} \ No newline at end of file +} + +d = 1 +d = 2 \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index 8eba4629d..bd360fb32 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -13,9 +13,11 @@ use kclvm_sema::resolver::scope::ProgramScope; use lsp_types::request::GotoTypeDefinitionResponse; use lsp_types::CompletionResponse; use lsp_types::Diagnostic; +use lsp_types::DiagnosticRelatedInformation; use lsp_types::DiagnosticSeverity; use lsp_types::DocumentSymbol; use lsp_types::DocumentSymbolResponse; +use lsp_types::Location; use lsp_types::MarkedString; use lsp_types::SymbolKind; use lsp_types::Url; @@ -80,7 +82,33 @@ fn diagnostics_test() { pos: (u32, u32, u32, u32), message: String, severity: Option, + related_info: Vec<(String, (u32, u32, u32, u32), String)>, ) -> Diagnostic { + let related_information = if related_info.is_empty() { + None + } else { + Some( + related_info + .iter() + .map(|(file, pos, msg)| DiagnosticRelatedInformation { + location: Location { + uri: Url::from_file_path(file).unwrap(), + range: Range { + start: Position { + line: pos.0, + character: pos.1, + }, + end: Position { + line: pos.2, + character: pos.3, + }, + }, + }, + message: msg.clone(), + }) + .collect(), + ) + }; Diagnostic { range: lsp_types::Range { start: Position { @@ -97,7 +125,7 @@ fn diagnostics_test() { code_description: None, source: None, message, - related_information: None, + related_information, tags: None, data: None, } @@ -127,11 +155,13 @@ fn diagnostics_test() { "expected one of [\"identifier\", \"literal\", \"(\", \"[\", \"{\"] got newline" .to_string(), Some(DiagnosticSeverity::ERROR), + vec![], ), build_lsp_diag( (0, 0, 0, 10), "pkgpath abc not found in the program".to_string(), Some(DiagnosticSeverity::ERROR), + vec![], ), build_lsp_diag( (0, 0, 0, 10), @@ -140,20 +170,43 @@ fn diagnostics_test() { path.to_str().unwrap() ), Some(DiagnosticSeverity::ERROR), + vec![], + ), + build_lsp_diag( + (8, 0, 8, 1), + "Can not change the value of 'd', because it was declared immutable".to_string(), + Some(DiagnosticSeverity::ERROR), + vec![( + file.to_string(), + (7, 0, 7, 1), + "The variable 'd' is declared here".to_string(), + )], + ), + build_lsp_diag( + (7, 0, 7, 1), + "The variable 'd' is declared here".to_string(), + Some(DiagnosticSeverity::ERROR), + vec![( + file.to_string(), + (8, 0, 8, 1), + "Can not change the value of 'd', because it was declared immutable".to_string(), + )], ), build_lsp_diag( (2, 0, 2, 1), "expected str, got int(1)".to_string(), Some(DiagnosticSeverity::ERROR), + vec![], ), build_lsp_diag( (0, 0, 0, 10), "Module 'abc' imported but unused".to_string(), Some(DiagnosticSeverity::WARNING), + vec![], ), ]; for (get, expected) in diagnostics.iter().zip(expected_diags.iter()) { - assert_eq!(get, expected); + assert_eq!(get, expected) } } diff --git a/kclvm/tools/src/LSP/src/to_lsp.rs b/kclvm/tools/src/LSP/src/to_lsp.rs index 732f81eb3..c856d8a31 100644 --- a/kclvm/tools/src/LSP/src/to_lsp.rs +++ b/kclvm/tools/src/LSP/src/to_lsp.rs @@ -21,11 +21,35 @@ pub fn lsp_pos(pos: &KCLPos) -> Position { } /// Convert KCL Message to LSP Diagnostic -fn kcl_msg_to_lsp_diags(msg: &Message, severity: DiagnosticSeverity) -> Diagnostic { +fn kcl_msg_to_lsp_diags( + msg: &Message, + severity: DiagnosticSeverity, + related_msg: Vec, +) -> Diagnostic { let range = msg.range.clone(); let start_position = lsp_pos(&range.0); let end_position = lsp_pos(&range.1); + let related_information = if related_msg.is_empty() { + None + } else { + Some( + related_msg + .iter() + .map(|m| DiagnosticRelatedInformation { + location: Location { + uri: Url::from_file_path(m.range.0.filename.clone()).unwrap(), + range: Range { + start: lsp_pos(&m.range.0), + end: lsp_pos(&m.range.1), + }, + }, + message: m.message.clone(), + }) + .collect(), + ) + }; + Diagnostic { range: Range::new(start_position, end_position), severity: Some(severity), @@ -33,7 +57,7 @@ fn kcl_msg_to_lsp_diags(msg: &Message, severity: DiagnosticSeverity) -> Diagnost code_description: None, source: None, message: msg.message.clone(), - related_information: None, + related_information, tags: None, data: None, } @@ -48,13 +72,20 @@ fn kcl_err_level_to_severity(level: Level) -> DiagnosticSeverity { } /// Convert KCL Diagnostic to LSP Diagnostics. -/// Because the diagnostic of KCL contains multiple messages, and each messages corresponds to a diagnostic of LSP, the return value is a vec pub fn kcl_diag_to_lsp_diags(diag: &KCLDiagnostic, file_name: &str) -> Vec { - diag.messages - .iter() - .filter(|msg| msg.range.0.filename == file_name) - .map(|msg| kcl_msg_to_lsp_diags(msg, kcl_err_level_to_severity(diag.level))) - .collect() + let mut diags = vec![]; + for (idx, msg) in diag.messages.iter().enumerate() { + if msg.range.0.filename == file_name { + let mut related_msg = diag.messages.clone(); + related_msg.remove(idx); + diags.push(kcl_msg_to_lsp_diags( + msg, + kcl_err_level_to_severity(diag.level), + related_msg, + )) + } + } + diags } /// Returns the `Url` associated with the specified `FileId`.