Skip to content
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

Support tools other than Cargo for rust-analyzer.check.overrideCommand #18460

Open
otiv-willem-vanhulle opened this issue Nov 1, 2024 · 17 comments
Assignees
Labels
C-feature Category: feature request

Comments

@otiv-willem-vanhulle
Copy link

otiv-willem-vanhulle commented Nov 1, 2024

I would like to use Rust Analyzer to show hints in my editor of choice.

I know that I can override the command used to feed data to Rust Analyzer.

{
  "rust-analyzer.check.overrideCommand": [
    "cargo",
    "check",
    "--message-format=json"
  ]
}

The problem is that I am working in a Bazel repository where there is no Cargo. The command that comes close is

bazel build --output_groups=+clippy_checks //...

but it produces no JSON output.

I have created a minimal example in https://github.com/otiv-willem-vanhulle/bazel-rust-check

The bazel rules_rust project https://github.com/bazelbuild/rules_rust does not offer a solution either.

@otiv-willem-vanhulle otiv-willem-vanhulle added the C-feature Category: feature request label Nov 1, 2024
@alibektas
Copy link
Member

Could you maybe share the output ( if it has a reasonable size) that you want to see in the editor? To do what you are looking for is not very straightforward. We first need to implement a reader that can make sense of the said output and turn it into something that resembles cargo check --message-format=json.

@alibektas
Copy link
Member

I haven't really deeply looked into the issue. We are rewriting our cargo check infra at the moment. I can look into this after that.

Did you read this ?

@otiv-willem-vanhulle
Copy link
Author

otiv-willem-vanhulle commented Nov 1, 2024

I haven't really deeply looked into the issue. We are rewriting our cargo check infra at the moment. I can look into this after that.

Did you read this ?

Yes I have read into that, but it doesn't explain which command to run to get the right JSON output for Rust analyzer.

The output that I would like to see is red underlined text beneath symbols that do not exist. Right now, the editor experience in Bazel is very bad because Rust analyzer does not cooperate with Bazel. Missing identifers are not marked. I have to run the compiler to see mistakes in my code.

I cannot find any documentation online about how to obtain the output that comes from cargo clippy without using cargo clippy. I have searched for special options to the clippy tool offered by https://bazelbuild.github.io/rules_rust/rust_clippy.html#rust_clippy.

I managed to get some output from clippy-driver by passing it the rustc option error-format=json but the format does not seem to contain file names compared to the output of cargo clippy --output-format=json

