# Bevy CLI Linter :::warning **Warning** This document is outdated. For the latest information, please see the repository at [TheBevyFlock/bevy_cli](https://github.com/TheBevyFlock/bevy_cli). ::: See [the MVP](/cCHAfbtaSviU_MDnbNHKxg) for motivation behind the Bevy CLI. The prototype repository can be found at [TheBevyFlock/bevy_cli](https://github.com/TheBevyFlock/bevy_cli). ## Goal The goal is to write a linter, like `cargo check` and [Clippy](https://doc.rust-lang.org/stable/clippy/), that checks projects that use the [Bevy game engine](https://bevyengine.org) for best practices, potential footguns, and style. The aim is for the MVP of this linter to be released with Bevy 0.15, around November / December of 2024. [BD103](https://github.com/BD103) intends to lead this effort, though others are welcome to join! ## How it works - Expose the `bevy lint` command in the main CLI. - Export the `bevy_lint_driver` binary, installed alongside the rest of the CLI. - When `bevy lint` is called, it will call `RUSTC_WORKSPACE_WRAPPER=path/to/bevy_lint_driver cargo check`. - This instructs Cargo to pass all arguments to `bevy_lint_driver` instead of the default `rustc`. ([Cargo Environmental Variables]) - `bevy_lint_driver` will use [`rustc_driver::RunCompiler`] to run the compiler with a custom [`Callbacks`] struct. - The custom callback will set the [`Config::register_lints`] callback with one that registers Bevy-specific lints in the [`LintStore`]. - `rustc` will then call these lints alongside its builtin ones, displaying the results to the caller. [Cargo Environmental Variables]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads [`rustc_driver::RunCompiler`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/struct.RunCompiler.html [`Callbacks`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/trait.Callbacks.html [`Config::register_lints`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_interface/interface/struct.Config.html#structfield.register_lints [`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/context/struct.LintStore.html ## Philosophy Lints should be: - Useful, even if not to everyone - _Extensively_ documented (both to users and contributors) - Categorized - Configurable, but with sensible defaults - Actionable (with `cargo fix` support) ## Categories Rust supports organizing lints into categories that can be toggled in bulk. The planned categories introduced by this linter are: - Correctness (Deny) - Outright wrong / useless code - Carefully picked, no false positives - Suspicious (Warn) - Strange code that is likely wrong - Like Correctness, but may include false positives - Performance (Warn) - Detects potential performance footguns - Style (Allow, eventually Warn) - Checks for unidiomatic code and tries to make it more readable - This is the most opinionated, which is why it's Allow for now. It will eventually migrate to Warn after multiple rounds of feedback are received. - Restriction (Allow) - Restricts certain patterns that a developer may not want These categories are largely inspired by [Clippy's lint categories](https://doc.rust-lang.org/clippy/lints.html). ## MVP lints These lints are going to be implemented before the linter is released. ### `main_return_without_appexit` #### What it does Checks for `fn main()` entrypoints that call `App::run()` but do not return `AppExit`. #### Why is it bad? `AppExit` is used to determine whether the `App` exited successful or due to an error. Returning it from `main()` will set the exit code, which allows external processes to detect whether there was an error. #### Example ```rust= fn main() { App::new().run(); } ``` Use instead: ```rust= fn main() -> AppExit { // Note the removed semicolon. App::new().run() } ``` ### `plugin_not_ending_in_plugin` #### What it does Checks if a struct's name does not end in `Plugin` if it implements `Plugin`. #### Why is it bad? It is convention for all plugins to end in `Plugin`, since doing so signals how the struct is meant to be used. #### Example ```rust= struct Rendering; impl Plugin for Rendering { fn build(_: &mut App) {} } ``` Use instead: ```rust= struct RenderingPlugin; impl Plugin for RenderingPlugin { fn build(_: &mut App) {} } ``` ### `pre_bevy_0_14` #### What it does Checks if the version of Bevy installed is older than 0.14. #### Why is this bad? This linter was written for Bevy 0.14 and later, so it may not work on earlier versions. ## Post-MVP Lints These lints are good ideas that have generally been approved, but should only be worked on once the MVP ones have been implemented. ### `file_format_without_feature` #### What it does Checks if an asset format is placed inside the `assets` folder without the associated feature flag being enabled. For a list of supported file formats, see: <!-- Make this a table in the final documentation. --> - [Image formats](https://github.com/bevyengine/bevy/blob/d65eb39277c4f85749ba27460b8398f815ef3802/crates/bevy_internal/Cargo.toml#L32-L46) - [Audio formats](https://github.com/bevyengine/bevy/blob/d65eb39277c4f85749ba27460b8398f815ef3802/crates/bevy_internal/Cargo.toml#L54-L65) - [Shader formats](https://github.com/bevyengine/bevy/blob/d65eb39277c4f85749ba27460b8398f815ef3802/crates/bevy_internal/Cargo.toml#L67-L72) #### Why is this bad? Bevy will throw an error if it is unable to read a file. It's common for users to set `default-features = false` but forget to enable the features required for their assets. ### `dynamic_linking_enabled_in_manifest` #### What it does Checks for Bevy's `dynamic_linking` feature being enabled in `Cargo.toml` by default. #### Why is this bad? `dynamic_linking` is not recommended for `--release` builds because it requires distributing the generated `libbevy_dylib` alongside your game and is generally slower. Because Cargo features cannot easily be disabled once turned on, `dynamic_linking` should not be enabled by default. #### Example ```toml= [dependencies] bevy = { version = "0.14", features = ["dynamic_linking"] } ``` Use instead: ```toml= [dependencies] bevy = "0.14" ``` ```bash= cargo run --features bevy/dynamic_linking ``` <!-- The Bevy CLI may have an option to make this easier. --> ### `name_stutter` #### What it does Checks for components, resources, and events whose names end in `Component`, `Resource`, and `Event`. #### Why is this bad? Each of these types usually have a `#[derive(...)]` annotation above them that clearly describes what they are. This is different from plugins and systems, where the trait implementation is either separated from the definition or automatically implemented. #### Example ```rust= #[derive(Component)] struct PowerComponent(f32); #[derive(Resource)] struct ScoreResource(usize); #[derive(Event)] struct GameOverEvent; ``` Use instead: ```rust= #[derive(Component)] struct Power(f32); #[derive(Resource)] struct Score(usize); #[derive(Event)] struct GameOver; ``` ## Resources - [Errors and Lints](https://rustc-dev-guide.rust-lang.org/diagnostics.html) - [`LintStore`](https://rustc-dev-guide.rust-lang.org/diagnostics/lintstore.html) - [Clippy development](https://doc.rust-lang.org/stable/clippy/development/index.html) - [Common tools for writing lints](https://doc.rust-lang.org/stable/clippy/development/common_tools_writing_lints.html) - [Write Rust lints without forking Clippy](https://blog.trailofbits.com/2021/11/09/write-rust-lints-without-forking-clippy/) ## Lint ideas Feel free to add your lint ideas below! All are considered, though they may be rejected for one reason or another. - [ ] [OS dependencies not installed](https://bevyengine.org/learn/quick-start/getting-started/setup/#installing-os-dependencies) - [x] Features that need to be enabled / disabled for certain patterns - File formats (images, sound) - [x] `dynamic_linking` enabled by default in `Cargo.toml` - This is considered an anti-pattern in release mode. - [ ] Disable trace level logging. - [x] Stutter in components, resources, events, systems - A component named `NameComponent` is unnecessarily redundant, `Name` is much better. - [ ] Bad queries - `Query<&Foo, With<Foo>>` needlessly filters for `Foo` - Forgetting to query a reference: `Query<Foo>` will fail, try `Query<&Foo>` - [ ] Unregistered types (events, resources, reflected) - [ ] System ordering ambiguity - [ ] Logging without registering `LogPlugin` - [ ] `impl Plugin` vs. `fn plugin(&mut App)` - Only when `impl Plugin` just implements `build()` - Prefer `fn plugin()` when `pub(super)`, maybe not when `pub` - [x] Returning `AppExit` from `fn main()` - [ ] Missing `#[derive(Reflect)]` - [x] Plugin names should end in `Plugin` - [ ] ~~Every file should contain a plugin~~ - [ ] Systems sets should end in `System` or `Set` - [ ] Not adding `#[reflect(Foo)]` when it is derived - [ ] Calling panicking `single()` instead of `get()` - [ ] Using `before()` and `after()` in places where `chain()` could be used instead - [ ] Duplicate Bevy dependencies - [ ] unneeded `ResMut` (not actually used as mutable) - [ ] unneeded `&mut Component` - [ ] unneeded `Ref<Component>` - [ ] `Res<Events<T>>` (trap because of double-reading events) - [ ] `.init_resource` on `Event` resource (won't set up cleanup systems) - [ ] manual construction of `SystemState` in an exclusive system (use `&mut SystemState` system param) - [ ] manual `Mut::set_if_neq` implementation [Initial discussion](https://discord.com/channels/691052431525675048/1277626501197463576/1278058521933381714), [opinionated list by Jan](https://discord.com/channels/691052431525675048/1277626501197463576/1278063696224059525)