rust-playground icon indicating copy to clipboard operation
rust-playground copied to clipboard

Use Cargo's JSON diagnostics for suggestions

Open hafeoz opened this issue 3 years ago • 5 comments

Fix #688 .

Uses JSON message format to communicate with cargo when building and running. This allows suggestions to be prompted correctly.

Example

Currently

a

New output

b

Possible TO-DOs

  • [ ] Any mode other than "building" and "running" is still using human-readable mode since I'm not sure how are they different from the normal cargo output.
  • [ ] 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.
  • [ ] No multi-line diagnostics have been tested. I wasn't able to find or think of any fixes with more than one line.
  • [ ] The standard error output's timeline is not correct. Currently, all cargo's output is located at the bottom of the standard error output, regardless of the time arrangement between outputs. Related cargo comment
  • [ ] Better prompt. Maybe discard the "rendered_message" and render the diagnostics message ourselves? This way we can have suggestions in message clickable, instead of having a list of suggestions under the message, which might be confusing.

hafeoz avatar Jan 11 '22 22:01 hafeoz

Should we leave the cargo message in stdout instead of moving it to stderr? This way the timeline will not be disrupted.

hafeoz avatar Jan 13 '22 02:01 hafeoz

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. 😢

Maybe discard the "rendered_message" and render the diagnostics message ourselves? This way we can have suggestions in message clickable, instead of having a list of suggestions under the message, which might be confusing.

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.

Not clear if [cargo_metadata] can work with different cargo versions

I'd expect that as long as we are using the v1 of the output (our whatever is the current version) that it should be compatible.

shepmaster avatar Jan 18 '22 16:01 shepmaster

We'll want to (as best we can!) preserve the structure of the API response/requests

So should we preserve (at least to our best) the stdout and stderr field in the JSON response for URL /execute? If that's the case, then we might need to add a new field to the response (like actions):

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.

hafeoz avatar Jan 20 '22 11:01 hafeoz

An advantage for such response is that we might be able to remove almost all regex (not only the "import" one)

Yep, that's what I meant by

but would allow removing the custom regex-based error / warning formatting as well

😉

shepmaster avatar Jan 20 '22 15:01 shepmaster

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)));
    }
}

hafeoz avatar Jan 23 '22 06:01 hafeoz