diff options
Diffstat (limited to '')
23 files changed, 2521 insertions, 0 deletions
diff --git a/src/tools/clippy/book/README.md b/src/tools/clippy/book/README.md new file mode 100644 index 000000000..6d67f80ff --- /dev/null +++ b/src/tools/clippy/book/README.md @@ -0,0 +1,4 @@ +# Clippy Book + +This is the source for the Clippy Book. See the +[book](src/development/infrastructure/book.md) for more information. diff --git a/src/tools/clippy/book/book.toml b/src/tools/clippy/book/book.toml new file mode 100644 index 000000000..93b6641f7 --- /dev/null +++ b/src/tools/clippy/book/book.toml @@ -0,0 +1,28 @@ +[book] +authors = ["The Rust Clippy Developers"] +language = "en" +multilingual = false +src = "src" +title = "Clippy Documentation" + +[rust] +edition = "2018" + +[output.html] +edit-url-template = "https://github.com/rust-lang/rust-clippy/edit/master/book/{path}" +git-repository-url = "https://github.com/rust-lang/rust-clippy/tree/master/book" +mathjax-support = true +site-url = "/rust-clippy/" + +[output.html.playground] +editable = true +line-numbers = true + +[output.html.search] +boost-hierarchy = 2 +boost-paragraph = 1 +boost-title = 2 +expand = true +heading-split-level = 2 +limit-results = 20 +use-boolean-and = true diff --git a/src/tools/clippy/book/src/README.md b/src/tools/clippy/book/src/README.md new file mode 100644 index 000000000..6248d588a --- /dev/null +++ b/src/tools/clippy/book/src/README.md @@ -0,0 +1,34 @@ +# Clippy + +[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%22+event%3Apush+branch%3Aauto) +[![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) + +A collection of lints to catch common mistakes and improve your +[Rust](https://github.com/rust-lang/rust) code. + +[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) + +Lints are divided into categories, each with a default [lint +level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how +much Clippy is supposed to ~~annoy~~ help you by changing the lint level by +category. + +| Category | Description | Default level | +| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | +| `clippy::correctness` | code that is outright wrong or useless | **deny** | +| `clippy::suspicious` | code that is most likely wrong or useless | **warn** | +| `clippy::complexity` | code that does something simple but in a complex way | **warn** | +| `clippy::perf` | code that can be written to run faster | **warn** | +| `clippy::style` | code that should be written in a more idiomatic way | **warn** | +| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | +| `clippy::nursery` | new lints that are still under development | allow | +| `clippy::cargo` | lints for the cargo manifest | allow | | allow | + +More to come, please [file an +issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! + +The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also +contains "restriction lints", which are for things which are usually not +considered "bad", but may be useful to turn on in specific cases. These should +be used very selectively, if at all. diff --git a/src/tools/clippy/book/src/SUMMARY.md b/src/tools/clippy/book/src/SUMMARY.md new file mode 100644 index 000000000..0b945faf9 --- /dev/null +++ b/src/tools/clippy/book/src/SUMMARY.md @@ -0,0 +1,23 @@ +# Summary + +[Introduction](README.md) + +- [Installation](installation.md) +- [Usage](usage.md) +- [Configuration](configuration.md) +- [Clippy's Lints](lints.md) +- [Continuous Integration](continuous_integration/README.md) + - [GitHub Actions](continuous_integration/github_actions.md) + - [Travis CI](continuous_integration/travis.md) +- [Development](development/README.md) + - [Basics](development/basics.md) + - [Adding Lints](development/adding_lints.md) + - [Common Tools](development/common_tools_writing_lints.md) + - [Infrastructure](development/infrastructure/README.md) + - [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md) + - [Backporting Changes](development/infrastructure/backport.md) + - [Updating the Changelog](development/infrastructure/changelog_update.md) + - [Release a New Version](development/infrastructure/release.md) + - [The Clippy Book](development/infrastructure/book.md) + - [Proposals](development/proposals/README.md) + - [Roadmap 2021](development/proposals/roadmap-2021.md) diff --git a/src/tools/clippy/book/src/configuration.md b/src/tools/clippy/book/src/configuration.md new file mode 100644 index 000000000..6e295ac31 --- /dev/null +++ b/src/tools/clippy/book/src/configuration.md @@ -0,0 +1,92 @@ +# Configuring Clippy + +> **Note:** The configuration file is unstable and may be deprecated in the future. + +Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a +basic `variable = value` mapping eg. + +```toml +avoid-breaking-exported-api = false +blacklisted-names = ["toto", "tata", "titi"] +cognitive-complexity-threshold = 30 +``` + +See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which +lints can be configured and the meaning of the variables. + +To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` +environment variable. + +### Allowing/denying lints + +You can add options to your code to `allow`/`warn`/`deny` Clippy lints: + +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) + +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false + positives. + +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) + +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. + +Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` +the lint will emit an error, when triggering for your code. An error causes clippy to exit with an error code, so is +useful in scripts like CI/CD. + +If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra +flags to Clippy during the run: + +To allow `lint_name`, run + +```terminal +cargo clippy -- -A clippy::lint_name +``` + +And to warn on `lint_name`, run + +```terminal +cargo clippy -- -W clippy::lint_name +``` + +This also works with lint groups. For example you can run Clippy with warnings for all lints enabled: + +```terminal +cargo clippy -- -W clippy::pedantic +``` + +If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are +interested in: + +```terminal +cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... +``` + +### Specifying the minimum supported Rust version + +Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the +minimum supported Rust version (MSRV) in the clippy configuration file. + +```toml +msrv = "1.30.0" +``` + +The MSRV can also be specified as an inner attribute, like below. + +```rust +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.30.0"] + +fn main() { + ... +} +``` + +You can also omit the patch version when specifying the MSRV, so `msrv = 1.30` +is equivalent to `msrv = 1.30.0`. + +Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly. + +Lints that recognize this configuration option can be +found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) diff --git a/src/tools/clippy/book/src/continuous_integration/README.md b/src/tools/clippy/book/src/continuous_integration/README.md new file mode 100644 index 000000000..e5c3673bd --- /dev/null +++ b/src/tools/clippy/book/src/continuous_integration/README.md @@ -0,0 +1,18 @@ +# Continuous Integration + +It is recommended to run Clippy on CI with `-Dwarnings`, so that Clippy lints +prevent CI from passing. To enforce errors on warnings on all `cargo` commands +not just `cargo clippy`, you can set the env var `RUSTFLAGS="-Dwarnings"`. + +We recommend to use Clippy from the same toolchain, that you use for compiling +your crate for maximum compatibility. E.g. if your crate is compiled with the +`stable` toolchain, you should also use `stable` Clippy. + +> _Note:_ New Clippy lints are first added to the `nightly` toolchain. If you +> want to help with improving Clippy and have CI resources left, please consider +> adding a `nightly` Clippy check to your CI and report problems like false +> positives back to us. With that we can fix bugs early, before they can get to +> stable. + +This chapter will give an overview on how to use Clippy on different popular CI +providers. diff --git a/src/tools/clippy/book/src/continuous_integration/github_actions.md b/src/tools/clippy/book/src/continuous_integration/github_actions.md new file mode 100644 index 000000000..339287a7d --- /dev/null +++ b/src/tools/clippy/book/src/continuous_integration/github_actions.md @@ -0,0 +1,21 @@ +# GitHub Actions + +GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed. +It is as simple as running `cargo clippy` to run lints against the codebase. + +```yml +on: push +name: Clippy check + +# Make sure CI fails on all warnings, including Clippy lints +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run Clippy + run: cargo clippy --all-targets --all-features +``` diff --git a/src/tools/clippy/book/src/continuous_integration/travis.md b/src/tools/clippy/book/src/continuous_integration/travis.md new file mode 100644 index 000000000..85b9ed53d --- /dev/null +++ b/src/tools/clippy/book/src/continuous_integration/travis.md @@ -0,0 +1,20 @@ +# Travis CI + +You can add Clippy to Travis CI in the same way you use it locally: + +```yml +language: rust +rust: + - stable + - beta +before_script: + - rustup component add clippy +script: + - cargo clippy + # if you want the build job to fail when encountering warnings, use + - cargo clippy -- -D warnings + # in order to also check tests and non-default crate features, use + - cargo clippy --all-targets --all-features -- -D warnings + - cargo test + # etc. +``` diff --git a/src/tools/clippy/book/src/development/README.md b/src/tools/clippy/book/src/development/README.md new file mode 100644 index 000000000..5cf7201cf --- /dev/null +++ b/src/tools/clippy/book/src/development/README.md @@ -0,0 +1,43 @@ +# Clippy Development + +Hello fellow Rustacean! If you made it here, you're probably interested in +making Clippy better by contributing to it. In that case, welcome to the +project! + +> _Note:_ If you're just interested in using Clippy, there's nothing to see from +> this point onward and you should return to one of the earlier chapters. + +## Getting started + +If this is your first time contributing to Clippy, you should first read the +[Basics docs](basics.md). This will explain the basics on how to get the source +code and how to compile and test the code. + +## Writing code + +If you have done the basic setup, it's time to start hacking. + +The [Adding lints](adding_lints.md) chapter is a walk through on how to add a +new lint to Clippy. This is also interesting if you just want to fix a lint, +because it also covers how to test lints and gives an overview of the bigger +picture. + +If you want to add a new lint or change existing ones apart from bugfixing, it's +also a good idea to give the [stability guarantees][rfc_stability] and +[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a +quick read. The lint categories are also described [earlier in this +book](../lints.md). + +> _Note:_ Some higher level things about contributing to Clippy are still +> covered in the [`CONTRIBUTING.md`] document. Some of those will be moved to +> the book over time, like: +> - Finding something to fix +> - IDE setup +> - High level overview on how Clippy works +> - Triage procedure +> - Bors and Homu + +[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md +[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees +[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories +[`CONTRIBUTING.md`]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md diff --git a/src/tools/clippy/book/src/development/adding_lints.md b/src/tools/clippy/book/src/development/adding_lints.md new file mode 100644 index 000000000..da781eb97 --- /dev/null +++ b/src/tools/clippy/book/src/development/adding_lints.md @@ -0,0 +1,739 @@ +# Adding a new lint + +You are probably here because you want to add a new lint to Clippy. If this is +the first time you're contributing to Clippy, this document guides you through +creating an example lint from scratch. + +To get started, we will create a lint that detects functions called `foo`, +because that's clearly a non-descriptive name. + +- [Adding a new lint](#adding-a-new-lint) + - [Setup](#setup) + - [Getting Started](#getting-started) + - [Defining Our Lint](#defining-our-lint) + - [Standalone](#standalone) + - [Specific Type](#specific-type) + - [Tests Location](#tests-location) + - [Testing](#testing) + - [Cargo lints](#cargo-lints) + - [Rustfix tests](#rustfix-tests) + - [Testing manually](#testing-manually) + - [Lint declaration](#lint-declaration) + - [Lint registration](#lint-registration) + - [Lint passes](#lint-passes) + - [Emitting a lint](#emitting-a-lint) + - [Adding the lint logic](#adding-the-lint-logic) + - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv) + - [Author lint](#author-lint) + - [Print HIR lint](#print-hir-lint) + - [Documentation](#documentation) + - [Running rustfmt](#running-rustfmt) + - [Debugging](#debugging) + - [PR Checklist](#pr-checklist) + - [Adding configuration to a lint](#adding-configuration-to-a-lint) + - [Cheat Sheet](#cheat-sheet) + +## Setup + +See the [Basics](basics.md#get-the-code) documentation. + +## Getting Started + +There is a bit of boilerplate code that needs to be set up when creating a new +lint. Fortunately, you can use the Clippy dev tools to handle this for you. We +are naming our new lint `foo_functions` (lints are generally written in snake +case), and we don't need type information, so it will have an early pass type +(more on this later). If you're unsure if the name you chose fits the lint, +take a look at our [lint naming guidelines][lint_naming]. + +## Defining Our Lint +To get started, there are two ways to define our lint. + +### Standalone +Command: `cargo dev new_lint --name=foo_functions --pass=early --category=pedantic` +(category will default to nursery if not provided) + +This command will create a new file: `clippy_lints/src/foo_functions.rs`, as well +as [register the lint](#lint-registration). + +### Specific Type +Command: `cargo dev new_lint --name=foo_functions --type=functions --category=pedantic` + +This command will create a new file: `clippy_lints/src/{type}/foo_functions.rs`. + +Notice how this command has a `--type` flag instead of `--pass`. Unlike a standalone +definition, this lint won't be registered in the traditional sense. Instead, you will +call your lint from within the type's lint pass, found in `clippy_lints/src/{type}/mod.rs`. + +A "type" is just the name of a directory in `clippy_lints/src`, like `functions` in +the example command. These are groupings of lints with common behaviors, so if your +lint falls into one, it would be best to add it to that type. + +### Tests Location +Both commands will create a file: `tests/ui/foo_functions.rs`. For cargo lints, +two project hierarchies (fail/pass) will be created by default under `tests/ui-cargo`. + +Next, we'll open up these files and add our lint! + +## Testing + +Let's write some tests first that we can execute while we iterate on our lint. + +Clippy uses UI tests for testing. UI tests check that the output of Clippy is +exactly as expected. Each test is just a plain Rust file that contains the code +we want to check. The output of Clippy is compared against a `.stderr` file. +Note that you don't have to create this file yourself, we'll get to generating +the `.stderr` files further down. + +We start by opening the test file created at `tests/ui/foo_functions.rs`. + +Update the file with some examples to get started: + +```rust +#![warn(clippy::foo_functions)] + +// Impl methods +struct A; +impl A { + pub fn fo(&self) {} + pub fn foo(&self) {} + pub fn food(&self) {} +} + +// Default trait methods +trait B { + fn fo(&self) {} + fn foo(&self) {} + fn food(&self) {} +} + +// Plain functions +fn fo() {} +fn foo() {} +fn food() {} + +fn main() { + // We also don't want to lint method calls + foo(); + let a = A; + a.foo(); +} +``` + +Now we can run the test with `TESTNAME=foo_functions cargo uitest`, currently +this test is meaningless though. + +While we are working on implementing our lint, we can keep running the UI test. +That allows us to check if the output is turning into what we want. + +Once we are satisfied with the output, we need to run `cargo dev bless` to +update the `.stderr` file for our lint. Please note that, we should run +`TESTNAME=foo_functions cargo uitest` every time before running `cargo dev +bless`. Running `TESTNAME=foo_functions cargo uitest` should pass then. When we +commit our lint, we need to commit the generated `.stderr` files, too. In +general, you should only commit files changed by `cargo dev bless` for the +specific lint you are creating/editing. Note that if the generated files are +empty, they should be removed. + +> _Note:_ you can run multiple test files by specifying a comma separated list: +> `TESTNAME=foo_functions,test2,test3`. + +### Cargo lints + +For cargo lints, the process of testing differs in that we are interested in the +`Cargo.toml` manifest file. We also need a minimal crate associated with that +manifest. + +If our new lint is named e.g. `foo_categories`, after running `cargo dev +new_lint` we will find by default two new crates, each with its manifest file: + +* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the + new lint to raise an error. +* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger + the lint. + +If you need more cases, you can copy one of those crates (under +`foo_categories`) and rename it. + +The process of generating the `.stderr` file is the same, and prepending the +`TESTNAME` variable to `cargo uitest` works too. + +## Rustfix tests + +If the lint you are working on is making use of structured suggestions, the test +file should include a `// run-rustfix` comment at the top. This will +additionally run [rustfix] for that test. Rustfix will apply the suggestions +from the lint to the code of the test file and compare that to the contents of a +`.fixed` file. + +Use `cargo dev bless` to automatically generate the `.fixed` file after running +the tests. + +[rustfix]: https://github.com/rust-lang/rustfix + +## Testing manually + +Manually testing against an example file can be useful if you have added some +`println!`s and the test suite output becomes unreadable. To try Clippy with +your local modifications, run + +``` +cargo dev lint input.rs +``` + +from the working copy root. With tests in place, let's have a look at +implementing our lint now. + +## Lint declaration + +Let's start by opening the new file created in the `clippy_lints` crate at +`clippy_lints/src/foo_functions.rs`. That's the crate where all the lint code +is. This file has already imported some initial things we will need: + +```rust +use rustc_lint::{EarlyLintPass, EarlyContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_ast::ast::*; +``` + +The next step is to update the lint declaration. Lints are declared using the +[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update +the auto-generated lint declaration to have a real description, something like +this: + +```rust +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// // example code + /// ``` + #[clippy::version = "1.29.0"] + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +* The section of lines prefixed with `///` constitutes the lint documentation + section. This is the default documentation style and will be displayed [like + this][example_lint_page]. To render and open this documentation locally in a + browser, run `cargo dev serve`. +* The `#[clippy::version]` attribute will be rendered as part of the lint + documentation. The value should be set to the current Rust version that the + lint is developed in, it can be retrieved by running `rustc -vV` in the + rust-clippy directory. The version is listed under *release*. (Use the version + without the `-nightly`) suffix. +* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the [lint naming + guidelines][lint_naming] here when naming your lint. In short, the name should + state the thing that is being checked for and read well when used with + `allow`/`warn`/`deny`. +* `pedantic` sets the lint level to `Allow`. The exact mapping can be found + [here][category_level_mapping] +* The last part should be a text that explains what exactly is wrong with the + code + +The rest of this file contains an empty implementation for our lint pass, which +in this case is `EarlyLintPass` and should look like this: + +```rust +// clippy_lints/src/foo_functions.rs + +// .. imports and lint declaration .. + +declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]); + +impl EarlyLintPass for FooFunctions {} +``` + +[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60 +[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure +[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110 + +## Lint registration + +When using `cargo dev new_lint`, the lint is automatically registered and +nothing more has to be done. + +When declaring a new lint by hand and `cargo dev update_lints` is used, the lint +pass may have to be registered manually in the `register_plugins` function in +`clippy_lints/src/lib.rs`: + +```rust +store.register_early_pass(|| Box::new(foo_functions::FooFunctions)); +``` + +As one may expect, there is a corresponding `register_late_pass` method +available as well. Without a call to one of `register_early_pass` or +`register_late_pass`, the lint pass in question will not be run. + +One reason that `cargo dev update_lints` does not automate this step is that +multiple lints can use the same lint pass, so registering the lint pass may +already be done when adding a new lint. Another reason that this step is not +automated is that the order that the passes are registered determines the order +the passes actually run, which in turn affects the order that any emitted lints +are output in. + +## Lint passes + +Writing a lint that only checks for the name of a function means that we only +have to deal with the AST and don't have to deal with the type system at all. +This is good, because it makes writing this particular lint less complicated. + +We have to make this decision with every new Clippy lint. It boils down to using +either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. + +In short, the `LateLintPass` has access to type information while the +`EarlyLintPass` doesn't. If you don't need access to type information, use the +`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed +hasn't really been a concern with Clippy so far. + +Since we don't need type information for checking the function name, we used +`--pass=early` when running the new lint automation and all the imports were +added accordingly. + +[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html + +## Emitting a lint + +With UI tests and the lint declaration in place, we can start working on the +implementation of the lint logic. + +Let's start by implementing the `EarlyLintPass` for our `FooFunctions`: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + // TODO: Emit lint here + } +} +``` + +We implement the [`check_fn`][check_fn] method from the +[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various +information about the function that is currently being checked. More on that in +the next section. Let's worry about the details later and emit our lint for +*every* function definition first. + +Depending on how complex we want our lint message to be, we can choose from a +variety of lint emission functions. They can all be found in +[`clippy_utils/src/diagnostics.rs`][diagnostics]. + +`span_lint_and_help` seems most appropriate in this case. It allows us to +provide an extra help message and we can't really suggest a better name +automatically. This is how it looks: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } +} +``` + +Running our UI test should now produce output that contains the lint message. + +According to [the rustc-dev-guide], the text should be matter of fact and avoid +capitalization and periods, unless multiple sentences are needed. When code or +an identifier must appear in a message or label, it should be surrounded with +single grave accents \`. + +[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn +[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs +[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html + +## Adding the lint logic + +Writing the logic for your lint will most likely be different from our example, +so this section is kept rather short. + +Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind] +that has the [`FnKind::Fn`] variant. It provides access to the name of the +function/method via an [`Ident`][ident]. + +With that we can expand our `check_fn` method to: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + if is_foo_fn(fn_kind) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } + } +} +``` + +We separate the lint conditional from the lint emissions because it makes the +code a bit easier to read. In some cases this separation would also allow to +write some unit tests (as opposed to only UI tests) for the separate function. + +In our example, `is_foo_fn` looks like: + +```rust +// use statements, impl EarlyLintPass, check_fn, .. + +fn is_foo_fn(fn_kind: FnKind<'_>) -> bool { + match fn_kind { + FnKind::Fn(_, ident, ..) => { + // check if `fn` name is `foo` + ident.name.as_str() == "foo" + } + // ignore closures + FnKind::Closure(..) => false + } +} +``` + +Now we should also run the full test suite with `cargo test`. At this point +running `cargo test` should produce the expected output. Remember to run `cargo +dev bless` to update the `.stderr` file. + +`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint +implementation is not violating any Clippy lints itself. + +That should be it for the lint implementation. Running `cargo test` should now +pass. + +[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html +[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn +[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html + +## Specifying the lint's minimum supported Rust version (MSRV) + +Sometimes a lint makes suggestions that require a certain version of Rust. For +example, the `manual_strip` lint suggests using `str::strip_prefix` and +`str::strip_suffix` which is only available after Rust 1.45. In such cases, you +need to ensure that the MSRV configured for the project is >= the MSRV of the +required Rust feature. If multiple features are required, just use the one with +a lower MSRV. + +First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`]. +This can be accessed later as `msrvs::STR_STRIP_PREFIX`, for example. + +```rust +msrv_aliases! { + .. + 1,45,0 { STR_STRIP_PREFIX } +} +``` + +In order to access the project-configured MSRV, you need to have an `msrv` field +in the LintPass struct, and a constructor to initialize the field. The `msrv` +value is passed to the constructor in `clippy_lints/lib.rs`. + +```rust +pub struct ManualStrip { + msrv: Option<RustcVersion>, +} + +impl ManualStrip { + #[must_use] + pub fn new(msrv: Option<RustcVersion>) -> Self { + Self { msrv } + } +} +``` + +The project's MSRV can then be matched against the feature MSRV in the LintPass +using the `meets_msrv` utility function. + +``` rust +if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) { + return; +} +``` + +The project's MSRV can also be specified as an inner attribute, which overrides +the value from `clippy.toml`. This can be accounted for using the +`extract_msrv_attr!(LintContext)` macro and passing +`LateContext`/`EarlyContext`. + +```rust +impl<'tcx> LateLintPass<'tcx> for ManualStrip { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + ... + } + extract_msrv_attr!(LateContext); +} +``` + +Once the `msrv` is added to the lint, a relevant test case should be added to +`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted +if the project's MSRV is lower. + +As a last step, the lint should be added to the lint documentation. This is done +in `clippy_lints/src/utils/conf.rs`: + +```rust +define_Conf! { + /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports + (msrv: Option<String> = None), + ... +} +``` + +[`clippy_utils::msrvs`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/msrvs/index.html + +## Author lint + +If you have trouble implementing your lint, there is also the internal `author` +lint to generate Clippy code that detects the offending pattern. It does not +work for all of the Rust syntax, but can give a good starting point. + +The quickest way to use it, is the [Rust playground: +play.rust-lang.org][author_example]. Put the code you want to lint into the +editor and add the `#[clippy::author]` attribute above the item. Then run Clippy +via `Tools -> Clippy` and you should see the generated code in the output below. + +[Here][author_example] is an example on the playground. + +If the command was executed successfully, you can copy the code over to where +you are implementing your lint. + +[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 + +## Print HIR lint + +To implement a lint, it's helpful to first understand the internal +representation that rustc uses. Clippy has the `#[clippy::dump]` attribute that +prints the [_High-Level Intermediate Representation (HIR)_] of the item, +statement, or expression that the attribute is attached to. To attach the +attribute to expressions you often need to enable +`#![feature(stmt_expr_attributes)]`. + +[Here][print_hir_example] you can find an example, just select _Tools_ and run +_Clippy_. + +[_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html +[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb + +## Documentation + +The final thing before submitting our PR is to add some documentation to our +lint declaration. + +Please document your lint with a doc comment akin to the following: + +```rust +declare_clippy_lint! { + /// ### What it does + /// Checks for ... (describe what the lint matches). + /// + /// ### Why is this bad? + /// Supply the reason for linting the code. + /// + /// ### Example + /// + /// ```rust,ignore + /// // A short example of code that triggers the lint + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// // A short example of improved code that doesn't trigger the lint + /// ``` + #[clippy::version = "1.29.0"] + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +Once your lint is merged, this documentation will show up in the [lint +list][lint_list]. + +[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html + +## Running rustfmt + +[Rustfmt] is a tool for formatting Rust code according to style guidelines. Your +code has to be formatted by `rustfmt` before a PR can be merged. Clippy uses +nightly `rustfmt` in the CI. + +It can be installed via `rustup`: + +```bash +rustup component add rustfmt --toolchain=nightly +``` + +Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is +installed for the nightly toolchain. + +[Rustfmt]: https://github.com/rust-lang/rustfmt + +## Debugging + +If you want to debug parts of your lint implementation, you can use the [`dbg!`] +macro anywhere in your code. Running the tests should then include the debug +output in the `stdout` part. + +[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html + +## PR Checklist + +Before submitting your PR make sure you followed all of the basic requirements: + +<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` --> + +- \[ ] Followed [lint naming conventions][lint_naming] +- \[ ] Added passing UI tests (including committed `.stderr` file) +- \[ ] `cargo test` passes locally +- \[ ] Executed `cargo dev update_lints` +- \[ ] Added lint documentation +- \[ ] Run `cargo dev fmt` + +## Adding configuration to a lint + +Clippy supports the configuration of lints values using a `clippy.toml` file in +the workspace directory. Adding a configuration to a lint can be useful for +thresholds or to constrain some behavior that can be seen as a false positive +for some users. Adding a configuration is done in the following steps: + +1. Adding a new configuration entry to [`clippy_lints::utils::conf`] like this: + + ```rust + /// Lint: LINT_NAME. + /// + /// <The configuration field doc comment> + (configuration_ident: Type = DefaultValue), + ``` + + The doc comment is automatically added to the documentation of the listed + lints. The default value will be formatted using the `Debug` implementation + of the type. +2. Adding the configuration value to the lint impl struct: + 1. This first requires the definition of a lint impl struct. Lint impl + structs are usually generated with the `declare_lint_pass!` macro. This + struct needs to be defined manually to add some kind of metadata to it: + ```rust + // Generated struct definition + declare_lint_pass!(StructName => [ + LINT_NAME + ]); + + // New manual definition struct + #[derive(Copy, Clone)] + pub struct StructName {} + + impl_lint_pass!(StructName => [ + LINT_NAME + ]); + ``` + + 2. Next add the configuration value and a corresponding creation method like + this: + ```rust + #[derive(Copy, Clone)] + pub struct StructName { + configuration_ident: Type, + } + + // ... + + impl StructName { + pub fn new(configuration_ident: Type) -> Self { + Self { + configuration_ident, + } + } + } + ``` +3. Passing the configuration value to the lint impl struct: + + First find the struct construction in the [`clippy_lints` lib file]. The + configuration value is now cloned or copied into a local value that is then + passed to the impl struct like this: + + ```rust + // Default generated registration: + store.register_*_pass(|| box module::StructName); + + // New registration with configuration value + let configuration_ident = conf.configuration_ident.clone(); + store.register_*_pass(move || box module::StructName::new(configuration_ident)); + ``` + + Congratulations the work is almost done. The configuration value can now be + accessed in the linting code via `self.configuration_ident`. + +4. Adding tests: + 1. The default configured value can be tested like any normal lint in + [`tests/ui`]. + 2. The configuration itself will be tested separately in [`tests/ui-toml`]. + Simply add a new subfolder with a fitting name. This folder contains a + `clippy.toml` file with the configuration value and a rust file that + should be linted by Clippy. The test can otherwise be written as usual. + +[`clippy_lints::utils::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/conf.rs +[`clippy_lints` lib file]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs +[`tests/ui`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui +[`tests/ui-toml`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui-toml + +## Cheat Sheet + +Here are some pointers to things you are likely going to need for every lint: + +* [Clippy utils][utils] - Various helper functions. Maybe the function you need + is already in here ([`is_type_diagnostic_item`], [`implements_trait`], + [`snippet`], etc) +* [Clippy diagnostics][diagnostics] +* [Let chains][let-chains] +* [`from_expansion`][from_expansion] and + [`in_external_macro`][in_external_macro] +* [`Span`][span] +* [`Applicability`][applicability] +* [Common tools for writing lints](common_tools_writing_lints.md) helps with + common operations +* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler + concepts +* [The nightly rustc docs][nightly_docs] which has been linked to throughout + this guide + +For `EarlyLintPass` lints: + +* [`EarlyLintPass`][early_lint_pass] +* [`rustc_ast::ast`][ast] + +For `LateLintPass` lints: + +* [`LateLintPass`][late_lint_pass] +* [`Ty::TyKind`][ty] + +While most of Clippy's lint utils are documented, most of rustc's internals lack +documentation currently. This is unfortunate, but in most cases you can probably +get away with copying things from existing similar lints. If you are stuck, +don't hesitate to ask on [Zulip] or in the issue/PR. + +[utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html +[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html +[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html +[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html +[let-chains]: https://github.com/rust-lang/rust/pull/94927 +[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion +[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html +[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html +[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html +[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/ +[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ +[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html +[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy diff --git a/src/tools/clippy/book/src/development/basics.md b/src/tools/clippy/book/src/development/basics.md new file mode 100644 index 000000000..44ba6e327 --- /dev/null +++ b/src/tools/clippy/book/src/development/basics.md @@ -0,0 +1,192 @@ +# Basics for hacking on Clippy + +This document explains the basics for hacking on Clippy. Besides others, this +includes how to build and test Clippy. For a more in depth description on the +codebase take a look at [Adding Lints] or [Common Tools]. + +[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md +[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md + +- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy) + - [Get the Code](#get-the-code) + - [Building and Testing](#building-and-testing) + - [`cargo dev`](#cargo-dev) + - [lintcheck](#lintcheck) + - [PR](#pr) + - [Common Abbreviations](#common-abbreviations) + - [Install from source](#install-from-source) + +## Get the Code + +First, make sure you have checked out the latest version of Clippy. If this is +your first time working on Clippy, create a fork of the repository and clone it +afterwards with the following command: + +```bash +git clone git@github.com:<your-username>/rust-clippy +``` + +If you've already cloned Clippy in the past, update it to the latest version: + +```bash +# If the upstream remote has not been added yet +git remote add upstream https://github.com/rust-lang/rust-clippy +# upstream has to be the remote of the rust-lang/rust-clippy repo +git fetch upstream +# make sure that you are on the master branch +git checkout master +# rebase your master branch on the upstream master +git rebase upstream/master +# push to the master branch of your fork +git push +``` + +## Building and Testing + +You can build and test Clippy like every other Rust project: + +```bash +cargo build # builds Clippy +cargo test # tests Clippy +``` + +Since Clippy's test suite is pretty big, there are some commands that only run a +subset of Clippy's tests: + +```bash +# only run UI tests +cargo uitest +# only run UI tests starting with `test_` +TESTNAME="test_" cargo uitest +# only run dogfood tests +cargo dev dogfood +``` + +If the output of a [UI test] differs from the expected output, you can update +the reference file with: + +```bash +cargo dev bless +``` + +For example, this is necessary, if you fix a typo in an error message of a lint +or if you modify a test file to add a test case. + +> _Note:_ This command may update more files than you intended. In that case +> only commit the files you wanted to update. + +[UI test]: https://rustc-dev-guide.rust-lang.org/tests/adding.html#guide-to-the-ui-tests + +## `cargo dev` + +Clippy has some dev tools to make working on Clippy more convenient. These tools +can be accessed through the `cargo dev` command. Available tools are listed +below. To get more information about these commands, just call them with +`--help`. + +```bash +# formats the whole Clippy codebase and all tests +cargo dev fmt +# register or update lint names/groups/... +cargo dev update_lints +# create a new lint and register it +cargo dev new_lint +# deprecate a lint and attempt to remove code relating to it +cargo dev deprecate +# automatically formatting all code before each commit +cargo dev setup git-hook +# (experimental) Setup Clippy to work with IntelliJ-Rust +cargo dev setup intellij +# runs the `dogfood` tests +cargo dev dogfood +``` + +More about intellij command usage and reasons +[here](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md#intellij-rust) + +## lintcheck + +`cargo lintcheck` will build and run clippy on a fixed set of crates and +generate a log of the results. You can `git diff` the updated log against its +previous version and see what impact your lint made on a small set of crates. +If you add a new lint, please audit the resulting warnings and make sure there +are no false positives and that the suggestions are valid. + +Refer to the tools [README] for more details. + +[README]: https://github.com/rust-lang/rust-clippy/blob/master/lintcheck/README.md + +## PR + +We follow a rustc no merge-commit policy. See +<https://rustc-dev-guide.rust-lang.org/contributing.html#opening-a-pr>. + +## Common Abbreviations + +| Abbreviation | Meaning | +| ------------ | -------------------------------------- | +| UB | Undefined Behavior | +| FP | False Positive | +| FN | False Negative | +| ICE | Internal Compiler Error | +| AST | Abstract Syntax Tree | +| MIR | Mid-Level Intermediate Representation | +| HIR | High-Level Intermediate Representation | +| TCX | Type context | + +This is a concise list of abbreviations that can come up during Clippy +development. An extensive general list can be found in the [rustc-dev-guide +glossary][glossary]. Always feel free to ask if an abbreviation or meaning is +unclear to you. + +## Install from source + +If you are hacking on Clippy and want to install it from source, do the +following: + +First, take note of the toolchain +[override](https://rust-lang.github.io/rustup/overrides.html) in +`/rust-toolchain`. We will use this override to install Clippy into the right +toolchain. + +> Tip: You can view the active toolchain for the current directory with `rustup +> show active-toolchain`. + +From the Clippy project root, run the following command to build the Clippy +binaries and copy them into the toolchain directory. This will override the +currently installed Clippy component. + +```terminal +cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin" +``` + +Now you may run `cargo clippy` in any project, using the toolchain where you +just installed Clippy. + +```terminal +cd my-project +cargo +nightly-2021-07-01 clippy +``` + +...or `clippy-driver` + +```terminal +clippy-driver +nightly-2021-07-01 <filename> +``` + +If you need to restore the default Clippy installation, run the following (from +the Clippy project root). + +```terminal +rustup component remove clippy +rustup component add clippy +``` + +> **DO NOT** install using `cargo install --path . --force` since this will +> overwrite rustup +> [proxies](https://rust-lang.github.io/rustup/concepts/proxies.html). That is, +> `~/.cargo/bin/cargo-clippy` and `~/.cargo/bin/clippy-driver` should be hard or +> soft links to `~/.cargo/bin/rustup`. You can repair these by running `rustup +> update`. + +[glossary]: https://rustc-dev-guide.rust-lang.org/appendix/glossary.html diff --git a/src/tools/clippy/book/src/development/common_tools_writing_lints.md b/src/tools/clippy/book/src/development/common_tools_writing_lints.md new file mode 100644 index 000000000..15e00c7d7 --- /dev/null +++ b/src/tools/clippy/book/src/development/common_tools_writing_lints.md @@ -0,0 +1,279 @@ +# Common tools for writing lints + +You may need following tooltips to catch up with common operations. + +- [Common tools for writing lints](#common-tools-for-writing-lints) + - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) + - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking for a specific type](#checking-for-a-specific-type) + - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) + - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method) + - [Dealing with macros](#dealing-with-macros-and-expansions) + +Useful Rustc dev guide links: +- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) +- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html) +- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) +- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) + +## Retrieving the type of an expression + +Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for +example to answer following questions: + +- which type does this expression correspond to (using its [`TyKind`][TyKind])? +- is it a sized type? +- is it a primitive type? +- does it implement a trait? + +This operation is performed using the [`expr_ty()`][expr_ty] method from the +[`TypeckResults`][TypeckResults] struct, that gives you access to the underlying +structure [`Ty`][Ty]. + +Example of use: +```rust +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Get type of `expr` + let ty = cx.typeck_results().expr_ty(expr); + // Match its kind to enter its type + match ty.kind { + ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), + _ => () + } + } +} +``` + +Similarly in [`TypeckResults`][TypeckResults] methods, you have the +[`pat_ty()`][pat_ty] method to retrieve a type from a pattern. + +Two noticeable items here: +- `cx` is the lint context [`LateContext`][LateContext]. The two most useful + data structures in this context are `tcx` and the `TypeckResults` returned by + `LateContext::typeck_results`, allowing us to jump to type definitions and + other compilation stages such as HIR. +- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is + created by type checking step, it includes useful information such as types of + expressions, ways to resolve methods and so on. + +## Checking if an expr is calling a specific method + +Starting with an `expr`, you can check whether it is calling a specific method +`some_method`: + +```rust +impl<'tcx> LateLintPass<'tcx> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + // Check our expr is calling a method + if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..]) = &expr.kind + // Check the name of this method is `some_method` + && path.ident.name == sym!(some_method) + // Optionally, check the type of the self argument. + // - See "Checking for a specific type" + { + // ... + } + } +} +``` + +## Checking for a specific type + +There are three ways to check if an expression type is a specific type we want +to check for. All of these methods only check for the base type, generic +arguments have to be checked separately. + +```rust +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::{paths, match_def_path}; +use rustc_span::symbol::sym; +use rustc_hir::LangItem; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Getting the expression type + let ty = cx.typeck_results().expr_ty(expr); + + // 1. Using diagnostic items + // The last argument is the diagnostic item to check for + if is_type_diagnostic_item(cx, ty, sym::Option) { + // The type is an `Option` + } + + // 2. Using lang items + if is_type_lang_item(cx, ty, LangItem::RangeFull) { + // The type is a full range like `.drain(..)` + } + + // 3. Using the type path + // This method should be avoided if possible + if match_def_path(cx, def_id, &paths::RESULT) { + // The type is a `core::result::Result` + } + } +} +``` + +Prefer using diagnostic items and lang items where possible. + +## Checking if a type implements a specific trait + +There are three ways to do this, depending on if the target trait has a +diagnostic item, lang item or neither. + +```rust +use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths}; +use rustc_span::symbol::sym; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // 1. Using diagnostic items with the expression + // we use `is_trait_method` function from Clippy's utils + if is_trait_method(cx, expr, sym::Iterator) { + // method call in `expr` belongs to `Iterator` trait + } + + // 2. Using lang items with the expression type + let ty = cx.typeck_results().expr_ty(expr); + if cx.tcx.lang_items() + // we are looking for the `DefId` of `Drop` trait in lang items + .drop_trait() + // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils + .map_or(false, |id| implements_trait(cx, ty, id, &[])) { + // `expr` implements `Drop` trait + } + + // 3. Using the type path with the expression + // we use `match_trait_method` function from Clippy's utils + // (This method should be avoided if possible) + if match_trait_method(cx, expr, &paths::INTO) { + // `expr` implements `Into` trait + } + } +} +``` + +> Prefer using diagnostic and lang items, if the target trait has one. + +We access lang items through the type context `tcx`. `tcx` is of type +[`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined +paths for Clippy can be found in [paths.rs][paths] + +## Checking if a type defines a specific method + +To check if our type defines a method called `some_method`: + +```rust +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::return_ty; + +impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + // Check if item is a method/function + if let ImplItemKind::Fn(ref signature, _) = impl_item.kind + // Check the method is named `some_method` + && impl_item.ident.name == sym!(some_method) + // We can also check it has a parameter `self` + && signature.decl.implicit_self.has_implicit_self() + // We can go further and even check if its return type is `String` + && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)) + { + // ... + } + } +} +``` + +## Dealing with macros and expansions + +Keep in mind that macros are already expanded and desugaring is already applied +to the code representation that you are working with in Clippy. This +unfortunately causes a lot of false positives because macro expansions are +"invisible" unless you actively check for them. Generally speaking, code with +macro expansions should just be ignored by Clippy because that code can be +dynamic in ways that are difficult or impossible to see. Use the following +functions to deal with macros: + +- `span.from_expansion()`: detects if a span is from macro expansion or + desugaring. Checking this is a common first step in a lint. + + ```rust + if expr.span.from_expansion() { + // just forget it + return; + } + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, which macro call expanded it. It is sometimes useful to check if the + context of two spans are equal. + + ```rust + // expands to `1 + 0`, but don't lint + 1 + mac!() + ``` + ```rust + if left.span.ctxt() != right.span.ctxt() { + // the coder most likely cannot modify this expression + return; + } + ``` + > Note: Code that is not from expansion is in the "root" context. So any spans + > where `from_expansion` returns `true` can be assumed to have the same + > context. And so just using `span.from_expansion()` is often good enough. + + +- `in_external_macro(span)`: detect if the given span is from a macro defined in + a foreign crate. If you want the lint to work with macro-generated code, this + is the next line of defense to avoid macros not defined in the current crate. + It doesn't make sense to lint code that the coder can't change. + + You may want to use it for example to not start linting in macros from other + crates + + ```rust + #[macro_use] + extern crate a_crate_with_macros; + + // `foo` is defined in `a_crate_with_macros` + foo!("bar"); + + // if we lint the `match` of `foo` call and test its span + assert_eq!(in_external_macro(cx.sess(), match_span), true); + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, what expanded it + + One thing `SpanContext` is useful for is to check if two spans are in the same + context. For example, in `a == b`, `a` and `b` have the same context. In a + `macro_rules!` with `a == $b`, `$b` is expanded to some expression with a + different context from `a`. + + ```rust + macro_rules! m { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + } + } + + let x: Option<u32> = Some(42); + m!(x, x.unwrap()); + + // These spans are not from the same context + // x.is_some() is from inside the macro + // x.unwrap() is from outside the macro + assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt()); + ``` + +[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html +[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html +[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty +[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html +[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty +[paths]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/paths/index.html diff --git a/src/tools/clippy/book/src/development/infrastructure/README.md b/src/tools/clippy/book/src/development/infrastructure/README.md new file mode 100644 index 000000000..3b2a25399 --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/README.md @@ -0,0 +1,19 @@ +# Infrastructure + +In order to deploy Clippy over `rustup`, some infrastructure is necessary. This +chapter describes the different parts of the Clippy infrastructure that need to +be maintained to make this possible. + +The most important part is the sync between the `rust-lang/rust` repository and +the Clippy repository that takes place every two weeks. This process is +described in the [Syncing changes between Clippy and `rust-lang/rust`](sync.md) +section. + +A new Clippy release is done together with every Rust release, so every six +weeks. The release process is described in the [Release a new Clippy +Version](release.md) section. During a release cycle a changelog entry for the +next release has to be written. The format of that and how to do that is +documented in the [Changelog Update](changelog_update.md) section. + +> _Note:_ The Clippy CI should also be described in this chapter, but for now is +> left as a TODO. diff --git a/src/tools/clippy/book/src/development/infrastructure/backport.md b/src/tools/clippy/book/src/development/infrastructure/backport.md new file mode 100644 index 000000000..15f3d1f08 --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/backport.md @@ -0,0 +1,71 @@ +# Backport Changes + +Sometimes it is necessary to backport changes to the beta release of Clippy. +Backports in Clippy are rare and should be approved by the Clippy team. For +example, a backport is done, if a crucial ICE was fixed or a lint is broken to a +point, that it has to be disabled, before landing on stable. + +Backports are done to the `beta` branch of Clippy. Backports to stable Clippy +releases basically don't exist, since this would require a Rust point release, +which is almost never justifiable for a Clippy fix. + + +## Backport the changes + +Backports are done on the beta branch of the Clippy repository. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git checkout -b backport +$ git cherry-pick <SHA> # `<SHA>` is the commit hash of the commit(s), that should be backported +$ git push origin backport +``` + +Now you should test that the backport passes all the tests in the Rust +repository. You can do this with: + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git subtree pull -p src/tools/clippy https://github.com/<your-github-name>/rust-clippy backport +$ ./x.py test src/tools/clippy +``` + +Should the test fail, you can fix Clippy directly in the Rust repository. This +has to be first applied to the Clippy beta branch and then again synced to the +Rust repository, though. The easiest way to do this is: + +```bash +# In the Rust repository +$ git diff --patch --relative=src/tools/clippy > clippy.patch +# In the Clippy repository +$ git apply /path/to/clippy.patch +$ git add -u +$ git commit -m "Fix rustup fallout" +$ git push origin backport +``` + +After this, you can open a PR to the `beta` branch of the Clippy repository. + + +## Update Clippy in the Rust Repository + +This step must be done, **after** the PR of the previous step was merged. + +After the backport landed in the Clippy repository, the branch has to be synced +back to the beta branch of the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git checkout -b clippy_backport +$ git subtree pull -p src/tools/clippy https://github.com/rust-lang/rust-clippy beta +$ git push origin clippy_backport +``` + +Make sure to test the backport in the Rust repository before opening a PR. This +is done with `./x.py test src/tools/clippy`. If that passes all tests, open a PR +to the `beta` branch of the Rust repository. In this PR you should tag the +Clippy team member, that agreed to the backport or the `@rust-lang/clippy` team. +Make sure to add `[beta]` to the title of the PR. diff --git a/src/tools/clippy/book/src/development/infrastructure/book.md b/src/tools/clippy/book/src/development/infrastructure/book.md new file mode 100644 index 000000000..a48742191 --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/book.md @@ -0,0 +1,42 @@ +# The Clippy Book + +This document explains how to make additions and changes to the Clippy book, the +guide to Clippy that you're reading right now. The Clippy book is formatted with +[Markdown](https://www.markdownguide.org) and generated by +[mdbook](https://github.com/rust-lang/mdBook). + +- [Get mdbook](#get-mdbook) +- [Make changes](#make-changes) + +## Get mdbook + +While not strictly necessary since the book source is simply Markdown text +files, having mdbook locally will allow you to build, test and serve the book +locally to view changes before you commit them to the repository. You likely +already have `cargo` installed, so the easiest option is to simply: + +```shell +cargo install mdbook +``` + +See the mdbook [installation](https://github.com/rust-lang/mdBook#installation) +instructions for other options. + +## Make changes + +The book's +[src](https://github.com/rust-lang/rust-clippy/tree/master/book/src) +directory contains all of the markdown files used to generate the book. If you +want to see your changes in real time, you can use the mdbook `serve` command to +run a web server locally that will automatically update changes as they are +made. From the top level of your `rust-clippy` directory: + +```shell +mdbook serve book --open +``` + +Then navigate to `http://localhost:3000` to see the generated book. While the +server is running, changes you make will automatically be updated. + +For more information, see the mdbook +[guide](https://rust-lang.github.io/mdBook/). diff --git a/src/tools/clippy/book/src/development/infrastructure/changelog_update.md b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md new file mode 100644 index 000000000..80a47affe --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md @@ -0,0 +1,105 @@ +# Changelog Update + +If you want to help with updating the [changelog], you're in the right place. + +## When to update + +Typos and other small fixes/additions are _always_ welcome. + +Special care needs to be taken when it comes to updating the changelog for a new +Rust release. For that purpose, the changelog is ideally updated during the week +before an upcoming stable release. You can find the release dates on the [Rust +Forge][forge]. + +Most of the time we only need to update the changelog for minor Rust releases. +It's been very rare that Clippy changes were included in a patch release. + +## Changelog update walkthrough + +### 1. Finding the relevant Clippy commits + +Each Rust release ships with its own version of Clippy. The Clippy subtree can +be found in the `tools` directory of the Rust repository. + +Depending on the current time and what exactly you want to update, the following +bullet points might be helpful: + +* When writing the release notes for the **upcoming stable release** you need to + check out the Clippy commit of the current Rust `beta` branch. + [Link][rust_beta_tools] +* When writing the release notes for the **upcoming beta release**, you need to + check out the Clippy commit of the current Rust `master`. + [Link][rust_master_tools] +* When writing the (forgotten) release notes for a **past stable release**, you + need to check out the Rust release tag of the stable release. + [Link][rust_stable_tools] + +Usually you want to write the changelog of the **upcoming stable release**. Make +sure though, that `beta` was already branched in the Rust repository. + +To find the commit hash, issue the following command when in a `rust-lang/rust` +checkout: +``` +git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" +``` + +### 2. Fetching the PRs between those commits + +Once you've got the correct commit range, run + +``` +util/fetch_prs_between.sh commit1 commit2 > changes.txt +``` + +and open that file in your editor of choice. + +When updating the changelog it's also a good idea to make sure that `commit1` is +already correct in the current changelog. + +### 3. Authoring the final changelog + +The above script should have dumped all the relevant PRs to the file you +specified. It should have filtered out most of the irrelevant PRs already, but +it's a good idea to do a manual cleanup pass where you look for more irrelevant +PRs. If you're not sure about some PRs, just leave them in for the review and +ask for feedback. + +With the PRs filtered, you can start to take each PR and move the `changelog: ` +content to `CHANGELOG.md`. Adapt the wording as you see fit but try to keep it +somewhat coherent. + +The order should roughly be: + +1. New lints +2. Moves or deprecations of lints +3. Changes that expand what code existing lints cover +4. False positive fixes +5. Suggestion fixes/improvements +6. ICE fixes +7. Documentation improvements +8. Others + +As section headers, we use: + +``` +### New Lints +### Moves and Deprecations +### Enhancements +### False Positive Fixes +### Suggestion Fixes/Improvements +### ICE Fixes +### Documentation Improvements +### Others +``` + +Please also be sure to update the Beta/Unreleased sections at the top with the +relevant commit ranges. + +If you have the time, it would be appreciated if you double-check, that the +`#[clippy::version]` attributes for the added lints contains the correct version. + +[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md +[forge]: https://forge.rust-lang.org/ +[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy +[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy +[rust_stable_tools]: https://github.com/rust-lang/rust/releases diff --git a/src/tools/clippy/book/src/development/infrastructure/release.md b/src/tools/clippy/book/src/development/infrastructure/release.md new file mode 100644 index 000000000..057228180 --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/release.md @@ -0,0 +1,142 @@ +# Release a new Clippy Version + +> _NOTE:_ This document is probably only relevant to you, if you're a member of +> the Clippy team. + +Clippy is released together with stable Rust releases. The dates for these +releases can be found at the [Rust Forge]. This document explains the necessary +steps to create a Clippy release. + +1. [Remerge the `beta` branch](#remerge-the-beta-branch) +2. [Update the `beta` branch](#update-the-beta-branch) +3. [Find the Clippy commit](#find-the-clippy-commit) +4. [Tag the stable commit](#tag-the-stable-commit) +5. [Update `CHANGELOG.md`](#update-changelogmd) + +> _NOTE:_ This document is for stable Rust releases, not for point releases. For +> point releases, step 1. and 2. should be enough. + +[Rust Forge]: https://forge.rust-lang.org/ + +## Remerge the `beta` branch + +This step is only necessary, if since the last release something was backported +to the beta Rust release. The remerge is then necessary, to make sure that the +Clippy commit, that was used by the now stable Rust release, persists in the +tree of the Clippy repository. + +To find out if this step is necessary run + +```bash +# Assumes that the local master branch of rust-lang/rust-clippy is up-to-date +$ git fetch upstream +$ git branch master --contains upstream/beta +``` + +If this command outputs `master`, this step is **not** necessary. + +```bash +# Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy +$ git checkout -b backport_remerge +$ git merge upstream/beta +$ git diff # This diff has to be empty, otherwise something with the remerge failed +$ git push origin backport_remerge # This can be pushed to your fork +``` + +After this, open a PR to the master branch. In this PR, the commit hash of the +`HEAD` of the `beta` branch must exists. In addition to that, no files should be +changed by this PR. + +## Update the `beta` branch + +This step must be done **after** the PR of the previous step was merged. + +First, the Clippy commit of the `beta` branch of the Rust repository has to be +determined. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream +$ git checkout upstream/beta +$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") +``` + +After finding the Clippy commit, the `beta` branch in the Clippy repository can +be updated. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git reset --hard $BETA_SHA +$ git push upstream beta +``` + +## Find the Clippy commit + +The first step is to tag the Clippy commit, that is included in the stable Rust +release. This commit can be found in the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream # `upstream` is the `rust-lang/rust` remote +$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version +$ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") +``` + +## Tag the stable commit + +After finding the Clippy commit, it can be tagged with the release number. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout $SHA +$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version +$ git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote +``` + +After this, the release should be available on the Clippy [release page]. + +[release page]: https://github.com/rust-lang/rust-clippy/releases + +## Update the `stable` branch + +At this step you should have already checked out the commit of the `rust-1.XX.0` +tag. Updating the stable branch from here is as easy as: + +```bash +# Assuming the current directory corresponds to the Clippy repository and the +# commit of the just created rust-1.XX.0 tag is checked out. +$ git push upstream rust-1.XX.0:stable # `upstream` is the `rust-lang/rust-clippy` remote +``` + +> _NOTE:_ Usually there are no stable backports for Clippy, so this update +> should be possible without force pushing or anything like this. If there +> should have happened a stable backport, make sure to re-merge those changes +> just as with the `beta` branch. + +## Update `CHANGELOG.md` + +For this see the document on [how to update the changelog]. + +If you don't have time to do a complete changelog update right away, just update +the following parts: + +- Remove the `(beta)` from the new stable version: + + ```markdown + ## Rust 1.XX (beta) -> ## Rust 1.XX + ``` + +- Update the release date line of the new stable version: + + ```markdown + Current beta, release 20YY-MM-DD -> Current stable, released 20YY-MM-DD + ``` + +- Update the release date line of the previous stable version: + + ```markdown + Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD + ``` + +[how to update the changelog]: changelog_update.md diff --git a/src/tools/clippy/book/src/development/infrastructure/sync.md b/src/tools/clippy/book/src/development/infrastructure/sync.md new file mode 100644 index 000000000..5a0f7409a --- /dev/null +++ b/src/tools/clippy/book/src/development/infrastructure/sync.md @@ -0,0 +1,123 @@ +# Syncing changes between Clippy and [`rust-lang/rust`] + +Clippy currently gets built with a pinned nightly version. + +In the `rust-lang/rust` repository, where rustc resides, there's a copy of +Clippy that compiler hackers modify from time to time to adapt to changes in the +unstable API of the compiler. + +We need to sync these changes back to this repository periodically, and the +changes made to this repository in the meantime also need to be synced to the +`rust-lang/rust` repository. + +To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is +done in a bi-weekly basis if there's no urgent changes. This is done starting on +the day of the Rust stable release and then every other week. That way we +guarantee that we keep this repo up to date with the latest compiler API, and +every feature in Clippy is available for 2 weeks in nightly, before it can get +to beta. For reference, the first sync following this cadence was performed the +2020-08-27. + +This process is described in detail in the following sections. For general +information about `subtree`s in the Rust repository see [Rust's +`CONTRIBUTING.md`][subtree]. + +## Patching git-subtree to work with big repos + +Currently, there's a bug in `git-subtree` that prevents it from working properly +with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's +stale. Before continuing with the following steps, we need to manually apply +that fix to our local copy of `git-subtree`. + +You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. +Put this file under `/usr/lib/git-core` (making a backup of the previous file) +and make sure it has the proper permissions: + +```bash +sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree +sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +``` + +> _Note:_ The first time running `git subtree push` a cache has to be built. +> This involves going through the complete Clippy history once. For this you +> have to increase the stack limit though, which you can do with `ulimit -s +> 60000`. Make sure to run the `ulimit` command from the same session you call +> git subtree. + +> _Note:_ If you are a Debian user, `dash` is the shell used by default for +> scripts instead of `sh`. This shell has a hardcoded recursion limit set to +> 1000. In order to make this process work, you need to force the script to run +> `bash` instead. You can do this by editing the first line of the `git-subtree` +> script and changing `sh` to `bash`. + +## Defining remotes + +You may want to define remotes, so you don't have to type out the remote +addresses on every sync. You can do this with the following commands (these +commands still have to be run inside the `rust` directory): + +```bash +# Set clippy-upstream remote for pulls +$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy +# Make sure to not push to the upstream repo +$ git remote set-url --push clippy-upstream DISABLED +# Set a local remote +$ git remote add clippy-local /path/to/rust-clippy +``` + +> Note: The following sections assume that you have set those remotes with the +> above remote names. + +## Performing the sync from [`rust-lang/rust`] to Clippy + +Here is a TL;DR version of the sync process (all of the following commands have +to be run inside the `rust` directory): + +1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. +2. Checkout the commit from the latest available nightly. You can get it using + `rustup check`. +3. Sync the changes to the rust-copy of Clippy to your Clippy fork: + ```bash + # Make sure to change `your-github-name` to your github name in the following command. Also be + # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand + # because changes cannot be fast forwarded and you have to run this command again. + git subtree push -P src/tools/clippy clippy-local sync-from-rust + ``` + + > _Note:_ Most of the time you have to create a merge commit in the + > `rust-clippy` repo (this has to be done in the Clippy repo, not in the + > rust-copy of Clippy): + ```bash + git fetch upstream # assuming upstream is the rust-lang/rust remote + git checkout sync-from-rust + git merge upstream/master --no-ff + ``` + > Note: This is one of the few instances where a merge commit is allowed in + > a PR. +4. Bump the nightly version in the Clippy repository by changing the date in the + rust-toolchain file to the current date and committing it with the message: + ```bash + git commit -m "Bump nightly version -> YYYY-MM-DD" + ``` +5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to + accelerate the process ping the `@rust-lang/clippy` team in your PR and/or + ask them in the [Zulip] stream.) + +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy + +## Performing the sync from Clippy to [`rust-lang/rust`] + +All of the following commands have to be run inside the `rust` directory. + +1. Make sure you have checked out the latest `master` of `rust-lang/rust`. +2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: + ```bash + git checkout -b sync-from-clippy + git subtree pull -P src/tools/clippy clippy-upstream master + ``` +3. Open a PR to [`rust-lang/rust`] + +[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 +[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree +[`rust-lang/rust`]: https://github.com/rust-lang/rust diff --git a/src/tools/clippy/book/src/development/proposals/README.md b/src/tools/clippy/book/src/development/proposals/README.md new file mode 100644 index 000000000..78fe34ebf --- /dev/null +++ b/src/tools/clippy/book/src/development/proposals/README.md @@ -0,0 +1,11 @@ +# Proposals + +This chapter is about accepted proposals for changes that should be worked on in +or around Clippy in the long run. + +Besides adding more and more lints and improve the lints that Clippy already +has, Clippy is also interested in making the experience of its users, developers +and maintainers better over time. Projects that address bigger picture things +like this usually take more time and it is useful to have a proposal for those +first. This is the place where such proposals are collected, so that we can +refer to them when working on them. diff --git a/src/tools/clippy/book/src/development/proposals/roadmap-2021.md b/src/tools/clippy/book/src/development/proposals/roadmap-2021.md new file mode 100644 index 000000000..fe8b080f5 --- /dev/null +++ b/src/tools/clippy/book/src/development/proposals/roadmap-2021.md @@ -0,0 +1,235 @@ +# Roadmap 2021 + +# Summary + +This Roadmap lays out the plans for Clippy in 2021: + +- Improving usability and reliability +- Improving experience of contributors and maintainers +- Develop and specify processes + +Members of the Clippy team will be assigned tasks from one or more of these +topics. The team member is then responsible to complete the assigned tasks. This +can either be done by implementing them or by providing mentorship to interested +contributors. + +# Motivation + +With the ongoing growth of the Rust language and with that of the whole +ecosystem, also Clippy gets more and more users and contributors. This is good +for the project, but also brings challenges along. Some of these challenges are: + +- More issues about reliability or usability are popping up +- Traffic is hard to handle for a small team +- Bigger projects don't get completed due to the lack of processes and/or time + of the team members + +Additionally, according to the [Rust Roadmap 2021], clear processes should be +defined by every team and unified across teams. This Roadmap is the first step +towards this. + +[Rust Roadmap 2021]: https://github.com/rust-lang/rfcs/pull/3037 + +# Explanation + +This section will explain the things that should be done in 2021. It is +important to note, that this document focuses on the "What?", not the "How?". +The later will be addressed in follow-up tracking issue, with an assigned team +member. + +The following is split up in two major sections. The first section covers the +user facing plans, the second section the internal plans. + +## User Facing + +Clippy should be as pleasant to use and configure as possible. This section +covers plans that should be implemented to improve the situation of Clippy in +this regard. + +### Usability + +In the following, plans to improve the usability are covered. + +#### No Output After `cargo check` + +Currently when `cargo clippy` is run after `cargo check`, it does not produce +any output. This is especially problematic since `rust-analyzer` is on the rise +and it uses `cargo check` for checking code. A fix is already implemented, but +it still has to be pushed over the finish line. This also includes the +stabilization of the `cargo clippy --fix` command or the support of multi-span +suggestions in `rustfix`. + +- [#4612](https://github.com/rust-lang/rust-clippy/issues/4612) + +#### `lints.toml` Configuration + +This is something that comes up every now and then: a reusable configuration +file, where lint levels can be defined. Discussions about this often lead to +nothing specific or to "we need an RFC for this". And this is exactly what needs +to be done. Get together with the cargo team and write an RFC and implement such +a configuration file somehow and somewhere. + +- [#3164](https://github.com/rust-lang/rust-clippy/issues/3164) +- [cargo#5034](https://github.com/rust-lang/cargo/issues/5034) +- [IRLO](https://internals.rust-lang.org/t/proposal-cargo-lint-configuration/9135/8) + +#### Lint Groups + +There are more and more issues about managing lints in Clippy popping up. Lints +are hard to implement with a guarantee of no/few false positives (FPs). One way +to address this might be to introduce more lint groups to give users the ability +to better manage lints, or improve the process of classifying lints, so that +disabling lints due to FPs becomes rare. It is important to note, that Clippy +lints are less conservative than `rustc` lints, which won't change in the +future. + +- [#5537](https://github.com/rust-lang/rust-clippy/issues/5537) +- [#6366](https://github.com/rust-lang/rust-clippy/issues/6366) + +### Reliability + +In the following, plans to improve the reliability are covered. + +#### False Positive Rate + +In the worst case, new lints are only available in nightly for 2 weeks, before +hitting beta and ultimately stable. This and the fact that fewer people use +nightly Rust nowadays makes it more probable that a lint with many FPs hits +stable. This leads to annoyed users, that will disable these new lints in the +best case and to more annoyed users, that will stop using Clippy in the worst. +A process should be developed and implemented to prevent this from happening. + +- [#6429](https://github.com/rust-lang/rust-clippy/issues/6429) + +## Internal + +(The end of) 2020 has shown, that Clippy has to think about the available +resources, especially regarding management and maintenance of the project. This +section address issues affecting team members and contributors. + +### Management + +In 2020 Clippy achieved over 1000 open issues with regularly between 25-35 open +PRs. This is simultaneously a win and a loss. More issues and PRs means more +people are interested in Clippy and in contributing to it. On the other hand, it +means for team members more work and for contributors longer wait times for +reviews. The following will describe plans how to improve the situation for both +team members and contributors. + +#### Clear Expectations for Team Members + +According to the [Rust Roadmap 2021], a document specifying what it means to be +a member of the team should be produced. This should not put more pressure on +the team members, but rather help them and interested folks to know what the +expectations are. With this it should also be easier to recruit new team members +and may encourage people to get in touch, if they're interested to join. + +#### Scaling up the Team + +More people means less work for each individual. Together with the document +about expectations for team members, a document defining the process of how to +join the team should be produced. This can also increase the stability of the +team, in case of current members dropping out (temporarily). There can also be +different roles in the team, like people triaging vs. people reviewing. + +#### Regular Meetings + +Other teams have regular meetings. Clippy is big enough that it might be worth +to also do them. Especially if more people join the team, this can be important +for sync-ups. Besides the asynchronous communication, that works well for +working on separate lints, a meeting adds a synchronous alternative at a known +time. This is especially helpful if there are bigger things that need to be +discussed (like the projects in this roadmap). For starters bi-weekly meetings +before Rust syncs might make sense. + +#### Triaging + +To get a handle on the influx of open issues, a process for triaging issues and +PRs should be developed. Officially, Clippy follows the Rust triage process, but +currently no one enforces it. This can be improved by sharing triage teams +across projects or by implementing dashboards / tools which simplify triaging. + +### Development + +Improving the developer and contributor experience is something the Clippy team +works on regularly. Though, some things might need special attention and +planing. These topics are listed in the following. + +#### Process for New and Existing Lints + +As already mentioned above, classifying new lints gets quite hard, because the +probability of a buggy lint getting into stable is quite high. A process should +be implemented on how to classify lints. In addition, a test system should be +developed to find out which lints are currently problematic in real world code +to fix or disable them. + +- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741056379) +- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741153345) + +#### Processes + +Related to the point before, a process for suggesting and discussing major +changes should be implemented. It's also not clearly defined when a lint should +be enabled or disabled by default. This can also be improved by the test system +mentioned above. + +#### Dev-Tools + +There's already `cargo dev` which makes Clippy development easier and more +pleasant. This can still be expanded, so that it covers more areas of the +development process. + +- [#5394](https://github.com/rust-lang/rust-clippy/issues/5394) + +#### Contributor Guide + +Similar to a Clippy Book, which describes how to use Clippy, a book about how to +contribute to Clippy might be helpful for new and existing contributors. There's +already the `doc` directory in the Clippy repo, this can be turned into a +`mdbook`. + +#### `rustc` integration + +Recently Clippy was integrated with `git subtree` into the `rust-lang/rust` +repository. This made syncing between the two repositories easier. A +`#[non_exhaustive]` list of things that still can be improved is: + +1. Use the same `rustfmt` version and configuration as `rustc`. +2. Make `cargo dev` work in the Rust repo, just as it works in the Clippy repo. + E.g. `cargo dev bless` or `cargo dev update_lints`. And even add more things + to it that might be useful for the Rust repo, e.g. `cargo dev deprecate`. +3. Easier sync process. The `subtree` situation is not ideal. + +## Prioritization + +The most pressing issues for users of Clippy are of course the user facing +issues. So there should be a priority on those issues, but without losing track +of the internal issues listed in this document. + +Getting the FP rate of warn/deny-by-default lints under control should have the +highest priority. Other user facing issues should also get a high priority, but +shouldn't be in the way of addressing internal issues. + +To better manage the upcoming projects, the basic internal processes, like +meetings, tracking issues and documentation, should be established as soon as +possible. They might even be necessary to properly manage the projects, +regarding the user facing issues. + +# Prior Art + +## Rust Roadmap + +Rust's roadmap process was established by [RFC 1728] in 2016. Since then every +year a roadmap was published, that defined the bigger plans for the coming +years. This years roadmap can be found [here][Rust Roadmap 2021]. + +[RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html + +# Drawbacks + +## Big Roadmap + +This roadmap is pretty big and not all items listed in this document might be +addressed during 2021. Because this is the first roadmap for Clippy, having open +tasks at the end of 2021 is fine, but they should be revisited in the 2022 +roadmap. diff --git a/src/tools/clippy/book/src/installation.md b/src/tools/clippy/book/src/installation.md new file mode 100644 index 000000000..b2a28d0be --- /dev/null +++ b/src/tools/clippy/book/src/installation.md @@ -0,0 +1,24 @@ +# Installation + +If you're using `rustup` to install and manage you're Rust toolchains, Clippy is +usually **already installed**. In that case you can skip this chapter and go to +the [Usage] chapter. + +> Note: If you used the `minimal` profile when installing a Rust toolchain, +> Clippy is not automatically installed. + +## Using Rustup + +If Clippy was not installed for a toolchain, it can be installed with + +``` +$ rustup component add clippy [--toolchain=<name>] +``` + +## From Source + +Take a look at the [Basics] chapter in the Clippy developer guide to find step +by step instructions on how to build and install Clippy from source. + +[Basics]: development/basics.md#install-from-source +[Usage]: usage.md diff --git a/src/tools/clippy/book/src/lints.md b/src/tools/clippy/book/src/lints.md new file mode 100644 index 000000000..35e30960b --- /dev/null +++ b/src/tools/clippy/book/src/lints.md @@ -0,0 +1,105 @@ +# Clippy's Lints + +Clippy offers a bunch of additional lints, to help its users write more correct +and idiomatic Rust code. A full list of all lints, that can be filtered by +category, lint level or keywords, can be found in the [Clippy lint +documentation]. + +This chapter will give an overview of the different lint categories, which kind +of lints they offer and recommended actions when you should see a lint out of +that category. For examples, see the [Clippy lint documentation] and filter by +category. + +The different lint groups were defined in the [Clippy 1.0 RFC]. + +## Correctness + +The `clippy::correctness` group is the only lint group in Clippy which lints are +deny-by-default and abort the compilation when triggered. This is for good +reason: If you see a `correctness` lint, it means that your code is outright +wrong or useless and you should try to fix it. + +Lints in this category are carefully picked and should be free of false +positives. So just `#[allow]`ing those lints is not recommended. + +## Suspicious + +The `clippy::suspicious` group is similar to the correctness lints in that it +contains lints that trigger on code that is really _sus_ and should be fixed. As +opposed to correctness lints, it might be possible that the linted code is +intentionally written like it is. + +It is still recommended to fix code that is linted by lints out of this group +instead of `#[allow]`ing the lint. In case you intentionally have written code +that offends the lint you should specifically and locally `#[allow]` the lint +and add give a reason why the code is correct as written. + +## Complexity + +The `clippy::complexity` group offers lints that give you suggestions on how to +simplify your code. It mostly focuses on code that can be written in a shorter +and more readable way, while preserving the semantics. + +If you should see a complexity lint, it usually means that you can remove or +replace some code and it is recommended to do so. However, if you need the more +complex code for some expressiveness reason, it is recommended to allow +complexity lints on a case-by-case basis. + +## Perf + +The `clippy::perf` group gives you suggestions on how you can increase the +performance of your code. Those lints are mostly about code that the compiler +can't trivially optimize, but has to be written in a slightly different way to +make the optimizer's job easier. + +Perf lints are usually easy to apply and it is recommended to do so. + +## Style + +The `clippy::style` group is mostly about writing idiomatic code. Because style +is subjective, this lint group is the most opinionated warn-by-default group in +Clippy. + +If you see a style lint, applying the suggestion usually makes your code more +readable and idiomatic. But because we know that this is opinionated, feel free +to sprinkle `#[allow]`s for style lints in your code or `#![allow]` a style lint +on your whole crate if you disagree with the suggested style completely. + +## Pedantic + +The `clippy::pedantic` group makes Clippy even more _pedantic_. You can enable +the whole group with `#![warn(clippy::pedantic)]` in the `lib.rs`/`main.rs` of +your crate. This lint group is for Clippy power users that want an in depth +check of their code. + +> _Note:_ Instead of enabling the whole group (like Clippy itself does), you may +> want to cherry-pick lints out of the pedantic group. + +If you enable this group, expect to also use `#[allow]` attributes generously +throughout your code. Lints in this group are designed to be pedantic and false +positives sometimes are intentional in order to prevent false negatives. + +## Restriction + +The `clippy::restriction` group contains lints that will _restrict_ you from +using certain parts of the Rust language. It is **not** recommended to enable +the whole group, but rather cherry-pick lints that are useful for your code base +and your use case. + +> _Note:_ Clippy will produce a warning if it finds a +> `#![warn(clippy::restriction)]` attribute in your code! + +Lints from this group will restrict you in some way. If you enable a restriction +lint for your crate it is recommended to also fix code that this lint triggers +on. However, those lints are really strict by design and you might want to +`#[allow]` them in some special cases, with a comment justifying that. + +## Cargo + +The `clippy::cargo` group gives you suggestions on how to improve your +`Cargo.toml` file. This might be especially interesting if you want to publish +your crate and are not sure if you have all useful information in your +`Cargo.toml`. + +[Clippy lint documentation]: https://rust-lang.github.io/rust-clippy/ +[Clippy 1.0 RFC]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories diff --git a/src/tools/clippy/book/src/usage.md b/src/tools/clippy/book/src/usage.md new file mode 100644 index 000000000..61a90445d --- /dev/null +++ b/src/tools/clippy/book/src/usage.md @@ -0,0 +1,151 @@ +# Usage + +This chapter describes how to use Clippy to get the most out of it. Clippy can +be used as a `cargo` subcommand or, like `rustc`, directly with the +`clippy-driver` binary. + +> _Note:_ This chapter assumes that you have Clippy installed already. If you're +> not sure, take a look at the [Installation] chapter. + +## Cargo subcommand + +The easiest and most common way to run Clippy is through `cargo`. To do that, +just run + +```bash +cargo clippy +``` + +### Lint configuration + +The above command will run the default set of lints, which are included in the +lint group `clippy::all`. You might want to use even more lints or you might not +agree with every Clippy lint, and for that there are ways to configure lint +levels. + +> _Note:_ Clippy is meant to be used with a generous sprinkling of +> `#[allow(..)]`s through your code. So if you disagree with a lint, don't feel +> bad disabling them for parts of your code or the whole project. + +#### Command line + +You can configure lint levels on the command line by adding +`-A/W/D clippy::lint_name` like this: + +```bash +cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf +``` + +For [CI] all warnings can be elevated to errors which will inturn fail +the build and cause Clippy to exit with a code other than `0`. + +``` +cargo clippy -- -Dwarnings +``` + +> _Note:_ Adding `-D warnings` will cause your build to fail if **any** warnings +> are found in your code. That includes warnings found by rustc (e.g. +> `dead_code`, etc.). + +For more information on configuring lint levels, see the [rustc documentation]. + +[rustc documentation]: https://doc.rust-lang.org/rustc/lints/levels.html#configuring-warning-levels + +#### Even more lints + +Clippy has lint groups which are allow-by-default. This means, that you will +have to enable the lints in those groups manually. + +For a full list of all lints with their description and examples, please refer +to [Clippy's lint list]. The two most important allow-by-default groups are +described below: + +[Clippy's lint list]: https://rust-lang.github.io/rust-clippy/master/index.html + +##### `clippy::pedantic` + +The first group is the `pedantic` group. This group contains really opinionated +lints, that may have some intentional false positives in order to prevent false +negatives. So while this group is ready to be used in production, you can expect +to sprinkle multiple `#[allow(..)]`s in your code. If you find any false +positives, you're still welcome to report them to us for future improvements. + +> FYI: Clippy uses the whole group to lint itself. + +##### `clippy::restriction` + +The second group is the `restriction` group. This group contains lints that +"restrict" the language in some way. For example the `clippy::unwrap` lint from +this group won't allow you to use `.unwrap()` in your code. You may want to look +through the lints in this group and enable the ones that fit your need. + +> _Note:_ You shouldn't enable the whole lint group, but cherry-pick lints from +> this group. Some lints in this group will even contradict other Clippy lints! + +#### Too many lints + +The most opinionated warn-by-default group of Clippy is the `clippy::style` +group. Some people prefer to disable this group completely and then cherry-pick +some lints they like from this group. The same is of course possible with every +other of Clippy's lint groups. + +> _Note:_ We try to keep the warn-by-default groups free from false positives +> (FP). If you find that a lint wrongly triggers, please report it in an issue +> (if there isn't an issue for that FP already) + +#### Source Code + +You can configure lint levels in source code the same way you can configure +`rustc` lints: + +```rust +#![allow(clippy::style)] + +#[warn(clippy::double_neg)] +fn main() { + let x = 1; + let y = --x; + // ^^ warning: double negation +} +``` + +### Automatically applying Clippy suggestions + +Clippy can automatically apply some lint suggestions, just like the compiler. + +```terminal +cargo clippy --fix +``` + +### Workspaces + +All the usual workspace options should work with Clippy. For example the +following command will run Clippy on the `example` crate in your workspace: + +```terminal +cargo clippy -p example +``` + +As with `cargo check`, this includes dependencies that are members of the +workspace, like path dependencies. If you want to run Clippy **only** on the +given crate, use the `--no-deps` option like this: + +```terminal +cargo clippy -p example -- --no-deps +``` + +## Using Clippy without `cargo`: `clippy-driver` + +Clippy can also be used in projects that do not use cargo. To do so, run +`clippy-driver` with the same arguments you use for `rustc`. For example: + +```terminal +clippy-driver --edition 2018 -Cpanic=abort foo.rs +``` + +> _Note:_ `clippy-driver` is designed for running Clippy and should not be used +> as a general replacement for `rustc`. `clippy-driver` may produce artifacts +> that are not optimized as expected, for example. + +[Installation]: installation.md +[CI]: continuous_integration/index.md |