From 220813dcb0881ff199619c11eb34a39a6de0f67a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 15 May 2020 01:51:48 +0200 Subject: [PATCH 1/3] Move LSP bits from flycheck to rust-analyzer There should be only one place that knows about LSP, and that place is right before we spit JSON on stdout. --- Cargo.lock | 4 +- crates/ra_flycheck/Cargo.toml | 4 - crates/ra_flycheck/src/conv.rs | 341 ---- crates/ra_flycheck/src/conv/test.rs | 1072 ------------- crates/ra_flycheck/src/lib.rs | 90 +- crates/rust-analyzer/Cargo.toml | 2 + crates/rust-analyzer/src/diagnostics.rs | 1 + ...roto__tests__snap_clippy_pass_by_ref.snap} | 2 +- ...__tests__snap_handles_macro_location.snap} | 2 +- ...to__tests__snap_macro_compiler_error.snap} | 2 +- ...to_proto__tests__snap_multi_line_fix.snap} | 2 +- ...ap_rustc_incompatible_type_for_trait.snap} | 2 +- ...o__tests__snap_rustc_mismatched_type.snap} | 2 +- ...o__tests__snap_rustc_unused_variable.snap} | 2 +- ...nap_rustc_wrong_number_of_parameters.snap} | 2 +- .../rust-analyzer/src/diagnostics/to_proto.rs | 1408 +++++++++++++++++ crates/rust-analyzer/src/main_loop.rs | 67 +- crates/rust-analyzer/src/world.rs | 6 +- 18 files changed, 1505 insertions(+), 1506 deletions(-) delete mode 100644 crates/ra_flycheck/src/conv.rs delete mode 100644 crates/ra_flycheck/src/conv/test.rs rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_clippy_pass_by_ref.snap} (98%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_handles_macro_location.snap} (95%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_macro_compiler_error.snap} (97%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap} (98%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_incompatible_type_for_trait.snap} (95%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_mismatched_type.snap} (95%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap} (97%) rename crates/{ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap => rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_wrong_number_of_parameters.snap} (97%) create mode 100644 crates/rust-analyzer/src/diagnostics/to_proto.rs diff --git a/Cargo.lock b/Cargo.lock index 3a34978b1366..31fe89cdc03f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -975,10 +975,8 @@ version = "0.1.0" dependencies = [ "cargo_metadata", "crossbeam-channel", - "insta", "jod-thread", "log", - "lsp-types", "ra_toolchain", "serde_json", ] @@ -1378,9 +1376,11 @@ name = "rust-analyzer" version = "0.1.0" dependencies = [ "anyhow", + "cargo_metadata", "crossbeam-channel", "env_logger", "globset", + "insta", "itertools", "jod-thread", "log", diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml index eac502da51f6..1aa39badea96 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml @@ -9,12 +9,8 @@ doctest = false [dependencies] crossbeam-channel = "0.4.0" -lsp-types = { version = "0.74.0", features = ["proposed"] } log = "0.4.8" cargo_metadata = "0.10.0" serde_json = "1.0.48" jod-thread = "0.1.1" ra_toolchain = { path = "../ra_toolchain" } - -[dev-dependencies] -insta = "0.16.0" diff --git a/crates/ra_flycheck/src/conv.rs b/crates/ra_flycheck/src/conv.rs deleted file mode 100644 index 817543deb197..000000000000 --- a/crates/ra_flycheck/src/conv.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! This module provides the functionality needed to convert diagnostics from -//! `cargo check` json format to the LSP diagnostic format. -use cargo_metadata::diagnostic::{ - Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, - DiagnosticSpanMacroExpansion, -}; -use lsp_types::{ - CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, - Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, -}; -use std::{ - collections::HashMap, - fmt::Write, - path::{Component, Path, PathBuf, Prefix}, - str::FromStr, -}; - -#[cfg(test)] -mod test; - -/// Converts a Rust level string to a LSP severity -fn map_level_to_severity(val: DiagnosticLevel) -> Option { - match val { - DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), - DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), - DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), - DiagnosticLevel::Unknown => None, - } -} - -/// Check whether a file name is from macro invocation -fn is_from_macro(file_name: &str) -> bool { - file_name.starts_with('<') && file_name.ends_with('>') -} - -/// Converts a Rust macro span to a LSP location recursively -fn map_macro_span_to_location( - span_macro: &DiagnosticSpanMacroExpansion, - workspace_root: &PathBuf, -) -> Option { - if !is_from_macro(&span_macro.span.file_name) { - return Some(map_span_to_location(&span_macro.span, workspace_root)); - } - - if let Some(expansion) = &span_macro.span.expansion { - return map_macro_span_to_location(&expansion, workspace_root); - } - - None -} - -/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary -fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - if span.expansion.is_some() { - let expansion = span.expansion.as_ref().unwrap(); - if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { - return macro_range; - } - } - - map_span_to_location_naive(span, workspace_root) -} - -/// Converts a Rust span to a LSP location -fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - let mut file_name = workspace_root.clone(); - file_name.push(&span.file_name); - let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); - - let range = Range::new( - Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), - Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), - ); - - Location { uri, range } -} - -/// Converts a secondary Rust span to a LSP related information -/// -/// If the span is unlabelled this will return `None`. -fn map_secondary_span_to_related( - span: &DiagnosticSpan, - workspace_root: &PathBuf, -) -> Option { - if let Some(label) = &span.label { - let location = map_span_to_location(span, workspace_root); - Some(DiagnosticRelatedInformation { location, message: label.clone() }) - } else { - // Nothing to label this with - None - } -} - -/// Determines if diagnostic is related to unused code -fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" - | "unused_imports" | "unused_macros" | "unused_variables" => true, - _ => false, - } - } else { - false - } -} - -/// Determines if diagnostic is related to deprecated code -fn is_deprecated(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "deprecated" => true, - _ => false, - } - } else { - false - } -} - -enum MappedRustChildDiagnostic { - Related(DiagnosticRelatedInformation), - SuggestedFix(CodeAction), - MessageLine(String), -} - -fn map_rust_child_diagnostic( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> MappedRustChildDiagnostic { - let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); - if spans.is_empty() { - // `rustc` uses these spanless children as a way to print multi-line - // messages - return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); - } - - let mut edit_map: HashMap> = HashMap::new(); - for &span in &spans { - match (&span.suggestion_applicability, &span.suggested_replacement) { - (Some(Applicability::MachineApplicable), Some(suggested_replacement)) => { - let location = map_span_to_location(span, workspace_root); - let edit = TextEdit::new(location.range, suggested_replacement.clone()); - edit_map.entry(location.uri).or_default().push(edit); - } - _ => {} - } - } - - if !edit_map.is_empty() { - MappedRustChildDiagnostic::SuggestedFix(CodeAction { - title: rd.message.clone(), - kind: Some("quickfix".to_string()), - diagnostics: None, - edit: Some(WorkspaceEdit::new(edit_map)), - command: None, - is_preferred: None, - }) - } else { - MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { - location: map_span_to_location(spans[0], workspace_root), - message: rd.message.clone(), - }) - } -} - -#[derive(Debug)] -pub(crate) struct MappedRustDiagnostic { - pub location: Location, - pub diagnostic: Diagnostic, - pub fixes: Vec, -} - -/// Converts a Rust root diagnostic to LSP form -/// -/// This flattens the Rust diagnostic by: -/// -/// 1. Creating a LSP diagnostic with the root message and primary span. -/// 2. Adding any labelled secondary spans to `relatedInformation` -/// 3. Categorising child diagnostics as either `SuggestedFix`es, -/// `relatedInformation` or additional message lines. -/// -/// If the diagnostic has no primary span this will return `None` -pub(crate) fn map_rust_diagnostic_to_lsp( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> Vec { - let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); - if primary_spans.is_empty() { - return vec![]; - } - - let severity = map_level_to_severity(rd.level); - - let mut source = String::from("rustc"); - let mut code = rd.code.as_ref().map(|c| c.code.clone()); - if let Some(code_val) = &code { - // See if this is an RFC #2103 scoped lint (e.g. from Clippy) - let scoped_code: Vec<&str> = code_val.split("::").collect(); - if scoped_code.len() == 2 { - source = String::from(scoped_code[0]); - code = Some(String::from(scoped_code[1])); - } - } - - let mut needs_primary_span_label = true; - let mut related_information = vec![]; - let mut tags = vec![]; - - for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { - let related = map_secondary_span_to_related(secondary_span, workspace_root); - if let Some(related) = related { - related_information.push(related); - } - } - - let mut fixes = vec![]; - let mut message = rd.message.clone(); - for child in &rd.children { - let child = map_rust_child_diagnostic(&child, workspace_root); - match child { - MappedRustChildDiagnostic::Related(related) => related_information.push(related), - MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), - MappedRustChildDiagnostic::MessageLine(message_line) => { - write!(&mut message, "\n{}", message_line).unwrap(); - - // These secondary messages usually duplicate the content of the - // primary span label. - needs_primary_span_label = false; - } - } - } - - if is_unused_or_unnecessary(rd) { - tags.push(DiagnosticTag::Unnecessary); - } - - if is_deprecated(rd) { - tags.push(DiagnosticTag::Deprecated); - } - - primary_spans - .iter() - .map(|primary_span| { - let location = map_span_to_location(&primary_span, workspace_root); - - let mut message = message.clone(); - if needs_primary_span_label { - if let Some(primary_span_label) = &primary_span.label { - write!(&mut message, "\n{}", primary_span_label).unwrap(); - } - } - - // If error occurs from macro expansion, add related info pointing to - // where the error originated - if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { - let def_loc = map_span_to_location_naive(&primary_span, workspace_root); - related_information.push(DiagnosticRelatedInformation { - location: def_loc, - message: "Error originated from macro here".to_string(), - }); - } - - let diagnostic = Diagnostic { - range: location.range, - severity, - code: code.clone().map(NumberOrString::String), - source: Some(source.clone()), - message, - related_information: if !related_information.is_empty() { - Some(related_information.clone()) - } else { - None - }, - tags: if !tags.is_empty() { Some(tags.clone()) } else { None }, - }; - - MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() } - }) - .collect() -} - -/// Returns a `Url` object from a given path, will lowercase drive letters if present. -/// This will only happen when processing windows paths. -/// -/// When processing non-windows path, this is essentially the same as `Url::from_file_path`. -pub fn url_from_path_with_drive_lowercasing( - path: impl AsRef, -) -> Result> { - let component_has_windows_drive = path.as_ref().components().any(|comp| { - if let Component::Prefix(c) = comp { - match c.kind() { - Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true, - _ => return false, - } - } - false - }); - - // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. - if component_has_windows_drive { - let url_original = Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; - - let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect(); - - // There is a drive partition, but we never found a colon. - // This should not happen, but in this case we just pass it through. - if drive_partition.len() == 1 { - return Ok(url_original); - } - - let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; - let url = Url::from_str(&joined).expect("This came from a valid `Url`"); - - Ok(url) - } else { - Ok(Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?) - } -} - -// `Url` is not able to parse windows paths on unix machines. -#[cfg(target_os = "windows")] -#[cfg(test)] -mod path_conversion_windows_tests { - use super::url_from_path_with_drive_lowercasing; - #[test] - fn test_lowercase_drive_letter_with_drive() { - let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap(); - - assert_eq!(url.to_string(), "file:///c:/Test"); - } - - #[test] - fn test_drive_without_colon_passthrough() { - let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap(); - - assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); - } -} diff --git a/crates/ra_flycheck/src/conv/test.rs b/crates/ra_flycheck/src/conv/test.rs deleted file mode 100644 index 4e81455ca177..000000000000 --- a/crates/ra_flycheck/src/conv/test.rs +++ /dev/null @@ -1,1072 +0,0 @@ -//! This module contains the large and verbose snapshot tests for the -//! conversions between `cargo check` json and LSP diagnostics. -#[cfg(not(windows))] -use crate::*; - -#[cfg(not(windows))] -fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { - serde_json::from_str::(val).unwrap() -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_incompatible_type_for_trait() { - let diag = parse_diagnostic( - r##"{ - "message": "method `next` has an incompatible type for trait", - "code": { - "code": "E0053", - "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n" - }, - "level": "error", - "spans": [ - { - "file_name": "compiler/ty/list_iter.rs", - "byte_start": 1307, - "byte_end": 1350, - "line_start": 52, - "line_end": 52, - "column_start": 5, - "column_end": 48, - "is_primary": true, - "text": [ - { - "text": " fn next(&self) -> Option<&'list ty::Ref> {", - "highlight_start": 5, - "highlight_end": 48 - } - ], - "label": "types differ in mutability", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [ - { - "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - } - ], - "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`\n\n" - } - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_unused_variable() { - let diag = parse_diagnostic( - r##"{ -"message": "unused variable: `foo`", -"code": { - "code": "unused_variables", - "explanation": null -}, -"level": "warning", -"spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [ - { - "message": "#[warn(unused_variables)] on by default", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider prefixing with an underscore", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": "_foo", - "suggestion_applicability": "MachineApplicable", - "expansion": null - } - ], - "children": [], - "rendered": null - } -], -"rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_wrong_number_of_parameters() { - let diag = parse_diagnostic( - r##"{ -"message": "this function takes 2 parameters but 3 parameters were supplied", -"code": { - "code": "E0061", - "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n" -}, -"level": "error", -"spans": [ - { - "file_name": "compiler/ty/select.rs", - "byte_start": 8787, - "byte_end": 9241, - "line_start": 219, - "line_end": 231, - "column_start": 5, - "column_end": 6, - "is_primary": false, - "text": [ - { - "text": " pub fn add_evidence(", - "highlight_start": 5, - "highlight_end": 25 - }, - { - "text": " &mut self,", - "highlight_start": 1, - "highlight_end": 19 - }, - { - "text": " target_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 41 - }, - { - "text": " evidence_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " ) {", - "highlight_start": 1, - "highlight_end": 8 - }, - { - "text": " match target_poly {", - "highlight_start": 1, - "highlight_end": 28 - }, - { - "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),", - "highlight_start": 1, - "highlight_end": 81 - }, - { - "text": " ty::Ref::Fixed(target_ty) => {", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " let evidence_ty = evidence_poly.resolve_to_ty();", - "highlight_start": 1, - "highlight_end": 65 - }, - { - "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)", - "highlight_start": 1, - "highlight_end": 76 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 14 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 10 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 6 - } - ], - "label": "defined here", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - }, - { - "file_name": "compiler/ty/select.rs", - "byte_start": 4045, - "byte_end": 4057, - "line_start": 104, - "line_end": 104, - "column_start": 18, - "column_end": 30, - "is_primary": true, - "text": [ - { - "text": " self.add_evidence(target_fixed, evidence_fixed, false);", - "highlight_start": 18, - "highlight_end": 30 - } - ], - "label": "expected 2 parameters", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [], -"rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref,\n222 | | evidence_poly: &ty::Ref,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_clippy_pass_by_ref() { - let diag = parse_diagnostic( - r##"{ -"message": "this argument is passed by reference, but would be more efficient if passed by value", -"code": { - "code": "clippy::trivially_copy_pass_by_ref", - "explanation": null -}, -"level": "warning", -"spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [ - { - "message": "lint level defined here", - "code": null, - "level": "note", - "spans": [ - { - "file_name": "compiler/lib.rs", - "byte_start": 8, - "byte_end": 19, - "line_start": 1, - "line_end": 1, - "column_start": 9, - "column_end": 20, - "is_primary": true, - "text": [ - { - "text": "#![warn(clippy::all)]", - "highlight_start": 9, - "highlight_end": 20 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [], - "rendered": null - }, - { - "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", - "code": null, - "level": "help", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider passing by value instead", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": "self", - "suggestion_applicability": "Unspecified", - "expansion": null - } - ], - "children": [], - "rendered": null - } -], -"rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_rustc_mismatched_type() { - let diag = parse_diagnostic( - r##"{ -"message": "mismatched types", -"code": { - "code": "E0308", - "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" -}, -"level": "error", -"spans": [ - { - "file_name": "runtime/compiler_support.rs", - "byte_start": 1589, - "byte_end": 1594, - "line_start": 48, - "line_end": 48, - "column_start": 65, - "column_end": 70, - "is_primary": true, - "text": [ - { - "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);", - "highlight_start": 65, - "highlight_end": 70 - } - ], - "label": "expected usize, found u32", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } -], -"children": [], -"rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_handles_macro_location() { - let diag = parse_diagnostic( - r##"{ -"rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n", -"children": [ - { - "children": [], - "code": null, - "level": "help", - "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", - "rendered": null, - "spans": [] - } -], -"code": { - "code": "E0277", - "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" -}, -"level": "error", -"message": "can't compare `{integer}` with `&str`", -"spans": [ - { - "byte_end": 155, - "byte_start": 153, - "column_end": 33, - "column_start": 31, - "expansion": { - "def_site_span": { - "byte_end": 940, - "byte_start": 0, - "column_end": 6, - "column_start": 1, - "expansion": null, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": false, - "label": null, - "line_end": 36, - "line_start": 1, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 35, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " match (& $ left, & $ right)" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 18, - "highlight_start": 1, - "text": " right: `{:?}`\"#," - }, - { - "highlight_end": 47, - "highlight_start": 1, - "text": " & * left_val, & * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 42, - "highlight_start": 1, - "text": " }) ; ($ left : expr, $ right : expr,) =>" - }, - { - "highlight_end": 49, - "highlight_start": 1, - "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" - }, - { - "highlight_end": 53, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 37, - "highlight_start": 1, - "text": " match (& ($ left), & ($ right))" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 22, - "highlight_start": 1, - "text": " right: `{:?}`: {}\"#," - }, - { - "highlight_end": 72, - "highlight_start": 1, - "text": " & * left_val, & * right_val, $ crate :: format_args !" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " ($ ($ arg) +))" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 6, - "highlight_start": 1, - "text": " }) ;" - } - ] - }, - "macro_decl_name": "assert_eq!", - "span": { - "byte_end": 38, - "byte_start": 16, - "column_end": 27, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": false, - "label": null, - "line_end": 2, - "line_start": 2, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 27, - "highlight_start": 5, - "text": " assert_eq!(1, \"love\");" - } - ] - } - }, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": true, - "label": "no implementation for `{integer} == &str`", - "line_end": 7, - "line_start": 7, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 33, - "highlight_start": 31, - "text": " if ! (* left_val == * right_val)" - } - ] - } -] -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_macro_compiler_error() { - let diag = parse_diagnostic( - r##"{ - "rendered": "error: Please register your known path in the path module\n --> crates/ra_hir_def/src/path.rs:265:9\n |\n265 | compile_error!(\"Please register your known path in the path module\")\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n | \n ::: crates/ra_hir_def/src/data.rs:80:16\n |\n80 | let path = path![std::future::Future];\n | -------------------------- in this macro invocation\n\n", - "children": [], - "code": null, - "level": "error", - "message": "Please register your known path in the path module", - "spans": [ - { - "byte_end": 8285, - "byte_start": 8217, - "column_end": 77, - "column_start": 9, - "expansion": { - "def_site_span": { - "byte_end": 8294, - "byte_start": 7858, - "column_end": 2, - "column_start": 1, - "expansion": null, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 267, - "line_start": 254, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 28, - "highlight_start": 1, - "text": "macro_rules! __known_path {" - }, - { - "highlight_end": 37, - "highlight_start": 1, - "text": " (std::iter::IntoIterator) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::result::Result) => {};" - }, - { - "highlight_end": 29, - "highlight_start": 1, - "text": " (std::ops::Range) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::ops::RangeFrom) => {};" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " (std::ops::RangeFull) => {};" - }, - { - "highlight_end": 31, - "highlight_start": 1, - "text": " (std::ops::RangeTo) => {};" - }, - { - "highlight_end": 40, - "highlight_start": 1, - "text": " (std::ops::RangeToInclusive) => {};" - }, - { - "highlight_end": 38, - "highlight_start": 1, - "text": " (std::ops::RangeInclusive) => {};" - }, - { - "highlight_end": 27, - "highlight_start": 1, - "text": " (std::ops::Try) => {};" - }, - { - "highlight_end": 22, - "highlight_start": 1, - "text": " ($path:path) => {" - }, - { - "highlight_end": 77, - "highlight_start": 1, - "text": " compile_error!(\"Please register your known path in the path module\")" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " };" - }, - { - "highlight_end": 2, - "highlight_start": 1, - "text": "}" - } - ] - }, - "macro_decl_name": "$crate::__known_path!", - "span": { - "byte_end": 8427, - "byte_start": 8385, - "column_end": 51, - "column_start": 9, - "expansion": { - "def_site_span": { - "byte_end": 8611, - "byte_start": 8312, - "column_end": 2, - "column_start": 1, - "expansion": null, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 277, - "line_start": 270, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 22, - "highlight_start": 1, - "text": "macro_rules! __path {" - }, - { - "highlight_end": 43, - "highlight_start": 1, - "text": " ($start:ident $(:: $seg:ident)*) => ({" - }, - { - "highlight_end": 51, - "highlight_start": 1, - "text": " $crate::__known_path!($start $(:: $seg)*);" - }, - { - "highlight_end": 87, - "highlight_start": 1, - "text": " $crate::path::ModPath::from_simple_segments($crate::path::PathKind::Abs, vec![" - }, - { - "highlight_end": 76, - "highlight_start": 1, - "text": " $crate::path::__name![$start], $($crate::path::__name![$seg],)*" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " ])" - }, - { - "highlight_end": 8, - "highlight_start": 1, - "text": " });" - }, - { - "highlight_end": 2, - "highlight_start": 1, - "text": "}" - } - ] - }, - "macro_decl_name": "path!", - "span": { - "byte_end": 2966, - "byte_start": 2940, - "column_end": 42, - "column_start": 16, - "expansion": null, - "file_name": "crates/ra_hir_def/src/data.rs", - "is_primary": false, - "label": null, - "line_end": 80, - "line_start": 80, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 42, - "highlight_start": 16, - "text": " let path = path![std::future::Future];" - } - ] - } - }, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": false, - "label": null, - "line_end": 272, - "line_start": 272, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 51, - "highlight_start": 9, - "text": " $crate::__known_path!($start $(:: $seg)*);" - } - ] - } - }, - "file_name": "crates/ra_hir_def/src/path.rs", - "is_primary": true, - "label": null, - "line_end": 265, - "line_start": 265, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 77, - "highlight_start": 9, - "text": " compile_error!(\"Please register your known path in the path module\")" - } - ] - } - ] -} - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} - -#[test] -#[cfg(not(windows))] -fn snap_multi_line_fix() { - let diag = parse_diagnostic( - r##"{ - "rendered": "warning: returning the result of a let binding from a block\n --> src/main.rs:4:5\n |\n3 | let a = (0..10).collect();\n | -------------------------- unnecessary let binding\n4 | a\n | ^\n |\n = note: `#[warn(clippy::let_and_return)]` on by default\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return\nhelp: return the expression directly\n |\n3 | \n4 | (0..10).collect()\n |\n\n", - "children": [ - { - "children": [], - "code": null, - "level": "note", - "message": "`#[warn(clippy::let_and_return)]` on by default", - "rendered": null, - "spans": [] - }, - { - "children": [], - "code": null, - "level": "help", - "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return", - "rendered": null, - "spans": [] - }, - { - "children": [], - "code": null, - "level": "help", - "message": "return the expression directly", - "rendered": null, - "spans": [ - { - "byte_end": 55, - "byte_start": 29, - "column_end": 31, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 3, - "line_start": 3, - "suggested_replacement": "", - "suggestion_applicability": "MachineApplicable", - "text": [ - { - "highlight_end": 31, - "highlight_start": 5, - "text": " let a = (0..10).collect();" - } - ] - }, - { - "byte_end": 61, - "byte_start": 60, - "column_end": 6, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 4, - "line_start": 4, - "suggested_replacement": "(0..10).collect()", - "suggestion_applicability": "MachineApplicable", - "text": [ - { - "highlight_end": 6, - "highlight_start": 5, - "text": " a" - } - ] - } - ] - } - ], - "code": { - "code": "clippy::let_and_return", - "explanation": null - }, - "level": "warning", - "message": "returning the result of a let binding from a block", - "spans": [ - { - "byte_end": 55, - "byte_start": 29, - "column_end": 31, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": false, - "label": "unnecessary let binding", - "line_end": 3, - "line_start": 3, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 31, - "highlight_start": 5, - "text": " let a = (0..10).collect();" - } - ] - }, - { - "byte_end": 61, - "byte_start": 60, - "column_end": 6, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": true, - "label": null, - "line_end": 4, - "line_start": 4, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 6, - "highlight_start": 5, - "text": " a" - } - ] - } - ] - } - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); - insta::assert_debug_snapshot!(diag); -} diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index d5efb6ab3c99..178485c9ee57 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -1,10 +1,9 @@ //! cargo_check provides the functionality needed to run `cargo check` or //! another compatible command (f.x. clippy) in a background thread and provide //! LSP diagnostics based on the output of the command. -mod conv; use std::{ - io::{self, BufRead, BufReader}, + io::{self, BufReader}, path::PathBuf, process::{Command, Stdio}, time::Instant, @@ -12,14 +11,6 @@ use std::{ use cargo_metadata::Message; use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; -use lsp_types::{ - CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, - WorkDoneProgressEnd, WorkDoneProgressReport, -}; - -use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; - -pub use crate::conv::url_from_path_with_drive_lowercasing; #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { @@ -61,10 +52,17 @@ pub enum CheckTask { ClearDiagnostics, /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec }, + AddDiagnostic { workspace_root: PathBuf, diagnostic: cargo_metadata::diagnostic::Diagnostic }, /// Request check progress notification to client - Status(WorkDoneProgress), + Status(Status), +} + +#[derive(Debug)] +pub enum Status { + Being, + Progress(String), + End, } pub enum CheckCommand { @@ -131,9 +129,7 @@ impl FlycheckThread { fn clean_previous_results(&self, task_send: &Sender) { task_send.send(CheckTask::ClearDiagnostics).unwrap(); - task_send - .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::End)).unwrap(); } fn should_recheck(&mut self) -> bool { @@ -155,52 +151,24 @@ impl FlycheckThread { fn handle_message(&self, msg: CheckEvent, task_send: &Sender) { match msg { CheckEvent::Begin => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { - title: "Running `cargo check`".to_string(), - cancellable: Some(false), - message: None, - percentage: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::Being)).unwrap(); } CheckEvent::End => { - task_send - .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { - message: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::End)).unwrap(); } CheckEvent::Msg(Message::CompilerArtifact(msg)) => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { - cancellable: Some(false), - message: Some(msg.target.name), - percentage: None, - }))) - .unwrap(); + task_send.send(CheckTask::Status(Status::Progress(msg.target.name))).unwrap(); } CheckEvent::Msg(Message::CompilerMessage(msg)) => { - let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root); - if map_result.is_empty() { - return; - } - - for MappedRustDiagnostic { location, diagnostic, fixes } in map_result { - let fixes = fixes - .into_iter() - .map(|fix| { - CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into() - }) - .collect(); - - task_send - .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes }) - .unwrap(); - } + task_send + .send(CheckTask::AddDiagnostic { + workspace_root: self.workspace_root.clone(), + diagnostic: msg.message, + }) + .unwrap(); } CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} @@ -271,11 +239,11 @@ impl FlycheckThread { } } -#[derive(Debug)] -pub struct DiagnosticWithFixes { - diagnostic: Diagnostic, - fixes: Vec, -} +// #[derive(Debug)] +// pub struct DiagnosticWithFixes { +// diagnostic: Diagnostic, +// fixes: Vec, +// } enum CheckEvent { Begin, @@ -300,15 +268,11 @@ fn run_cargo( // erroneus output. let stdout = BufReader::new(child.stdout.take().unwrap()); let mut read_at_least_one_message = false; - - for line in stdout.lines() { - let line = line?; - - let message = serde_json::from_str::(&line); + for message in cargo_metadata::Message::parse_stream(stdout) { let message = match message { Ok(message) => message, Err(err) => { - log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line); + log::error!("Invalid json from cargo check, ignoring ({})", err); continue; } }; diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 8c94f430a29d..21e705597192 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -29,6 +29,7 @@ rustc-hash = "1.1.0" serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.48" threadpool = "1.7.1" +cargo_metadata = "0.10.0" stdx = { path = "../stdx" } @@ -53,6 +54,7 @@ winapi = "0.3.8" [dev-dependencies] tempfile = "3.1.0" +insta = "0.16.0" test_utils = { path = "../test_utils" } [features] diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index e7924f0a30f7..4bdd45a7deb1 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs @@ -1,4 +1,5 @@ //! Book keeping for keeping diagnostics easily in sync with the client. +pub(crate) mod to_proto; use std::{collections::HashMap, sync::Arc}; diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_clippy_pass_by_ref.snap similarity index 98% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_clippy_pass_by_ref.snap index 4c9db0385b95..d7f9ec0494ae 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_clippy_pass_by_ref.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_handles_macro_location.snap similarity index 95% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_handles_macro_location.snap index 7cde4d86702f..a59faf254c7e 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_handles_macro_location.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_macro_compiler_error.snap similarity index 97% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_macro_compiler_error.snap index 1cc37e087fb1..3c78e7f36c9e 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_macro_compiler_error.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap similarity index 98% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap index 615ed837873e..076b3cf2730a 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_incompatible_type_for_trait.snap similarity index 95% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_incompatible_type_for_trait.snap index 0df0fce1882b..46d0c56d222d 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_incompatible_type_for_trait.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_mismatched_type.snap similarity index 95% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_mismatched_type.snap index 28ebcb3b30a6..4182929ba319 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_mismatched_type.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap similarity index 97% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap index 5e08732817f9..69138c15b278 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_wrong_number_of_parameters.snap similarity index 97% rename from crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap rename to crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_wrong_number_of_parameters.snap index e500d3cd6c13..f6ab05004bba 100644 --- a/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_wrong_number_of_parameters.snap @@ -1,5 +1,5 @@ --- -source: crates/ra_flycheck/src/conv/test.rs +source: crates/rust-analyzer/src/diagnostics/to_proto.rs expression: diag --- [ diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs new file mode 100644 index 000000000000..0c8108591286 --- /dev/null +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -0,0 +1,1408 @@ +//! This module provides the functionality needed to convert diagnostics from +//! `cargo check` json format to the LSP diagnostic format. +use cargo_metadata::diagnostic::{ + Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, + DiagnosticSpanMacroExpansion, +}; +use lsp_types::{ + CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, + Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, +}; +use std::{ + collections::HashMap, + fmt::Write, + path::{Component, Path, PathBuf, Prefix}, + str::FromStr, +}; + +/// Converts a Rust level string to a LSP severity +fn map_level_to_severity(val: DiagnosticLevel) -> Option { + match val { + DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), + DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), + DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), + DiagnosticLevel::Unknown => None, + } +} + +/// Check whether a file name is from macro invocation +fn is_from_macro(file_name: &str) -> bool { + file_name.starts_with('<') && file_name.ends_with('>') +} + +/// Converts a Rust macro span to a LSP location recursively +fn map_macro_span_to_location( + span_macro: &DiagnosticSpanMacroExpansion, + workspace_root: &PathBuf, +) -> Option { + if !is_from_macro(&span_macro.span.file_name) { + return Some(map_span_to_location(&span_macro.span, workspace_root)); + } + + if let Some(expansion) = &span_macro.span.expansion { + return map_macro_span_to_location(&expansion, workspace_root); + } + + None +} + +/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary +fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { + if span.expansion.is_some() { + let expansion = span.expansion.as_ref().unwrap(); + if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { + return macro_range; + } + } + + map_span_to_location_naive(span, workspace_root) +} + +/// Converts a Rust span to a LSP location +fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { + let mut file_name = workspace_root.clone(); + file_name.push(&span.file_name); + let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); + + let range = Range::new( + Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), + Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), + ); + + Location { uri, range } +} + +/// Converts a secondary Rust span to a LSP related information +/// +/// If the span is unlabelled this will return `None`. +fn map_secondary_span_to_related( + span: &DiagnosticSpan, + workspace_root: &PathBuf, +) -> Option { + if let Some(label) = &span.label { + let location = map_span_to_location(span, workspace_root); + Some(DiagnosticRelatedInformation { location, message: label.clone() }) + } else { + // Nothing to label this with + None + } +} + +/// Determines if diagnostic is related to unused code +fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" + | "unused_imports" | "unused_macros" | "unused_variables" => true, + _ => false, + } + } else { + false + } +} + +/// Determines if diagnostic is related to deprecated code +fn is_deprecated(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "deprecated" => true, + _ => false, + } + } else { + false + } +} + +enum MappedRustChildDiagnostic { + Related(DiagnosticRelatedInformation), + SuggestedFix(CodeAction), + MessageLine(String), +} + +fn map_rust_child_diagnostic( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> MappedRustChildDiagnostic { + let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); + if spans.is_empty() { + // `rustc` uses these spanless children as a way to print multi-line + // messages + return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); + } + + let mut edit_map: HashMap> = HashMap::new(); + for &span in &spans { + match (&span.suggestion_applicability, &span.suggested_replacement) { + (Some(Applicability::MachineApplicable), Some(suggested_replacement)) => { + let location = map_span_to_location(span, workspace_root); + let edit = TextEdit::new(location.range, suggested_replacement.clone()); + edit_map.entry(location.uri).or_default().push(edit); + } + _ => {} + } + } + + if !edit_map.is_empty() { + MappedRustChildDiagnostic::SuggestedFix(CodeAction { + title: rd.message.clone(), + kind: Some("quickfix".to_string()), + diagnostics: None, + edit: Some(WorkspaceEdit::new(edit_map)), + command: None, + is_preferred: None, + }) + } else { + MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { + location: map_span_to_location(spans[0], workspace_root), + message: rd.message.clone(), + }) + } +} + +#[derive(Debug)] +pub(crate) struct MappedRustDiagnostic { + pub location: Location, + pub diagnostic: Diagnostic, + pub fixes: Vec, +} + +/// Converts a Rust root diagnostic to LSP form +/// +/// This flattens the Rust diagnostic by: +/// +/// 1. Creating a LSP diagnostic with the root message and primary span. +/// 2. Adding any labelled secondary spans to `relatedInformation` +/// 3. Categorising child diagnostics as either `SuggestedFix`es, +/// `relatedInformation` or additional message lines. +/// +/// If the diagnostic has no primary span this will return `None` +pub(crate) fn map_rust_diagnostic_to_lsp( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> Vec { + let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); + if primary_spans.is_empty() { + return vec![]; + } + + let severity = map_level_to_severity(rd.level); + + let mut source = String::from("rustc"); + let mut code = rd.code.as_ref().map(|c| c.code.clone()); + if let Some(code_val) = &code { + // See if this is an RFC #2103 scoped lint (e.g. from Clippy) + let scoped_code: Vec<&str> = code_val.split("::").collect(); + if scoped_code.len() == 2 { + source = String::from(scoped_code[0]); + code = Some(String::from(scoped_code[1])); + } + } + + let mut needs_primary_span_label = true; + let mut related_information = vec![]; + let mut tags = vec![]; + + for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { + let related = map_secondary_span_to_related(secondary_span, workspace_root); + if let Some(related) = related { + related_information.push(related); + } + } + + let mut fixes = vec![]; + let mut message = rd.message.clone(); + for child in &rd.children { + let child = map_rust_child_diagnostic(&child, workspace_root); + match child { + MappedRustChildDiagnostic::Related(related) => related_information.push(related), + MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), + MappedRustChildDiagnostic::MessageLine(message_line) => { + write!(&mut message, "\n{}", message_line).unwrap(); + + // These secondary messages usually duplicate the content of the + // primary span label. + needs_primary_span_label = false; + } + } + } + + if is_unused_or_unnecessary(rd) { + tags.push(DiagnosticTag::Unnecessary); + } + + if is_deprecated(rd) { + tags.push(DiagnosticTag::Deprecated); + } + + primary_spans + .iter() + .map(|primary_span| { + let location = map_span_to_location(&primary_span, workspace_root); + + let mut message = message.clone(); + if needs_primary_span_label { + if let Some(primary_span_label) = &primary_span.label { + write!(&mut message, "\n{}", primary_span_label).unwrap(); + } + } + + // If error occurs from macro expansion, add related info pointing to + // where the error originated + if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { + let def_loc = map_span_to_location_naive(&primary_span, workspace_root); + related_information.push(DiagnosticRelatedInformation { + location: def_loc, + message: "Error originated from macro here".to_string(), + }); + } + + let diagnostic = Diagnostic { + range: location.range, + severity, + code: code.clone().map(NumberOrString::String), + source: Some(source.clone()), + message, + related_information: if !related_information.is_empty() { + Some(related_information.clone()) + } else { + None + }, + tags: if !tags.is_empty() { Some(tags.clone()) } else { None }, + }; + + MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() } + }) + .collect() +} + +/// Returns a `Url` object from a given path, will lowercase drive letters if present. +/// This will only happen when processing windows paths. +/// +/// When processing non-windows path, this is essentially the same as `Url::from_file_path`. +pub fn url_from_path_with_drive_lowercasing( + path: impl AsRef, +) -> Result> { + let component_has_windows_drive = path.as_ref().components().any(|comp| { + if let Component::Prefix(c) = comp { + match c.kind() { + Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true, + _ => return false, + } + } + false + }); + + // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. + if component_has_windows_drive { + let url_original = Url::from_file_path(&path) + .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; + + let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect(); + + // There is a drive partition, but we never found a colon. + // This should not happen, but in this case we just pass it through. + if drive_partition.len() == 1 { + return Ok(url_original); + } + + let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; + let url = Url::from_str(&joined).expect("This came from a valid `Url`"); + + Ok(url) + } else { + Ok(Url::from_file_path(&path) + .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // `Url` is not able to parse windows paths on unix machines. + #[test] + #[cfg(target_os = "windows")] + fn test_lowercase_drive_letter_with_drive() { + let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap(); + + assert_eq!(url.to_string(), "file:///c:/Test"); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_drive_without_colon_passthrough() { + let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap(); + + assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); + } + + #[cfg(not(windows))] + fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { + serde_json::from_str::(val).unwrap() + } + + #[test] + #[cfg(not(windows))] + fn snap_rustc_incompatible_type_for_trait() { + let diag = parse_diagnostic( + r##"{ + "message": "method `next` has an incompatible type for trait", + "code": { + "code": "E0053", + "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n" + }, + "level": "error", + "spans": [ + { + "file_name": "compiler/ty/list_iter.rs", + "byte_start": 1307, + "byte_end": 1350, + "line_start": 52, + "line_end": 52, + "column_start": 5, + "column_end": 48, + "is_primary": true, + "text": [ + { + "text": " fn next(&self) -> Option<&'list ty::Ref> {", + "highlight_start": 5, + "highlight_end": 48 + } + ], + "label": "types differ in mutability", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + } + ], + "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`\n\n" + } + "##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_rustc_unused_variable() { + let diag = parse_diagnostic( + r##"{ + "message": "unused variable: `foo`", + "code": { + "code": "unused_variables", + "explanation": null + }, + "level": "warning", + "spans": [ + { + "file_name": "driver/subcommand/repl.rs", + "byte_start": 9228, + "byte_end": 9231, + "line_start": 291, + "line_end": 291, + "column_start": 9, + "column_end": 12, + "is_primary": true, + "text": [ + { + "text": " let foo = 42;", + "highlight_start": 9, + "highlight_end": 12 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "#[warn(unused_variables)] on by default", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "consider prefixing with an underscore", + "code": null, + "level": "help", + "spans": [ + { + "file_name": "driver/subcommand/repl.rs", + "byte_start": 9228, + "byte_end": 9231, + "line_start": 291, + "line_end": 291, + "column_start": 9, + "column_end": 12, + "is_primary": true, + "text": [ + { + "text": " let foo = 42;", + "highlight_start": 9, + "highlight_end": 12 + } + ], + "label": null, + "suggested_replacement": "_foo", + "suggestion_applicability": "MachineApplicable", + "expansion": null + } + ], + "children": [], + "rendered": null + } + ], + "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n" + }"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_rustc_wrong_number_of_parameters() { + let diag = parse_diagnostic( + r##"{ + "message": "this function takes 2 parameters but 3 parameters were supplied", + "code": { + "code": "E0061", + "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n" + }, + "level": "error", + "spans": [ + { + "file_name": "compiler/ty/select.rs", + "byte_start": 8787, + "byte_end": 9241, + "line_start": 219, + "line_end": 231, + "column_start": 5, + "column_end": 6, + "is_primary": false, + "text": [ + { + "text": " pub fn add_evidence(", + "highlight_start": 5, + "highlight_end": 25 + }, + { + "text": " &mut self,", + "highlight_start": 1, + "highlight_end": 19 + }, + { + "text": " target_poly: &ty::Ref,", + "highlight_start": 1, + "highlight_end": 41 + }, + { + "text": " evidence_poly: &ty::Ref,", + "highlight_start": 1, + "highlight_end": 43 + }, + { + "text": " ) {", + "highlight_start": 1, + "highlight_end": 8 + }, + { + "text": " match target_poly {", + "highlight_start": 1, + "highlight_end": 28 + }, + { + "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),", + "highlight_start": 1, + "highlight_end": 81 + }, + { + "text": " ty::Ref::Fixed(target_ty) => {", + "highlight_start": 1, + "highlight_end": 43 + }, + { + "text": " let evidence_ty = evidence_poly.resolve_to_ty();", + "highlight_start": 1, + "highlight_end": 65 + }, + { + "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)", + "highlight_start": 1, + "highlight_end": 76 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 14 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 10 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 6 + } + ], + "label": "defined here", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + }, + { + "file_name": "compiler/ty/select.rs", + "byte_start": 4045, + "byte_end": 4057, + "line_start": 104, + "line_end": 104, + "column_start": 18, + "column_end": 30, + "is_primary": true, + "text": [ + { + "text": " self.add_evidence(target_fixed, evidence_fixed, false);", + "highlight_start": 18, + "highlight_end": 30 + } + ], + "label": "expected 2 parameters", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref,\n222 | | evidence_poly: &ty::Ref,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n" + }"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_clippy_pass_by_ref() { + let diag = parse_diagnostic( + r##"{ + "message": "this argument is passed by reference, but would be more efficient if passed by value", + "code": { + "code": "clippy::trivially_copy_pass_by_ref", + "explanation": null + }, + "level": "warning", + "spans": [ + { + "file_name": "compiler/mir/tagset.rs", + "byte_start": 941, + "byte_end": 946, + "line_start": 42, + "line_end": 42, + "column_start": 24, + "column_end": 29, + "is_primary": true, + "text": [ + { + "text": " pub fn is_disjoint(&self, other: Self) -> bool {", + "highlight_start": 24, + "highlight_end": 29 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "lint level defined here", + "code": null, + "level": "note", + "spans": [ + { + "file_name": "compiler/lib.rs", + "byte_start": 8, + "byte_end": 19, + "line_start": 1, + "line_end": 1, + "column_start": 9, + "column_end": 20, + "is_primary": true, + "text": [ + { + "text": "#![warn(clippy::all)]", + "highlight_start": 9, + "highlight_end": 20 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": null + }, + { + "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", + "code": null, + "level": "help", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "consider passing by value instead", + "code": null, + "level": "help", + "spans": [ + { + "file_name": "compiler/mir/tagset.rs", + "byte_start": 941, + "byte_end": 946, + "line_start": 42, + "line_end": 42, + "column_start": 24, + "column_end": 29, + "is_primary": true, + "text": [ + { + "text": " pub fn is_disjoint(&self, other: Self) -> bool {", + "highlight_start": 24, + "highlight_end": 29 + } + ], + "label": null, + "suggested_replacement": "self", + "suggestion_applicability": "Unspecified", + "expansion": null + } + ], + "children": [], + "rendered": null + } + ], + "rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n" + }"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_rustc_mismatched_type() { + let diag = parse_diagnostic( + r##"{ + "message": "mismatched types", + "code": { + "code": "E0308", + "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" + }, + "level": "error", + "spans": [ + { + "file_name": "runtime/compiler_support.rs", + "byte_start": 1589, + "byte_end": 1594, + "line_start": 48, + "line_end": 48, + "column_start": 65, + "column_end": 70, + "is_primary": true, + "text": [ + { + "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);", + "highlight_start": 65, + "highlight_end": 70 + } + ], + "label": "expected usize, found u32", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n" + }"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_handles_macro_location() { + let diag = parse_diagnostic( + r##"{ + "rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n", + "children": [ + { + "children": [], + "code": null, + "level": "help", + "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", + "rendered": null, + "spans": [] + } + ], + "code": { + "code": "E0277", + "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" + }, + "level": "error", + "message": "can't compare `{integer}` with `&str`", + "spans": [ + { + "byte_end": 155, + "byte_start": 153, + "column_end": 33, + "column_start": 31, + "expansion": { + "def_site_span": { + "byte_end": 940, + "byte_start": 0, + "column_end": 6, + "column_start": 1, + "expansion": null, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": false, + "label": null, + "line_end": 36, + "line_start": 1, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 35, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " match (& $ left, & $ right)" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 18, + "highlight_start": 1, + "text": " right: `{:?}`\"#," + }, + { + "highlight_end": 47, + "highlight_start": 1, + "text": " & * left_val, & * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 42, + "highlight_start": 1, + "text": " }) ; ($ left : expr, $ right : expr,) =>" + }, + { + "highlight_end": 49, + "highlight_start": 1, + "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" + }, + { + "highlight_end": 53, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 37, + "highlight_start": 1, + "text": " match (& ($ left), & ($ right))" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 22, + "highlight_start": 1, + "text": " right: `{:?}`: {}\"#," + }, + { + "highlight_end": 72, + "highlight_start": 1, + "text": " & * left_val, & * right_val, $ crate :: format_args !" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " ($ ($ arg) +))" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 6, + "highlight_start": 1, + "text": " }) ;" + } + ] + }, + "macro_decl_name": "assert_eq!", + "span": { + "byte_end": 38, + "byte_start": 16, + "column_end": 27, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": false, + "label": null, + "line_end": 2, + "line_start": 2, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 27, + "highlight_start": 5, + "text": " assert_eq!(1, \"love\");" + } + ] + } + }, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": true, + "label": "no implementation for `{integer} == &str`", + "line_end": 7, + "line_start": 7, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 33, + "highlight_start": 31, + "text": " if ! (* left_val == * right_val)" + } + ] + } + ] + }"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_macro_compiler_error() { + let diag = parse_diagnostic( + r##"{ + "rendered": "error: Please register your known path in the path module\n --> crates/ra_hir_def/src/path.rs:265:9\n |\n265 | compile_error!(\"Please register your known path in the path module\")\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n | \n ::: crates/ra_hir_def/src/data.rs:80:16\n |\n80 | let path = path![std::future::Future];\n | -------------------------- in this macro invocation\n\n", + "children": [], + "code": null, + "level": "error", + "message": "Please register your known path in the path module", + "spans": [ + { + "byte_end": 8285, + "byte_start": 8217, + "column_end": 77, + "column_start": 9, + "expansion": { + "def_site_span": { + "byte_end": 8294, + "byte_start": 7858, + "column_end": 2, + "column_start": 1, + "expansion": null, + "file_name": "crates/ra_hir_def/src/path.rs", + "is_primary": false, + "label": null, + "line_end": 267, + "line_start": 254, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 28, + "highlight_start": 1, + "text": "macro_rules! __known_path {" + }, + { + "highlight_end": 37, + "highlight_start": 1, + "text": " (std::iter::IntoIterator) => {};" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " (std::result::Result) => {};" + }, + { + "highlight_end": 29, + "highlight_start": 1, + "text": " (std::ops::Range) => {};" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " (std::ops::RangeFrom) => {};" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " (std::ops::RangeFull) => {};" + }, + { + "highlight_end": 31, + "highlight_start": 1, + "text": " (std::ops::RangeTo) => {};" + }, + { + "highlight_end": 40, + "highlight_start": 1, + "text": " (std::ops::RangeToInclusive) => {};" + }, + { + "highlight_end": 38, + "highlight_start": 1, + "text": " (std::ops::RangeInclusive) => {};" + }, + { + "highlight_end": 27, + "highlight_start": 1, + "text": " (std::ops::Try) => {};" + }, + { + "highlight_end": 22, + "highlight_start": 1, + "text": " ($path:path) => {" + }, + { + "highlight_end": 77, + "highlight_start": 1, + "text": " compile_error!(\"Please register your known path in the path module\")" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " };" + }, + { + "highlight_end": 2, + "highlight_start": 1, + "text": "}" + } + ] + }, + "macro_decl_name": "$crate::__known_path!", + "span": { + "byte_end": 8427, + "byte_start": 8385, + "column_end": 51, + "column_start": 9, + "expansion": { + "def_site_span": { + "byte_end": 8611, + "byte_start": 8312, + "column_end": 2, + "column_start": 1, + "expansion": null, + "file_name": "crates/ra_hir_def/src/path.rs", + "is_primary": false, + "label": null, + "line_end": 277, + "line_start": 270, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 22, + "highlight_start": 1, + "text": "macro_rules! __path {" + }, + { + "highlight_end": 43, + "highlight_start": 1, + "text": " ($start:ident $(:: $seg:ident)*) => ({" + }, + { + "highlight_end": 51, + "highlight_start": 1, + "text": " $crate::__known_path!($start $(:: $seg)*);" + }, + { + "highlight_end": 87, + "highlight_start": 1, + "text": " $crate::path::ModPath::from_simple_segments($crate::path::PathKind::Abs, vec![" + }, + { + "highlight_end": 76, + "highlight_start": 1, + "text": " $crate::path::__name![$start], $($crate::path::__name![$seg],)*" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " ])" + }, + { + "highlight_end": 8, + "highlight_start": 1, + "text": " });" + }, + { + "highlight_end": 2, + "highlight_start": 1, + "text": "}" + } + ] + }, + "macro_decl_name": "path!", + "span": { + "byte_end": 2966, + "byte_start": 2940, + "column_end": 42, + "column_start": 16, + "expansion": null, + "file_name": "crates/ra_hir_def/src/data.rs", + "is_primary": false, + "label": null, + "line_end": 80, + "line_start": 80, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 42, + "highlight_start": 16, + "text": " let path = path![std::future::Future];" + } + ] + } + }, + "file_name": "crates/ra_hir_def/src/path.rs", + "is_primary": false, + "label": null, + "line_end": 272, + "line_start": 272, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 51, + "highlight_start": 9, + "text": " $crate::__known_path!($start $(:: $seg)*);" + } + ] + } + }, + "file_name": "crates/ra_hir_def/src/path.rs", + "is_primary": true, + "label": null, + "line_end": 265, + "line_start": 265, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 77, + "highlight_start": 9, + "text": " compile_error!(\"Please register your known path in the path module\")" + } + ] + } + ] + } + "##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } + + #[test] + #[cfg(not(windows))] + fn snap_multi_line_fix() { + let diag = parse_diagnostic( + r##"{ + "rendered": "warning: returning the result of a let binding from a block\n --> src/main.rs:4:5\n |\n3 | let a = (0..10).collect();\n | -------------------------- unnecessary let binding\n4 | a\n | ^\n |\n = note: `#[warn(clippy::let_and_return)]` on by default\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return\nhelp: return the expression directly\n |\n3 | \n4 | (0..10).collect()\n |\n\n", + "children": [ + { + "children": [], + "code": null, + "level": "note", + "message": "`#[warn(clippy::let_and_return)]` on by default", + "rendered": null, + "spans": [] + }, + { + "children": [], + "code": null, + "level": "help", + "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return", + "rendered": null, + "spans": [] + }, + { + "children": [], + "code": null, + "level": "help", + "message": "return the expression directly", + "rendered": null, + "spans": [ + { + "byte_end": 55, + "byte_start": 29, + "column_end": 31, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": true, + "label": null, + "line_end": 3, + "line_start": 3, + "suggested_replacement": "", + "suggestion_applicability": "MachineApplicable", + "text": [ + { + "highlight_end": 31, + "highlight_start": 5, + "text": " let a = (0..10).collect();" + } + ] + }, + { + "byte_end": 61, + "byte_start": 60, + "column_end": 6, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": true, + "label": null, + "line_end": 4, + "line_start": 4, + "suggested_replacement": "(0..10).collect()", + "suggestion_applicability": "MachineApplicable", + "text": [ + { + "highlight_end": 6, + "highlight_start": 5, + "text": " a" + } + ] + } + ] + } + ], + "code": { + "code": "clippy::let_and_return", + "explanation": null + }, + "level": "warning", + "message": "returning the result of a let binding from a block", + "spans": [ + { + "byte_end": 55, + "byte_start": 29, + "column_end": 31, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": false, + "label": "unnecessary let binding", + "line_end": 3, + "line_start": 3, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 31, + "highlight_start": 5, + "text": " let a = (0..10).collect();" + } + ] + }, + { + "byte_end": 61, + "byte_start": 60, + "column_end": 6, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": true, + "label": null, + "line_end": 4, + "line_start": 4, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 6, + "highlight_start": 5, + "text": " a" + } + ] + } + ] + } + "##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + insta::assert_debug_snapshot!(diag); + } +} diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 13d305b97341..15e5bb354968 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -25,7 +25,7 @@ use lsp_types::{ WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, }; -use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; +use ra_flycheck::{CheckTask, Status}; use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; use ra_prof::profile; use ra_project_model::{PackageRoot, ProjectWorkspace}; @@ -37,7 +37,7 @@ use threadpool::ThreadPool; use crate::{ config::{Config, FilesWatcher}, - diagnostics::DiagnosticTask, + diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, from_proto, lsp_ext, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, @@ -736,22 +736,61 @@ fn on_check_task( task_sender.send(Task::Diagnostic(DiagnosticTask::ClearCheck))?; } - CheckTask::AddDiagnostic { url, diagnostic, fixes } => { - let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; - let file_id = match world_state.vfs.read().path2file(&path) { - Some(file) => FileId(file.0), - None => { - log::error!("File with cargo diagnostic not found in VFS: {}", path.display()); - return Ok(()); - } - }; + CheckTask::AddDiagnostic { workspace_root, diagnostic } => { + let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( + &diagnostic, + &workspace_root, + ); + for diag in diagnostics { + let path = diag + .location + .uri + .to_file_path() + .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; + let file_id = match world_state.vfs.read().path2file(&path) { + Some(file) => FileId(file.0), + None => { + log::error!( + "File with cargo diagnostic not found in VFS: {}", + path.display() + ); + return Ok(()); + } + }; - task_sender - .send(Task::Diagnostic(DiagnosticTask::AddCheck(file_id, diagnostic, fixes)))?; + task_sender.send(Task::Diagnostic(DiagnosticTask::AddCheck( + file_id, + diag.diagnostic, + diag.fixes.into_iter().map(|it| it.into()).collect(), + )))?; + } } - CheckTask::Status(progress) => { + CheckTask::Status(status) => { if world_state.config.client_caps.work_done_progress { + let progress = match status { + Status::Being => { + lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { + title: "Running `cargo check`".to_string(), + cancellable: Some(false), + message: None, + percentage: None, + }) + } + Status::Progress(target) => { + lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { + cancellable: Some(false), + message: Some(target), + percentage: None, + }) + } + Status::End => { + lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { + message: None, + }) + } + }; + let params = lsp_types::ProgressParams { token: lsp_types::ProgressToken::String( "rustAnalyzer/cargoWatcher".to_string(), diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs index 6333c15b2bba..367272925be5 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/world.rs @@ -11,7 +11,7 @@ use std::{ use crossbeam_channel::{unbounded, Receiver}; use lsp_types::Url; use parking_lot::RwLock; -use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig}; +use ra_flycheck::{Flycheck, FlycheckConfig}; use ra_ide::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, }; @@ -22,7 +22,9 @@ use stdx::format_to; use crate::{ config::Config, - diagnostics::{CheckFixes, DiagnosticCollection}, + diagnostics::{ + to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection, + }, main_loop::pending_requests::{CompletedRequest, LatestRequests}, vfs_glob::{Glob, RustPackageFilterBuilder}, LspError, Result, From f1a5c489fd0e5b0b29f6c6f2537baed379fee460 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 15 May 2020 01:58:39 +0200 Subject: [PATCH 2/3] Better structure --- Cargo.lock | 1 - crates/ra_flycheck/src/lib.rs | 13 ++++++------ crates/rust-analyzer/Cargo.toml | 1 - .../rust-analyzer/src/diagnostics/to_proto.rs | 20 +++++++++---------- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31fe89cdc03f..f94cea81438f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1376,7 +1376,6 @@ name = "rust-analyzer" version = "0.1.0" dependencies = [ "anyhow", - "cargo_metadata", "crossbeam-channel", "env_logger", "globset", diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 178485c9ee57..541179920bae 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -12,6 +12,11 @@ use std::{ use cargo_metadata::Message; use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; +pub use cargo_metadata::diagnostic::{ + Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, + DiagnosticSpanMacroExpansion, +}; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec }, @@ -52,7 +57,7 @@ pub enum CheckTask { ClearDiagnostics, /// Request adding a diagnostic with fixes included to a file - AddDiagnostic { workspace_root: PathBuf, diagnostic: cargo_metadata::diagnostic::Diagnostic }, + AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, /// Request check progress notification to client Status(Status), @@ -239,12 +244,6 @@ impl FlycheckThread { } } -// #[derive(Debug)] -// pub struct DiagnosticWithFixes { -// diagnostic: Diagnostic, -// fixes: Vec, -// } - enum CheckEvent { Begin, Msg(cargo_metadata::Message), diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 21e705597192..9b2d29b1d57c 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -29,7 +29,6 @@ rustc-hash = "1.1.0" serde = { version = "1.0.106", features = ["derive"] } serde_json = "1.0.48" threadpool = "1.7.1" -cargo_metadata = "0.10.0" stdx = { path = "../stdx" } diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 0c8108591286..5c8d86eb5e90 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -1,13 +1,5 @@ //! This module provides the functionality needed to convert diagnostics from //! `cargo check` json format to the LSP diagnostic format. -use cargo_metadata::diagnostic::{ - Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, - DiagnosticSpanMacroExpansion, -}; -use lsp_types::{ - CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, - Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, -}; use std::{ collections::HashMap, fmt::Write, @@ -15,6 +7,12 @@ use std::{ str::FromStr, }; +use lsp_types::{ + CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, + Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, +}; +use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; + /// Converts a Rust level string to a LSP severity fn map_level_to_severity(val: DiagnosticLevel) -> Option { match val { @@ -91,7 +89,7 @@ fn map_secondary_span_to_related( } /// Determines if diagnostic is related to unused code -fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { +fn is_unused_or_unnecessary(rd: &ra_flycheck::Diagnostic) -> bool { if let Some(code) = &rd.code { match code.code.as_str() { "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" @@ -122,7 +120,7 @@ enum MappedRustChildDiagnostic { } fn map_rust_child_diagnostic( - rd: &RustDiagnostic, + rd: &ra_flycheck::Diagnostic, workspace_root: &PathBuf, ) -> MappedRustChildDiagnostic { let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); @@ -179,7 +177,7 @@ pub(crate) struct MappedRustDiagnostic { /// /// If the diagnostic has no primary span this will return `None` pub(crate) fn map_rust_diagnostic_to_lsp( - rd: &RustDiagnostic, + rd: &ra_flycheck::Diagnostic, workspace_root: &PathBuf, ) -> Vec { let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); From 08027c307556c8500ca6e44c432a08f83d33d1c3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 15 May 2020 02:08:50 +0200 Subject: [PATCH 3/3] Cleanups --- crates/ra_flycheck/src/lib.rs | 3 +- .../rust-analyzer/src/diagnostics/to_proto.rs | 157 +++++++++--------- 2 files changed, 75 insertions(+), 85 deletions(-) diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 541179920bae..041e38a9ff4c 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -13,8 +13,7 @@ use cargo_metadata::Message; use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; pub use cargo_metadata::diagnostic::{ - Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, - DiagnosticSpanMacroExpansion, + Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, }; #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 5c8d86eb5e90..eabf4908ff16 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -2,8 +2,7 @@ //! `cargo check` json format to the LSP diagnostic format. use std::{ collections::HashMap, - fmt::Write, - path::{Component, Path, PathBuf, Prefix}, + path::{Component, Path, Prefix}, str::FromStr, }; @@ -12,17 +11,21 @@ use lsp_types::{ Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, }; use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; +use stdx::format_to; + +use crate::Result; /// Converts a Rust level string to a LSP severity fn map_level_to_severity(val: DiagnosticLevel) -> Option { - match val { - DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), - DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), - DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), - DiagnosticLevel::Unknown => None, - } + let res = match val { + DiagnosticLevel::Ice => DiagnosticSeverity::Error, + DiagnosticLevel::Error => DiagnosticSeverity::Error, + DiagnosticLevel::Warning => DiagnosticSeverity::Warning, + DiagnosticLevel::Note => DiagnosticSeverity::Information, + DiagnosticLevel::Help => DiagnosticSeverity::Hint, + DiagnosticLevel::Unknown => return None, + }; + Some(res) } /// Check whether a file name is from macro invocation @@ -33,7 +36,7 @@ fn is_from_macro(file_name: &str) -> bool { /// Converts a Rust macro span to a LSP location recursively fn map_macro_span_to_location( span_macro: &DiagnosticSpanMacroExpansion, - workspace_root: &PathBuf, + workspace_root: &Path, ) -> Option { if !is_from_macro(&span_macro.span.file_name) { return Some(map_span_to_location(&span_macro.span, workspace_root)); @@ -47,7 +50,7 @@ fn map_macro_span_to_location( } /// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary -fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { +fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Location { if span.expansion.is_some() { let expansion = span.expansion.as_ref().unwrap(); if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { @@ -59,11 +62,12 @@ fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Loca } /// Converts a Rust span to a LSP location -fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - let mut file_name = workspace_root.clone(); +fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { + let mut file_name = workspace_root.to_path_buf(); file_name.push(&span.file_name); let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); + // FIXME: this doesn't handle UTF16 offsets correctly let range = Range::new( Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), @@ -77,39 +81,30 @@ fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) - /// If the span is unlabelled this will return `None`. fn map_secondary_span_to_related( span: &DiagnosticSpan, - workspace_root: &PathBuf, + workspace_root: &Path, ) -> Option { - if let Some(label) = &span.label { - let location = map_span_to_location(span, workspace_root); - Some(DiagnosticRelatedInformation { location, message: label.clone() }) - } else { - // Nothing to label this with - None - } + let message = span.label.clone()?; + let location = map_span_to_location(span, workspace_root); + Some(DiagnosticRelatedInformation { location, message }) } /// Determines if diagnostic is related to unused code fn is_unused_or_unnecessary(rd: &ra_flycheck::Diagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { + match &rd.code { + Some(code) => match code.code.as_str() { "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" | "unused_imports" | "unused_macros" | "unused_variables" => true, _ => false, - } - } else { - false + }, + None => false, } } /// Determines if diagnostic is related to deprecated code -fn is_deprecated(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "deprecated" => true, - _ => false, - } - } else { - false +fn is_deprecated(rd: &ra_flycheck::Diagnostic) -> bool { + match &rd.code { + Some(code) => code.code.as_str() == "deprecated", + None => false, } } @@ -121,7 +116,7 @@ enum MappedRustChildDiagnostic { fn map_rust_child_diagnostic( rd: &ra_flycheck::Diagnostic, - workspace_root: &PathBuf, + workspace_root: &Path, ) -> MappedRustChildDiagnostic { let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); if spans.is_empty() { @@ -142,7 +137,12 @@ fn map_rust_child_diagnostic( } } - if !edit_map.is_empty() { + if edit_map.is_empty() { + MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { + location: map_span_to_location(spans[0], workspace_root), + message: rd.message.clone(), + }) + } else { MappedRustChildDiagnostic::SuggestedFix(CodeAction { title: rd.message.clone(), kind: Some("quickfix".to_string()), @@ -151,11 +151,6 @@ fn map_rust_child_diagnostic( command: None, is_preferred: None, }) - } else { - MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { - location: map_span_to_location(spans[0], workspace_root), - message: rd.message.clone(), - }) } } @@ -178,11 +173,11 @@ pub(crate) struct MappedRustDiagnostic { /// If the diagnostic has no primary span this will return `None` pub(crate) fn map_rust_diagnostic_to_lsp( rd: &ra_flycheck::Diagnostic, - workspace_root: &PathBuf, + workspace_root: &Path, ) -> Vec { let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); if primary_spans.is_empty() { - return vec![]; + return Vec::new(); } let severity = map_level_to_severity(rd.level); @@ -199,8 +194,8 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } let mut needs_primary_span_label = true; - let mut related_information = vec![]; - let mut tags = vec![]; + let mut related_information = Vec::new(); + let mut tags = Vec::new(); for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { let related = map_secondary_span_to_related(secondary_span, workspace_root); @@ -209,7 +204,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } } - let mut fixes = vec![]; + let mut fixes = Vec::new(); let mut message = rd.message.clone(); for child in &rd.children { let child = map_rust_child_diagnostic(&child, workspace_root); @@ -217,7 +212,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( MappedRustChildDiagnostic::Related(related) => related_information.push(related), MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), MappedRustChildDiagnostic::MessageLine(message_line) => { - write!(&mut message, "\n{}", message_line).unwrap(); + format_to!(message, "\n{}", message_line); // These secondary messages usually duplicate the content of the // primary span label. @@ -242,7 +237,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( let mut message = message.clone(); if needs_primary_span_label { if let Some(primary_span_label) = &primary_span.label { - write!(&mut message, "\n{}", primary_span_label).unwrap(); + format_to!(message, "\n{}", primary_span_label); } } @@ -262,12 +257,12 @@ pub(crate) fn map_rust_diagnostic_to_lsp( code: code.clone().map(NumberOrString::String), source: Some(source.clone()), message, - related_information: if !related_information.is_empty() { - Some(related_information.clone()) - } else { + related_information: if related_information.is_empty() { None + } else { + Some(related_information.clone()) }, - tags: if !tags.is_empty() { Some(tags.clone()) } else { None }, + tags: if tags.is_empty() { None } else { Some(tags.clone()) }, }; MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() } @@ -279,21 +274,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp( /// This will only happen when processing windows paths. /// /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. -pub fn url_from_path_with_drive_lowercasing( - path: impl AsRef, -) -> Result> { +pub fn url_from_path_with_drive_lowercasing(path: impl AsRef) -> Result { let component_has_windows_drive = path.as_ref().components().any(|comp| { if let Component::Prefix(c) = comp { - match c.kind() { - Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true, - _ => return false, - } + return matches!(c.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)); } false }); // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. - if component_has_windows_drive { + let res = if component_has_windows_drive { let url_original = Url::from_file_path(&path) .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; @@ -308,11 +298,12 @@ pub fn url_from_path_with_drive_lowercasing( let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; let url = Url::from_str(&joined).expect("This came from a valid `Url`"); - Ok(url) + url } else { - Ok(Url::from_file_path(&path) - .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?) - } + Url::from_file_path(&path) + .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))? + }; + Ok(res) } #[cfg(test)] @@ -337,8 +328,8 @@ mod tests { } #[cfg(not(windows))] - fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { - serde_json::from_str::(val).unwrap() + fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic { + serde_json::from_str::(val).unwrap() } #[test] @@ -390,8 +381,8 @@ mod tests { "##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -473,8 +464,8 @@ mod tests { }"##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -598,8 +589,8 @@ mod tests { }"##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -719,8 +710,8 @@ mod tests { }"##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -763,8 +754,8 @@ mod tests { }"##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -1035,8 +1026,8 @@ mod tests { }"##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -1265,8 +1256,8 @@ mod tests { "##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } @@ -1399,8 +1390,8 @@ mod tests { "##, ); - let workspace_root = PathBuf::from("/test/"); - let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root); + let workspace_root = Path::new("/test/"); + let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); insta::assert_debug_snapshot!(diag); } }