Skip to content

Add a Bevy Linter #2582

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

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci

bevy_lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: cache
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
~/.dylint_drivers
target/
crates/bevy_lint/target
key: ${{ runner.os }}-cargo-dylint-${{ hashFiles('**/Cargo.toml') }}
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
- name: Install dylint
run: cargo install cargo-dylint; cargo install dylint-link
if: steps.cache.outputs.cache-hit != 'true'
- name: Run dylint
run: cargo dylint --all --workspace -- --all-targets
- name: Check Format
run: cd crates/bevy_lint; cargo fmt --all -- --check
- name: Check Clippy
run: cd crates/bevy_lint; cargo clippy --all-targets --all-features -- -D warnings -A clippy::type_complexity
- name: Run Tests
run: cd crates/bevy_lint; cargo test
env:
# Can be removed once https://github.com./trailofbits/dylint/pull/48 is merged and released
CARGO_TERM_COLOR: never

build-wasm:
strategy:
matrix:
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ readme = "README.md"
repository = "https://github.com./bevyengine/bevy"

[workspace]
exclude = ["benches"]
exclude = ["benches", "crates/bevy_lint"]
members = ["crates/*", "examples/ios", "tools/ci"]

[workspace.metadata.dylint]
libraries = [
{ path = "crates/bevy_lint" },
]

[features]
default = [
"bevy_audio",
Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_lint/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[target.x86_64-apple-darwin]
linker = "dylint-link"

[target.x86_64-unknown-linux-gnu]
linker = "dylint-link"

[target.x86_64-pc-windows-msvc]
linker = "dylint-link"
26 changes: 26 additions & 0 deletions crates/bevy_lint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "bevy_lint"
version = "0.1.0"
authors = ["Bevy Contributors <[email protected]>"]
description = "Provides Lints for Bevy Code"
edition = "2018"
publish = false

[package.metadata.rust-analyzer]
rustc_private=true

[lib]
crate-type = ["cdylib"]

[dependencies]
clippy_utils = { git = "https://github.com./rust-lang/rust-clippy", tag = "rust-1.54.0"}
dylint_linting = "1.0.1"
if_chain = "1.0.1"

[dev-dependencies]
dylint_testing = "1.0.1"
bevy_ecs = { path = "../bevy_ecs"}

[[example]]
name = "unnecessary_with"
path = "ui/unnecessary_with.rs"
34 changes: 34 additions & 0 deletions crates/bevy_lint/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Bevy Lint

## What is Bevy Lint?

This crates provides Lints for Bevy Code using [dylint](https://github.com./trailofbits/dylint).

## How to you run Lints

Add this to your Cargo.toml:

```toml
[workspace.metadata.dylint]
libraries = [
{ git = "https://github.com./bevyengine/bevy", tag = "v0.6.0", pattern = "crates/bevy_dylint" },
]
```

Instead of a `tag`, you can also provide a `branch` or a `rev` (revision).

Afterwards you need to run these commans:

```sh
cargo install cargo-dylint dylint-link # Only neccesary once
cargo dylint bevy_dylint
```

## Lint Creation

A Lint is created by implementing the [LateLintPass](https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html) trait and adding to the `register_lints` function.

When creating a UI Test, add the Test as an Example to the [Cargo.toml](Cargo.toml).
Also make sure that your `.stderr` File uses `LF` Line-endings and not `CRLF`, as otherwise the Test will fail without any explanation.

For more Resources you can take a look at the [dylint resources](https://github.com./trailofbits/dylint#resources).
3 changes: 3 additions & 0 deletions crates/bevy_lint/rust-toolchain
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2021-06-03"
components = ["llvm-tools-preview", "rustc-dev", "rustfmt", "clippy"]
73 changes: 73 additions & 0 deletions crates/bevy_lint/src/bevy_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use rustc_hir::{def::Res, GenericArg, MutTy, Path, QPath, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_span::{def_id::DefId, symbol::Symbol};

use crate::bevy_paths;

pub fn path_matches_symbol_path<'hir>(
ctx: &LateContext<'hir>,
path: &Path,
symbol_path: &[&str],
) -> bool {
if let Res::Def(_, def_id) = path.res {
return ctx.match_def_path(
def_id,
symbol_path
.iter()
.map(|str| Symbol::intern(str))
.collect::<Vec<_>>()
.as_slice(),
);
};

false
}

pub fn get_def_id_of_referenced_type(reference: &MutTy) -> Option<DefId> {
if let TyKind::Path(QPath::Resolved(_, path)) = reference.ty.kind {
if let Res::Def(_, def_id) = path.res {
return Some(def_id);
}
}

None
}

pub fn get_def_id_of_first_generic_arg(path: &Path) -> Option<DefId> {
if let Some(segment) = path.segments.iter().last() {
if let Some(generic_args) = segment.args {
if let Some(GenericArg::Type(component)) = &generic_args.args.get(0) {
if let TyKind::Path(QPath::Resolved(_, path)) = component.kind {
if let Res::Def(_, def_id) = path.res {
return Some(def_id);
}
}
}
}
}

None
}

pub fn get_generics_of_query<'hir>(
ctx: &LateContext<'hir>,
query: &'hir Ty,
) -> Option<(&'hir Ty<'hir>, Option<&'hir Ty<'hir>>)> {
if let TyKind::Path(QPath::Resolved(_, path)) = query.kind {
if path_matches_symbol_path(ctx, path, bevy_paths::QUERY) {
if let Some(segment) = path.segments.iter().last() {
if let Some(generic_args) = segment.args {
if let Some(GenericArg::Type(world)) = &generic_args.args.get(1) {
if let Some(GenericArg::Type(filter)) = &generic_args.args.get(2) {
return Some((world, Some(filter)));
}

return Some((world, None));
}
}
}
}
}

None
}
5 changes: 5 additions & 0 deletions crates/bevy_lint/src/bevy_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub const ADDED: &[&str] = &["bevy_ecs", "query", "filter", "Added"];
pub const CHANGED: &[&str] = &["bevy_ecs", "query", "filter", "Changed"];
pub const OR: &[&str] = &["bevy_ecs", "query", "filter", "Or"];
pub const WITH: &[&str] = &["bevy_ecs", "query", "filter", "With"];
pub const QUERY: &[&str] = &["bevy_ecs", "system", "query", "Query"];
40 changes: 40 additions & 0 deletions crates/bevy_lint/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#![feature(rustc_private)]
//#![warn(unused_extern_crates)]

dylint_linting::dylint_library!();

extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_data_structures;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_hir_pretty;
extern crate rustc_index;
extern crate rustc_infer;
extern crate rustc_lexer;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_mir;
extern crate rustc_parse;
extern crate rustc_parse_format;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_target;
extern crate rustc_trait_selection;
extern crate rustc_typeck;

mod bevy_helpers;
mod bevy_paths;
mod unnecessary_with;

#[no_mangle]
pub fn register_lints(_sess: &rustc_session::Session, lint_store: &mut rustc_lint::LintStore) {
lint_store.register_lints(&[unnecessary_with::UNNECESSARY_WITH]);
lint_store.register_late_pass(|| Box::new(unnecessary_with::UnnecessaryWith));
}

#[test]
fn ui() {
dylint_testing::ui_test_examples(env!("CARGO_PKG_NAME"));
}
Loading