Skip to content

Latest commit

 

History

History
200 lines (132 loc) · 10.3 KB

README.md

File metadata and controls

200 lines (132 loc) · 10.3 KB

bevy_lint

bevy_lint is a custom linter for the Bevy game engine, similar to Clippy.

Warning

This is an unofficial community project, hacked upon by the Bevy CLI working group until it is eventually upstreamed into the main Bevy Engine organization. Pardon our rough edges, and please consider submitting an issue if you run into trouble!

Installation

bevy_lint depends on a pinned nightly version of Rust with the rustc-dev Rustup component. This is because bevy_lint uses internal rustc crates that can only be imported with the permanently-unstable rustc_private feature. You can refer to the compatibility table to see which version of the linter requires which toolchain.

You can install the toolchain with:

rustup toolchain install $TOOLCHAIN_VERSION \
    --component rustc-dev \
    --component llvm-tools-preview

For example, you would replace $TOOLCHAIN_VERSION with nightly-2024-11-14 if you were installing bevy_lint 0.1.0, based on the compatibility table. Please be aware that you must keep this toolchain installed for bevy_lint to function1.

Once you have the toolchain installed, you can compile and install bevy_lint through cargo:

rustup run $TOOLCHAIN_VERSION cargo install \    
    --git https://github.com/TheBevyFlock/bevy_cli.git \
    --tag $TAG \
    --locked \
    bevy_lint

Make sure to replace $TOOLCHAIN_VERSION and $TAG in the above command. The tag for a specific release can be found in the releases tab. For example, the tag for 0.1.0 is lint-v0.1.0.

Usage

bevy_lint has the same API as the cargo check command:

bevy_lint --help

If you have the Bevy CLI installed, the linter is also available through the lint subcommand:

bevy lint --help

Note

bevy_lint checks your code with the nightly toolchain it was installed with, meaning you do have access to unstable features when it is called. This is best used when detecting bevy_lint.

Toggling Lints in Cargo.toml

You can set the default level for lints in a Cargo.toml using the [package.metadata.bevy_lint] table:

[package.metadata.bevy_lint]
missing_reflect = "warn"
panicking_methods = { level = "forbid" }

You can configure lints for an entire workspace by using [workspace.metadata.bevy_lint] in the root Cargo.toml instead:

[workspace.metadata.bevy_lint]
pedantic = "warn"

Crate lint configuration is merged with workspace lint configuration, with crate lint configuration taking priority.

Note that unlike with Cargo's [lints] table, the priority field is not supported. Furthermore, if you wish to use #[allow(...)] and related attributes inside your code for Bevy-specific lints, please see Toggling Lints in Code.

Detecting bevy_lint

The linter passes --cfg bevy_lint when it checks your code, allowing you to detect it:

// Conditionally include this function only when `bevy_lint` is used.
#[cfg(bevy_lint)]
fn foo() {
    // ...
}

// Conditionally add an attribute only when `bevy_lint` is used.
#[cfg_attr(bevy_lint, ...)]
struct Foo;

If you use this, you may also need to register bevy_lint as a valid cfg flag in your Cargo.toml:

[lints.rust]
unexpected_cfg = { level = "warn", check-cfg = ["cfg(bevy_lint)"] }

Registering bevy as a Tool

When you run bevy_lint on a project, rustc knows an exact list of all bevy:: lints registered. With this it can detect that bevy::missing_reflect is valid and bevy::uh_oh isn't, and emit a corresponding warning.

When you run normal cargo check, however, it does not know about any bevy:: lints. In order to avoid erroring on all usages of bevy::, but to still provide good diagnostics on typos, the #![register_tool(...)] attribute was introduced.

// Note that this is nightly-only. We'll get to that in a second!
#![register_tool(bevy)]

Using #![register_tool(bevy)] tells the compiler that bevy is a valid name in attributes, even if it does not know what bevy is.2 When cargo check now runs over a project with #[warn(bevy::lint_name)], it will simply skip it instead of emitting an error. (But running bevy_lint will still detect and check this attribute as normal.)

