Warning
This document is outdated. For the latest information, please see the repository at TheBevyFlock/bevy_cli.
See the MVP for motivation behind the Bevy CLI. The prototype repository can be found at TheBevyFlock/bevy_cli.
The goal is to write a linter, like cargo check
and Clippy, that checks projects that use the Bevy game engine 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 intends to lead this effort, though others are welcome to join!
bevy lint
command in the main CLI.bevy_lint_driver
binary, installed alongside the rest of the CLI.bevy lint
is called, it will call RUSTC_WORKSPACE_WRAPPER=path/to/bevy_lint_driver cargo check
.
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.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.Lints should be:
cargo fix
support)Rust supports organizing lints into categories that can be toggled in bulk. The planned categories introduced by this linter are:
These categories are largely inspired by Clippy's lint categories.
These lints are going to be implemented before the linter is released.
main_return_without_appexit
Checks for fn main()
entrypoints that call App::run()
but do not return AppExit
.
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.
fn main() {
App::new().run();
}
Use instead:
fn main() -> AppExit {
// Note the removed semicolon.
App::new().run()
}
plugin_not_ending_in_plugin
Checks if a struct's name does not end in Plugin
if it implements Plugin
.
It is convention for all plugins to end in Plugin
, since doing so signals how the struct is meant to be used.
struct Rendering;
impl Plugin for Rendering {
fn build(_: &mut App) {}
}
Use instead:
struct RenderingPlugin;
impl Plugin for RenderingPlugin {
fn build(_: &mut App) {}
}
pre_bevy_0_14
Checks if the version of Bevy installed is older than 0.14.
This linter was written for Bevy 0.14 and later, so it may not work on earlier versions.
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
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:
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
Checks for Bevy's dynamic_linking
feature being enabled in Cargo.toml
by default.
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.
[dependencies]
bevy = { version = "0.14", features = ["dynamic_linking"] }
Use instead:
[dependencies]
bevy = "0.14"
cargo run --features bevy/dynamic_linking
name_stutter
Checks for components, resources, and events whose names end in Component
, Resource
, and Event
.
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.
#[derive(Component)]
struct PowerComponent(f32);
#[derive(Resource)]
struct ScoreResource(usize);
#[derive(Event)]
struct GameOverEvent;
Use instead:
#[derive(Component)]
struct Power(f32);
#[derive(Resource)]
struct Score(usize);
#[derive(Event)]
struct GameOver;
LintStore
Feel free to add your lint ideas below! All are considered, though they may be rejected for one reason or another.
dynamic_linking
enabled by default in Cargo.toml
NameComponent
is unnecessarily redundant, Name
is much better.Query<&Foo, With<Foo>>
needlessly filters for Foo
Query<Foo>
will fail, try Query<&Foo>
LogPlugin
impl Plugin
vs. fn plugin(&mut App)
impl Plugin
just implements build()
fn plugin()
when pub(super)
, maybe not when pub
AppExit
from fn main()
#[derive(Reflect)]
Plugin
System
or Set
#[reflect(Foo)]
when it is derivedsingle()
instead of get()
before()
and after()
in places where chain()
could be used insteadResMut
(not actually used as mutable)&mut Component
Ref<Component>
Res<Events<T>>
(trap because of double-reading events).init_resource
on Event
resource (won't set up cleanup systems)SystemState
in an exclusive system (use &mut SystemState
system param)Mut::set_if_neq
implementation