{"$message_type":"diagnostic","message":"cannot find type `usizeadsf` in this scope","code":{"code":"E0412","explanation":"A used type name is not in scope.\n\nErroneous code examples:\n\n```compile_fail,E0412\nimpl Something {} // error: type name `Something` is not in scope\n\n// or:\n\ntrait Foo {\n    fn bar(N); // error: type name `N` is not in scope\n}\n\n// or:\n\nfn foo(x: T) {} // type name `T` is not in scope\n```\n\nTo fix this error, please verify you didn't misspell the type name, you did\ndeclare it or imported it into the scope. Examples:\n\n```\nstruct Something;\n\nimpl Something {} // ok!\n\n// or:\n\ntrait Foo {\n    type N;\n\n    fn bar(_: Self::N); // ok!\n}\n\n// or:\n\nfn foo<T>(x: T) {} // ok!\n```\n\nAnother case that causes this error is when a type is imported into a parent\nmodule. To fix this, you can follow the suggestion and use File directly or\n`use super::File;` which will import the types from the parent namespace. An\nexample that causes this error is below:\n\n```compile_fail,E0412\nuse std::fs::File;\n\nmod foo {\n    fn some_function(f: File) {}\n}\n```\n\n```\nuse std::fs::File;\n\nmod foo {\n    // either\n    use super::File;\n    // or\n    // use std::fs::File;\n    fn foo(f: File) {}\n}\n# fn main() {} // don't insert it for us; that'll break imports\n```\n"},"level":"error","spans":[{"file_name":"ring/ring.rs","byte_start":37,"byte_end":46,"line_start":1,"line_end":1,"column_start":38,"column_end":47,"is_primary":true,"text":[{"text":"pub struct RingBuffer<A, const SIZE: usizeadsf> {","highlight_start":38,"highlight_end":47}],"label":"not found in this scope","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":"error[E0412]: cannot find type `usizeadsf` in this scope\n --> ring/ring.rs:1:38\n  |\n1 | pub struct RingBuffer<A, const SIZE: usizeadsf> {\n  |                                      ^^^^^^^^^ not found in this scope\n\n"}
{"$message_type":"diagnostic","message":"unused variable: `clippy`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"ring/ring.rs","byte_start":1993,"byte_end":1999,"line_start":68,"line_end":68,"column_start":13,"column_end":19,"is_primary":true,"text":[{"text":"            clippy => None,","highlight_start":13,"highlight_end":19}],"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":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"ring/ring.rs","byte_start":1993,"byte_end":1999,"line_start":68,"line_end":68,"column_start":13,"column_end":19,"is_primary":true,"text":[{"text":"            clippy => None,","highlight_start":13,"highlight_end":19}],"label":null,"suggested_replacement":"_clippy","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `clippy`\n  --> ring/ring.rs:68:13\n   |\n68 |             clippy => None,\n   |             ^^^^^^ help: if this is intentional, prefix it with an underscore: `_clippy`\n   |\n   = note: `#[warn(unused_variables)]` on by default\n\n"}
{"$message_type":"diagnostic","message":"aborting due to 1 previous error; 1 warning emitted","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to 1 previous error; 1 warning emitted\n\n"}

Compared to

    Checking cargo v0.1.0 (/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo)
{"reason":"compiler-message","package_id":"path+file:///home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo#0.1.0","manifest_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo","src_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"error[E0412]: cannot find type `usizeadsf` in this scope\n --> src/lib.rs:1:38\n  |\n1 | pub struct RingBuffer<A, const SIZE: usizeadsf> {\n  |                                      ^^^^^^^^^ not found in this scope\n\n","$message_type":"diagnostic","children":[],"code":{"code":"E0412","explanation":"A used type name is not in scope.\n\nErroneous code examples:\n\n```compile_fail,E0412\nimpl Something {} // error: type name `Something` is not in scope\n\n// or:\n\ntrait Foo {\n    fn bar(N); // error: type name `N` is not in scope\n}\n\n// or:\n\nfn foo(x: T) {} // type name `T` is not in scope\n```\n\nTo fix this error, please verify you didn't misspell the type name, you did\ndeclare it or imported it into the scope. Examples:\n\n```\nstruct Something;\n\nimpl Something {} // ok!\n\n// or:\n\ntrait Foo {\n    type N;\n\n    fn bar(_: Self::N); // ok!\n}\n\n// or:\n\nfn foo<T>(x: T) {} // ok!\n```\n\nAnother case that causes this error is when a type is imported into a parent\nmodule. To fix this, you can follow the suggestion and use File directly or\n`use super::File;` which will import the types from the parent namespace. An\nexample that causes this error is below:\n\n```compile_fail,E0412\nuse std::fs::File;\n\nmod foo {\n    fn some_function(f: File) {}\n}\n```\n\n```\nuse std::fs::File;\n\nmod foo {\n    // either\n    use super::File;\n    // or\n    // use std::fs::File;\n    fn foo(f: File) {}\n}\n# fn main() {} // don't insert it for us; that'll break imports\n```\n"},"level":"error","message":"cannot find type `usizeadsf` in this scope","spans":[{"byte_end":46,"byte_start":37,"column_end":47,"column_start":38,"expansion":null,"file_name":"src/lib.rs","is_primary":true,"label":"not found in this scope","line_end":1,"line_start":1,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":47,"highlight_start":38,"text":"pub struct RingBuffer<A, const SIZE: usizeadsf> {"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo#0.1.0","manifest_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo","src_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"warning: unused variable: `clippy`\n  --> src/lib.rs:68:13\n   |\n68 |             clippy => None,\n   |             ^^^^^^ help: if this is intentional, prefix it with an underscore: `_clippy`\n   |\n   = note: `#[warn(unused_variables)]` on by default\n\n","$message_type":"diagnostic","children":[{"children":[],"code":null,"level":"note","message":"`#[warn(unused_variables)]` on by default","rendered":null,"spans":[]},{"children":[],"code":null,"level":"help","message":"if this is intentional, prefix it with an underscore","rendered":null,"spans":[{"byte_end":1999,"byte_start":1993,"column_end":19,"column_start":13,"expansion":null,"file_name":"src/lib.rs","is_primary":true,"label":null,"line_end":68,"line_start":68,"suggested_replacement":"_clippy","suggestion_applicability":"MaybeIncorrect","text":[{"highlight_end":19,"highlight_start":13,"text":"            clippy => None,"}]}]}],"code":{"code":"unused_variables","explanation":null},"level":"warning","message":"unused variable: `clippy`","spans":[{"byte_end":1999,"byte_start":1993,"column_end":19,"column_start":13,"expansion":null,"file_name":"src/lib.rs","is_primary":true,"label":null,"line_end":68,"line_start":68,"suggested_replacement":null,"suggestion_applicability":null,"text":[{"highlight_end":19,"highlight_start":13,"text":"            clippy => None,"}]}]}}
{"reason":"compiler-message","package_id":"path+file:///home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo#0.1.0","manifest_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo","src_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"error: aborting due to 1 previous error; 1 warning emitted\n\n","$message_type":"diagnostic","children":[],"code":null,"level":"error","message":"aborting due to 1 previous error; 1 warning emitted","spans":[]}}
{"reason":"compiler-message","package_id":"path+file:///home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo#0.1.0","manifest_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo","src_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"message":{"rendered":"For more information about this error, try `rustc --explain E0412`.\n","$message_type":"diagnostic","children":[],"code":null,"level":"failure-note","message":"For more information about this error, try `rustc --explain E0412`.","spans":[]}}
error: could not compile `cargo` (lib) due to 2 previous errors; 1 warning emitted
{"reason":"build-finished","success":false}

this contains clearly less information

@alibektas
Copy link
Member

@rustbot claim

@darichey
Copy link
Contributor

darichey commented Nov 2, 2024

I don't think there's anything for rust-analyzer to do here besides commit to and document the schema expected by check.overrideCommand (which is https://github.com/oli-obk/cargo_metadata/blob/main/src/diagnostic.rs#L136). Besides better docs, rust-analyzer already does support tools other than Cargo, the tool just needs to be able to output that json which isn't unreasonable since it's just what rustc outputs.

@otiv-willem-vanhulle
Copy link
Author

@darichey Okay, do you think it is possible to convert the first JSON output into an output format like the second without cargo?

It seems like there are some cargo specific fields in that JSON such as manifest which I don't have.

More precisely, the part

"package_id":"path+file:///home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo#0.1.0","manifest_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo","src_path":"/home/wvhulle/Documents/GitHub/debug_rust_analyzer/cargo/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true}

@darichey
Copy link
Contributor

darichey commented Nov 2, 2024

You don't need those, just the Diagnostic fields, so the first JSON output already matches the schema

@otiv-willem-vanhulle
Copy link
Author

@darichey I have tried using that output for rust analyser but it doesn't work. Are you sure I don't have to wrap the output in something else so it becomes more like the second JSON schema?

@darichey
Copy link
Contributor

darichey commented Nov 2, 2024

"rust-analyzer.check.overrideCommand": [
    "bazel",
    "build",
    "--@rules_rust//:error_format=json",
    "ring"
]

This works for me. Obviously you may want to replace "ring" with something involving $saved_file

If `$saved_file` is part of the command, rust-analyzer will pass
the absolute path of the saved file to the provided command. This is
intended to be used with non-Cargo build systems.
Note that `$saved_file` is experimental and may be removed in the future.

or maybe with something that just builds the whole "workspace". I'm not familiar enough with Bazel to say what that might look like.

@otiv-willem-vanhulle
Copy link
Author

otiv-willem-vanhulle commented Nov 2, 2024

"rust-analyzer.check.overrideCommand": [
    "bazel",
    "build",
    "--@rules_rust//:error_format=json",
    "ring"
]

This works for me. Obviously you may want to replace "ring" with something involving $saved_file

rust-analyzer/docs/user/generated_config.adoc

Lines 241 to 244 in d1fbfc6
If $saved_file is part of the command, rust-analyzer will pass
the absolute path of the saved file to the provided command. This is
intended to be used with non-Cargo build systems.
Note that $saved_file is experimental and may be removed in the future.

or maybe with something that just builds the whole "workspace". I'm not familiar enough with Bazel to say what that might look like.

I think your overrideCommand is invalid. Bazel does not work like that for me. Have you tried to execute it in https://github.com/otiv-willem-vanhulle/bazel-rust-check? Bazel expects a Build target, not a file.

So it would look like this:

  "rust-analyzer.check.overrideCommand": [
    "bazel",
    "build",
    "--@rules_rust//:error_format=json",
    "//..."
  ]

Another issue I found with Rust analyzer, is that there is no way to see whether the overrideCommand executed or not. So it is hard to debug this.

@otiv-willem-vanhulle
Copy link
Author

It seems like there should be a way to go from the output of

bazel build --output_groups=+clippy_checks  --@rules_rust//:error_format=json //...

to the output of

cargo clippy --message-format=json

@otiv-willem-vanhulle
Copy link
Author

otiv-willem-vanhulle commented Nov 2, 2024

@darichey i have written a conversion script to match the output from cargo but that doesn't show anything in the log of the rust analyser language server. When I change override command to a non existing command I get an error when running flycheck. Running it with the output of Bazel converted to JSON format expected does not show anything. The schema I am using is the one you mentioned.

@otiv-willem-vanhulle
Copy link
Author

For some reason the cargo output contains $message_type":"diagnostic" but this is not present in the serialization by Serde of a diagnostics message. It has to be added manually after building a Message as in the crate you mentioned @darichey. It means rust analyzer accepts a different schema.

@darichey
Copy link
Contributor

darichey commented Nov 2, 2024

No, it's not needed. Did you try the command I gave? It works for me. ring is the target, bazel will infer the full name.

@otiv-willem-vanhulle
Copy link
Author

No your command doesn't work. I ran it. Why does it work for you?The Bazel output I get is JSON that does not contain a particular field that the output of Cargo Clippy contains, see my earlier message. After manually adding this field, it works. It would really be helpful if Rust analyzer gave some feedback on whether the output of the command could be parsed or not. Without its just guessing how it should look by comparing with Cargo Clippy output.

@darichey
Copy link
Contributor

darichey commented Nov 2, 2024

I don't know. All I did is clone your repo, generate a rust-project.json with bazel run @rules_rust//tools/rust_analyzer:gen_rust_project, set check.overrideCommand as above, and it just works
Image

If the command failed, it will print debug info to Rust Analyzer Language Server output in vscode.

@otiv-willem-vanhulle
Copy link
Author

@darichey Which version of VS Code, Rust Analyzer and Bazel are you using?

For me, no command output is printed when a command is provided that produces JSON in the wrong format. It just silently fails and ignores it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature Category: feature request
Projects
None yet
Development

No branches or pull requests

3 participants