If you wish to refer to a bevy lint at all in your code (usually to toggle it), you must add #![register_tool(bevy)] to each crate root. Unfortunately, #![register_tool(...)] is currently unstable, meaning you need to add #![feature(register_tool)] to your code as well. This isn't an issue if you detect when bevy_lint is enabled, since it is guaranteed to check your code using nightly Rust.

// When `bevy_lint` is used, enable the `register_tool` feature and register `bevy` as a tool.
#![cfg_attr(bevy_lint, feature(register_tool), register_tool(bevy))]

Tip

If your project already uses nightly Rust, you can forego the #[cfg_attr(bevy_lint, ...)] attributes and write #![feature(register_tool)] and #![register_tool(bevy)] directly! Cool!

Toggling Lints in Code

It is possible to set lint levels on a case-by-case basis inside your code, but it requires a few more steps than setting the levels for the entire crate in Cargo.toml. First, you must register bevy as a tool. Not doing so will cause #[allow(bevy::lint_name)] and related attributes to fail to compile.

Once bevy is registered, you can toggle lints throughout your code, as long as they too are behind #[cfg_attr(bevy_lint, ...)]:

#![cfg_attr(bevy_lint, feature(register_tool), register_tool(bevy))]

// Enable pedantic lints, which are off by default.
#![cfg_attr(bevy_lint, warn(bevy::pedantic))]

// Deny methods of `World` in this system that can panic when a non-panicking alternative exists.
#[cfg_attr(bevy_lint, deny(bevy::panicking_world_methods))]
fn my_critical_system(world: &mut World) {
    // ...
}

There are several other ways to toggle lints, although some have varying levels of support:

Method Support Additional Information
[package.metadata.bevy_lint] See Toggling Lints in Cargo.toml.
#[allow(...)] and related Must be behind #[cfg_attr(bevy_lint, ...)] on stable Rust.
[lints.bevy] in Cargo.toml ⚠️ Nightly only because #[register_tool(bevy)] must always be enabled.
[workspace.lints.bevy] in Cargo.toml ⚠️ Nightly only (same as [lints.bevy]) and prints a warning each time cargo is executed.
RUSTFLAGS="-A bevy::lint" RUSTFLAGS applies to dependencies, but they do not have #[register_tool(bevy)].

Compatibility

bevy_lint Version Rust Version Rustup Toolchain Bevy Version
0.2.0-dev 1.84.0 nightly-2025-01-09 0.15
0.1.0 1.84.0 nightly-2024-11-14 0.14

The Rust version in the above table specifies what version of the Rust language can be compiled with bevy_lint. Code written for a later version of Rust may not compile. (This is not usually an issue, though, because bevy_lint's Rust version is kept 1 to 2 releases ahead of stable Rust.)

The Rustup toolchain specifies which toolchain must be installed in order for bevy_lint to be installed and used. Please see the installation section for more info.

The Bevy version is a range of Bevy versions that bevy_lint has been tested with and is guaranteed to work. Newer or older releases may not be linted correctly and may cause the linter to crash. (If this does happen for you, please consider submitting a bug report!)

License

The Bevy Linter is licensed under either of

at your option.

Contributing

Please see CONTRIBUTING.md for the CLI for more information! There is also a linter-specific contributing guide in the docs folder.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Footnotes

  1. bevy_lint imports internal rustc libraries in order to hook into the compiler process. These crates are stored in a dynamic library that is installed with the rustc-dev component and loaded by bevy_lint at runtime. Uninstalling the nightly toolchain would remove this dynamic library, causing bevy_lint to fail.

  2. If you've ever used #[rustfmt::skip] in your code, this is how rustc avoids erroring on it. However unlike the bevy namespace, rustfmt is registered automatically without a need for #![register_tool(rustfmt)] due to it being an official tool.