-
Notifications
You must be signed in to change notification settings - Fork 210
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
Use Cargo's JSON diagnostics for suggestions #767
Conversation
Should we leave the cargo message in |
I just wanted to swing by to let you know that I've seen this PR, but I'm not sure when I'll have time to give it the review focus it deserves. I'm super excited about this change, however! Random thoughts that may or may not be useful or even relevant (as I haven't even looked at the implementation)... We'll want to (as best we can!) preserve the structure of the API response/requests. e.g. we might want to add a new structured field for the compiler response, instead of repurposing the existing fields. Sadly, people use the existing APIs and assume they will be stable, even though almost no one has ever asked. 😢
Yeah, I think this is the ultimate end goal. Obviously it's more of a PITA, but would allow removing the custom regex-based error / warning formatting as well. Maybe this would also allow splitting out Cargo's output from the programs stderr/stdout as well.
I'd expect that as long as we are using the |
So should we preserve (at least to our best) the Current response example{
"success": false,
"stdout": "",
"stderr": " Compiling playground v0.0.1 (/playground)\nerror[E0433]: failed to resolve: use of undeclared type `HashMap`\n --> src/main.rs:3:13\n |\n3 | let a = HashMap::new();\n | ^^^^^^^ not found in this scope\n |\nhelp: consider importing one of these items\n |\n1 | use hashbrown::HashMap;\n |\n1 | use std::collections::HashMap;\n |\n\nFor more information about this error, try `rustc --explain E0433`.\nerror: could not compile `playground` due to previous error\n"
} Proposed response example{
"success": false,
"stdout": "",
"stderr": " Compiling playground v0.0.1 (/playground)\nerror[E0433]: failed to resolve: use of undeclared type `HashMap`\n --> src/main.rs:3:13\n |\n3 | let a = HashMap::new();\n | ^^^^^^^ not found in this scope\n |\nhelp: consider importing one of these items\n |\n1 | use hashbrown::HashMap;\n |\n1 | use std::collections::HashMap;\n |\n\nFor more information about this error, try `rustc --explain E0433`.\nerror: could not compile `playground` due to previous error\n",
"actions": [
{
"link": {
"startline": 9,
"endline": 9,
"startcol": 4,
"endcol": 27,
"target": "stderr"
},
"solution": {
"startline": 1,
"endline": 1,
"startcol": 1,
"endcol": 1,
"replacement": "use hashbrown::HashMap;"
}
},
{
"link": {
"startline": 9,
"endline": 9,
"startcol": 4,
"endcol": 34,
"target": "stderr"
},
"solution": {
"startline": 1,
"endline": 1,
"startcol": 1,
"endcol": 1,
"replacement": "use std::collections::HashMap;"
}
}
]
} It's definitely going to be more complex than the implementation this PR proposed (especially for front-end). An advantage for such response is that we might be able to remove almost all regex (not only the "import" one) since highlighting and error location (currently handled here) can be extracted directly from Cargo JSON output and passed to front-end using the new field in HTTP JSON response. |
Yep, that's what I meant by
😉 |
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.
This is an encouraging start, thanks! I added comments, but many of them are around the piece of code that's most likely to change as we push more structured information to the frontend, so don't feel like you need to adhere to it now.
fn parse_diagnostic( | ||
diagnostic: cargo_metadata::diagnostic::Diagnostic, | ||
should_output_message: bool, | ||
) -> String { |
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.
Instead of building a brand new string here, it'd be a bit more efficient to accept a &mut String
argument and append to it directly.
if let Some(rendered_msg) = diagnostic.rendered { | ||
diagnostic_string.push_str(&rendered_msg); | ||
} else { | ||
diagnostic_string.push_str(&diagnostic.message); | ||
} |
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.
Not a required changed, but this could be something along the lines of this (maybe with some .as_ref
or as_deref
calls added):
if let Some(rendered_msg) = diagnostic.rendered { | |
diagnostic_string.push_str(&rendered_msg); | |
} else { | |
diagnostic_string.push_str(&diagnostic.message); | |
} | |
let x = diagnostic.rendered.or(&diagnostic.message) | |
diagnostic_string.push_str(x); |
} | ||
|
||
for span in diagnostic.spans { | ||
if span.file_name != "src/lib.rs" && span.file_name != "src/main.rs" { |
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.
My brain is slow at parsing this. Maybe we could use
if span.file_name != "src/lib.rs" && span.file_name != "src/main.rs" { | |
if !matches!("src/lib.rs" | "src/main.rs", span.file_name) { |
diagnostic_string.push_str(&format!( | ||
"\n[[Line {} Col {} - Line {} Col {}: {}]]", | ||
span.line_start, span.column_start, span.line_end, span.column_end, label | ||
)); |
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.
Instead of creating the intermediate String
via format!
, you can format directly to the &mut String
using write!
. See also How can I append a formatted string to an existing String?
|
||
match message { | ||
cargo_metadata::Message::TextLine(line) => { | ||
composed_stdout_string.push_str(&(line + "\n")) |
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.
The same idea for using write!
can be applied here.
let mut composed_stderr_string = String::new(); | ||
let mut composed_stdout_string = String::new(); | ||
|
||
let metadata_stream = cargo_metadata::Message::parse_stream(&output[..]); |
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'd have expected you could elide the [..]
:
let metadata_stream = cargo_metadata::Message::parse_stream(&output[..]); | |
let metadata_stream = cargo_metadata::Message::parse_stream(&output); |
@@ -78,6 +78,8 @@ pub enum Error { | |||
UnableToReadOutput { source: io::Error }, | |||
#[snafu(display("Unable to read crate information: {}", source))] | |||
UnableToParseCrateInformation { source: ::serde_json::Error }, | |||
#[snafu(display("Unable to parse cargo output: {}", source))] |
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.
#[snafu(display("Unable to parse cargo output: {}", source))] | |
#[snafu(display("Unable to parse Cargo output: {}", source))] |
if (startline == endline) { | ||
state_lines[startline] = state_lines[startline].substring(0, startcol) + | ||
state_lines[startline].substring(endcol); | ||
} else { | ||
if (state_lines.length > startline) { | ||
state_lines[startline] = state_lines[startline].substring(0, startcol); | ||
} | ||
if (state_lines.length > endline) { | ||
state_lines[endline] = state_lines[endline].substring(endcol); | ||
} | ||
if (endline - startline > 1) { | ||
state_lines.splice(startline + 1, endline - startline - 1); | ||
} | ||
} | ||
state_lines[startline] = state_lines[startline].substring(0, startcol) + | ||
action.suggestion + state_lines[startline].substring(startcol); | ||
state = state_lines.join('\n'); | ||
return state; |
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.
This logic is tricky enough that I can't just glance at it and feel very confident about what it's doing. It'd be helpful to extract out the core logic here and write some unit tests to exercise the various if
s. Something like this sketch:
case ActionType.ApplySuggestion:
const startline = action.startline - 1;
const endline = action.endline - 1;
const startcol = action.startcol - 1;
const endcol = action.endcol - 1;
return someLogic(state);
I've been poking around and writing custom diagnostic renderer for the last few days and it's way more complicated than I expected. In order to not break existing API, we basically need to port this (or somehow intercept this call to manipulate JSON output) so that it can not only output formatted message but also export "actions" linked to part of the message. A port would be at least 1000+ lines and include some dirty hacks (mainly because we need to parse cargo's metadata back to rustc's one). Therefore, I think continue to use pre-rendered output and do some matching would be a better idea, like this (pseudo-code) fn add_suggestion_actions(pre_rendered: &str, diagnostic: cargo_metadata::diagnostic::Diagnostic, actions: &mut Vec<Action>) {
if let Some(position) = pre_rendered.find(diagnostic.suggested_replacement) {
actions.push(Action(position, ActionType::Replacement(diagnostic.suggested_replacement)));
}
}
fn add_filejump_actions(pre_rendered: &str, diagnostic: cargo_metadata::diagnostic::Diagnostic, actions: &mut Vec<Actions>) {
let file_loc = format!("{}:{}:{}", diagnostic.file_name, diagnostic.line_start, diagnostic.column_start);
if let Some(position) = pre_rendered.find(file_loc) {
actions.push(Action(position, ActionType::Jumpto(diagnostic.line_start, diagnostic.column_start)));
}
} |
Fix #688 .
Uses JSON message format to communicate with
cargo
when building and running. This allows suggestions to be prompted correctly.Example
Currently
New output
Possible TO-DOs
cargo_metadata
crate has been used. Not clear if the library can work with different cargo versions, or we need to do some kind of versioning. As far as I tested it works for all three variants at least for now.