-
Notifications
You must be signed in to change notification settings - Fork 6
Report Which Plugin a Diagnostic came from #784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f298fca
00f863d
70180fa
3dd9f6f
7f0081b
0a099ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,10 +50,12 @@ impl CompilationState { | |
| /// which may run identical validation. If multiple diagnostics are _exact_ duplicates, only the first will be | ||
| /// present in the returned [`Vec`]. | ||
| pub fn get_annotated_diagnostics(&self, options: &SliceOptions) -> Vec<AnnotatedDiagnostic> { | ||
| let mut annotated_diagnostics = Vec::new(); | ||
| let mut annotated_diagnostics = Vec::<AnnotatedDiagnostic>::new(); | ||
| for diagnostic in &*self.diagnostics { | ||
| let converted = crate::diagnostics::convert_diagnostic(diagnostic, options, &self.ast, &self.files); | ||
| if !annotated_diagnostics.contains(&converted) { | ||
| if let Some(original) = annotated_diagnostics.iter_mut().find(|d| **d == converted) { | ||
| original.reported_by.extend(converted.reported_by); | ||
|
InsertCreativityHere marked this conversation as resolved.
|
||
| } else { | ||
| annotated_diagnostics.push(converted); | ||
|
Comment on lines
+56
to
59
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously, we completely threw away duplicate diagnostics. |
||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,9 @@ impl<'a> DiagnosticEmitter<'a> { | |
| // Emit the message with the prefix. | ||
| writeln!(self.output, "{prefix}: {}", console::style(&diagnostic.message).bold())?; | ||
|
|
||
| // Emit what the diagnostic was reported by. | ||
| writeln!(self.output, " Reported by: [{}]", diagnostic.reported_by.join(", "))?; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the |
||
|
|
||
| // If the diagnostic contains a snippet of the offending code, display it. | ||
| if let Some(snippet) = &diagnostic.snippet { | ||
| self.emit_snippet(snippet)?; | ||
|
|
@@ -109,12 +112,13 @@ impl<'a> DiagnosticEmitter<'a> { | |
| }; | ||
|
|
||
| let mut serializer = serde_json::Serializer::new(&mut *self.output); | ||
| let mut state = serializer.serialize_struct("Diagnostic", 5)?; | ||
| let mut state = serializer.serialize_struct("Diagnostic", 6)?; | ||
| state.serialize_field("message", &diagnostic.message)?; | ||
| state.serialize_field("severity", severity)?; | ||
| state.serialize_field("snippet", &diagnostic.snippet)?; | ||
| state.serialize_field("notes", &diagnostic.notes)?; | ||
| state.serialize_field("error_code", &diagnostic.code)?; | ||
| state.serialize_field("reported_by", &diagnostic.reported_by)?; | ||
| state.end()?; | ||
| writeln!(self.output)?; // Separate each diagnostic by a newline character. | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,13 +9,26 @@ use serde::Serialize; | |
|
|
||
| /// An annotated version of a [`Diagnostic`], whose [`DiagnosticLevel`] has been computed (taking into account any | ||
| /// 'allow' attributes or command-line flags), and that has pre-extracted text snippets to display alongside messages. | ||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| #[derive(Clone, Debug, Eq)] | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We implement |
||
| pub struct AnnotatedDiagnostic { | ||
| pub message: String, | ||
| pub level: DiagnosticLevel, | ||
| pub code: String, | ||
| pub snippet: Option<Snippet>, | ||
| pub notes: Vec<AnnotatedNote>, | ||
| pub reported_by: Vec<String>, | ||
| } | ||
|
|
||
| impl PartialEq for AnnotatedDiagnostic { | ||
| fn eq(&self, other: &Self) -> bool { | ||
| // Check every field for equality except for 'reported_by'. | ||
| // Who reported the diagnostic isn't an intrinsic property of the diagnostic itself. | ||
| self.message == other.message | ||
| && self.level == other.level | ||
| && self.code == other.code | ||
| && self.snippet == other.snippet | ||
| && self.notes == other.notes | ||
| } | ||
| } | ||
|
|
||
| #[derive(Serialize, Clone, Debug, Eq, PartialEq)] | ||
|
|
@@ -42,12 +55,21 @@ pub fn convert_diagnostic( | |
| snippet: get_snippet(&n.span, files), | ||
| }); | ||
|
|
||
| // If the diagnostic was reported by a plugin, we just use the filename of the plugin (not its entire path). | ||
| fn get_plugin_file_stem(plugin_path: &str) -> Option<&str> { | ||
| std::path::Path::new(plugin_path).file_stem()?.to_str() | ||
| } | ||
| let reported_by = diagnostic.plugin.as_deref().map_or("slicec", |plugin_path| { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went back and forth on this, but I decided that in the case where the diagnostic is issued by It also makes the implementation cleaner and keep the error format 100% consistent, instead of being a little different for plugin diagnostics vs those about bad syntax for example.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that The normal Therefore |
||
| get_plugin_file_stem(plugin_path).unwrap_or(plugin_path) | ||
| }); | ||
|
InsertCreativityHere marked this conversation as resolved.
|
||
|
|
||
| AnnotatedDiagnostic { | ||
| message: diagnostic.message(), | ||
| level: get_diagnostic_level_for(diagnostic, options, ast, files), | ||
| code: diagnostic.code().to_owned(), | ||
| snippet: get_snippet(&diagnostic.span, files), | ||
| notes: notes.collect(), | ||
| reported_by: vec![reported_by.to_owned()], | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,9 @@ pub struct Diagnostic { | |
|
|
||
| /// Any additional information that should be reported alongside this diagnostic's main message. | ||
| pub notes: Vec<Note>, | ||
|
|
||
| /// The plugin that reported this diagnostic, or `None` if this diagnostic was reported by 'slicec' itself. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use the term |
||
| pub plugin: Option<String>, | ||
| } | ||
|
|
||
| impl Diagnostic { | ||
|
|
@@ -29,6 +32,7 @@ impl Diagnostic { | |
| span: None, | ||
| scope: None, | ||
| notes: Vec::new(), | ||
| plugin: None, | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -274,8 +274,11 @@ fn main() -> ExitCode { | |
| } | ||
| } | ||
|
|
||
| // Store generator's diagnostics for later emission. | ||
| diagnostics.extend(generator_diagnostics.into_inner()); | ||
| // Store generator's diagnostics for later emission, after setting the plugin that reported them. | ||
| for mut generator_diagnostic in generator_diagnostics.into_inner() { | ||
| generator_diagnostic.plugin = Some(generator.path.clone()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where we actually set |
||
| diagnostics.push(generator_diagnostic); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,7 +69,7 @@ pub fn convert_diagnostic(diagnostic: Diagnostic, ast: &Ast, files: &[GrammarSli | |
| let (span, scope) = convert_source(diagnostic.source.as_deref(), ast, files, output); | ||
|
|
||
| // Construct and push the converted 'slicec' `Diagnostic`. | ||
| let converted_diagnostic = slicec::diagnostics::Diagnostic { kind, span, scope, notes }; | ||
| let converted_diagnostic = slicec::diagnostics::Diagnostic { kind, span, scope, notes, plugin: None }; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We construct the diagnostic in one-shot here, so had to update it to include the new field. Rust doesn't have the idea of default field values like other languages. |
||
| output.push(converted_diagnostic); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,9 +41,9 @@ mod output { | |
|
|
||
| // Assert | ||
| let expected = concat!( | ||
| r#"{"message":"incorrect doc comment: comment has a 'param' tag for 'x', but operation 'op' has no parameter with that name","severity":"warning","snippet":{"span":{"start":{"row":5,"col":17},"end":{"row":5,"col":25},"file":"string-0"},"text":" |\n5 | /// @param x: this is an x\n | --------\n |"},"notes":[],"error_code":"IncorrectDocComment"}"#, | ||
| r#"{"message":"incorrect doc comment: comment has a 'param' tag for 'x', but operation 'op' has no parameter with that name","severity":"warning","snippet":{"span":{"start":{"row":5,"col":17},"end":{"row":5,"col":25},"file":"string-0"},"text":" |\n5 | /// @param x: this is an x\n | --------\n |"},"notes":[],"error_code":"IncorrectDocComment","reported_by":["slicec"]}"#, | ||
| "\n", | ||
| r#"{"message":"invalid enum 'E': enums must contain at least one enumerator","severity":"error","snippet":{"span":{"start":{"row":9,"col":9},"end":{"row":9,"col":15},"file":"string-0"},"text":" |\n9 | enum E : int8 {}\n | ------\n |"},"notes":[],"error_code":"E008"}"#, | ||
| r#"{"message":"invalid enum 'E': enums must contain at least one enumerator","severity":"error","snippet":{"span":{"start":{"row":9,"col":9},"end":{"row":9,"col":15},"file":"string-0"},"text":" |\n9 | enum E : int8 {}\n | ------\n |"},"notes":[],"error_code":"E008","reported_by":["slicec"]}"#, | ||
| "\n", | ||
| ); | ||
| assert_eq!(expected, String::from_utf8(output).unwrap()); | ||
|
|
@@ -87,12 +87,14 @@ mod output { | |
| // Assert | ||
| let expected = "\ | ||
| warning [IncorrectDocComment]: incorrect doc comment: comment has a 'param' tag for 'x', but operation 'op1' has no parameter with that name | ||
| Reported by: [slicec] | ||
| --> string-0:5:17 | ||
| | | ||
| 5 | /// @param x: this is an x | ||
| | -------- | ||
| | | ||
| error [E016]: invalid tag on member 'x': tagged members must be optional | ||
| Reported by: [slicec] | ||
| --> string-0:8:17 | ||
| | | ||
| 8 | op2(tag(1) | ||
|
|
@@ -103,6 +105,7 @@ error [E016]: invalid tag on member 'x': tagged members must be optional | |
| | ------------------------- | ||
| | | ||
| error [E008]: invalid enum 'E': enums must contain at least one enumerator | ||
| Reported by: [slicec] | ||
| --> string-0:14:9 | ||
| | | ||
| 14 | enum E : int8 {} | ||
|
|
@@ -114,29 +117,46 @@ error [E008]: invalid enum 'E': enums must contain at least one enumerator | |
| } | ||
|
|
||
| #[test] | ||
| fn duplicate_diagnostics_are_filtered_out() { | ||
| fn duplicate_diagnostics_are_merged_together() { | ||
| // Arrange | ||
|
|
||
| // All of these diagnostics are equal. | ||
| let diagnostic1_1 = Diagnostic::from_lint(Lint::Other { | ||
| // | ||
| let mut diagnostic1_1 = Diagnostic::from_lint(Lint::Other { | ||
| message: "This is a test".to_owned(), | ||
| }); | ||
| let diagnostic1_2 = Diagnostic::from_lint(Lint::Other { | ||
| diagnostic1_1.plugin = Some("foo".to_owned()); | ||
|
|
||
| let mut diagnostic1_2 = Diagnostic::from_lint(Lint::Other { | ||
| message: "This is a test".to_owned(), | ||
| }); | ||
| let diagnostic1_3 = Diagnostic::from_lint(Lint::Other { | ||
| diagnostic1_2.plugin = Some("/path/bar.exe".to_owned()); | ||
|
|
||
| let mut diagnostic1_3 = Diagnostic::from_lint(Lint::Other { | ||
| message: "This is a test".to_owned(), | ||
| }); | ||
| diagnostic1_3.plugin = None; // should map to 'slicec' when converted. | ||
|
|
||
| // This diagnostic is unique, there should be no other diagnostics equal to it. | ||
| let diagnostic2_1 = Diagnostic::from_lint(Lint::Other { | ||
| // | ||
| let mut diagnostic2_1 = Diagnostic::from_lint(Lint::Other { | ||
| message: "This is also a test".to_owned(), | ||
| }); | ||
| diagnostic2_1.plugin = Some("/path/bar.exe".to_owned()); | ||
|
|
||
| // These 2 diagnostics are equal. | ||
| let diagnostic3_1 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty); | ||
| let diagnostic3_2 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty); | ||
| // | ||
| let mut diagnostic3_1 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty); | ||
| diagnostic3_1.plugin = None; // should map to 'slicec' when converted. | ||
|
|
||
| let mut diagnostic3_2 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty); | ||
| diagnostic3_2.plugin = Some("foo".to_owned()); | ||
|
|
||
| // This diagnostic should not be equal to diagnostic 3, since this one has a note. | ||
| let diagnostic4_1 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty) | ||
| // | ||
| let mut diagnostic4_1 = Diagnostic::from_error(Error::CompactStructCannotBeEmpty) | ||
| .add_note("This diagnostic is different because it has a note", None); | ||
| diagnostic4_1.plugin = Some("foo".to_owned()); | ||
|
|
||
| // Manually set the diagnostics in our compilation state. | ||
| let mut state = CompilationState::create(); | ||
|
|
@@ -156,9 +176,13 @@ error [E008]: invalid enum 'E': enums must contain at least one enumerator | |
| // Assert | ||
| assert_eq!(converted_diagnostics.len(), 4); | ||
| assert_eq!(converted_diagnostics[0].message, "This is a test"); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check that the |
||
| assert_eq!(converted_diagnostics[0].reported_by, ["foo", "bar", "slicec"]); | ||
| assert_eq!(converted_diagnostics[1].message, "This is also a test"); | ||
| assert_eq!(converted_diagnostics[1].reported_by, ["bar"]); | ||
| assert_eq!(converted_diagnostics[2].message, "compact structs must be non-empty"); | ||
| assert_eq!(converted_diagnostics[2].reported_by, ["slicec", "foo"]); | ||
| assert_eq!(converted_diagnostics[3].message, "compact structs must be non-empty"); | ||
| assert_eq!(converted_diagnostics[3].reported_by, ["foo"]); | ||
|
|
||
| let (warnings, errors) = DiagnosticEmitter::get_totals(&converted_diagnostics); | ||
| assert_eq!(warnings, 2); | ||
|
|
@@ -230,7 +254,7 @@ error [E008]: invalid enum 'E': enums must contain at least one enumerator | |
|
|
||
| // Assert: Only one of the two lints should be allowed. | ||
| let expected = concat!( | ||
| r#"{"message":"incorrect doc comment: comment has a 'param' tag for 'x', but operation 'op' has no parameter with that name","severity":"warning","snippet":{"span":{"start":{"row":6,"col":21},"end":{"row":6,"col":29},"file":"string-0"},"text":" |\n6 | /// @param x: this is an x\n | --------\n |"},"notes":[],"error_code":"IncorrectDocComment"}"#, | ||
| r#"{"message":"incorrect doc comment: comment has a 'param' tag for 'x', but operation 'op' has no parameter with that name","severity":"warning","snippet":{"span":{"start":{"row":6,"col":21},"end":{"row":6,"col":29},"file":"string-0"},"text":" |\n6 | /// @param x: this is an x\n | --------\n |"},"notes":[],"error_code":"IncorrectDocComment","reported_by":["slicec"]}"#, | ||
| "\n", | ||
| ); | ||
| assert_eq!(expected, String::from_utf8(output).unwrap()); | ||
|
|
@@ -259,6 +283,7 @@ error [E008]: invalid enum 'E': enums must contain at least one enumerator | |
| // Assert | ||
| let expected = "\ | ||
| error [E008]: invalid enum 'E': enums must contain at least one enumerator | ||
| Reported by: [slicec] | ||
| --> string-0:2:4 | ||
| | | ||
| 2 | enum | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea why, but the compiler insists on putting a type-hint here now.
It seems unnecessary to me, but who am I to argue with the compiler...