about summary refs log tree commit diff
path: root/src/tools/clippy/doc/adding_lints.md
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/doc/adding_lints.md')
-rw-r--r--src/tools/clippy/doc/adding_lints.md477
1 files changed, 477 insertions, 0 deletions
diff --git a/src/tools/clippy/doc/adding_lints.md b/src/tools/clippy/doc/adding_lints.md
new file mode 100644
index 00000000000..9ad1315c175
--- /dev/null
+++ b/src/tools/clippy/doc/adding_lints.md
@@ -0,0 +1,477 @@
+# 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)
+  - [Testing](#testing)
+  - [Rustfix tests](#rustfix-tests)
+  - [Edition 2018 tests](#edition-2018-tests)
+  - [Testing manually](#testing-manually)
+  - [Lint declaration](#lint-declaration)
+  - [Lint passes](#lint-passes)
+  - [Emitting a lint](#emitting-a-lint)
+  - [Adding the lint logic](#adding-the-lint-logic)
+  - [Author lint](#author-lint)
+  - [Documentation](#documentation)
+  - [Running rustfmt](#running-rustfmt)
+  - [Debugging](#debugging)
+  - [PR Checklist](#pr-checklist)
+  - [Cheatsheet](#cheatsheet)
+
+## Setup
+
+When working on Clippy, you will need the current git master version of rustc,
+which can change rapidly. Make sure you're working near rust-clippy's master,
+and use the `setup-toolchain.sh` script to configure the appropriate toolchain
+for the Clippy directory.
+
+## 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 on). To get started on this lint you can run
+`cargo dev new_lint --name=foo_functions --pass=early --category=pedantic`
+(category will default to nursery if not provided). This command will create
+two files: `tests/ui/foo_functions.rs` and `clippy_lints/src/foo_functions.rs`,
+as well as run `cargo dev update_lints` to register the new lint. 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
+`tests/ui/update-all-references.sh` to update the `.stderr` file for our lint.
+Please note that, we should run `TESTNAME=foo_functions cargo uitest`
+every time before running `tests/ui/update-all-references.sh`.
+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 `tests/ui/update-all-references.sh` for the
+specific lint you are creating/editing.
+
+## 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 `tests/ui/update-all-references.sh` to automatically generate the
+`.fixed` file after running the tests.
+
+[rustfix]: https://github.com/rust-lang/rustfix
+
+## Edition 2018 tests
+
+Some features require the 2018 edition to work (e.g. `async_await`), but
+compile-test tests run on the 2015 edition by default. To change this behavior
+add `// edition:2018` at the top of the test file (note that it's space-sensitive).
+
+## 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 `env CLIPPY_TESTS=true cargo run --bin
+clippy-driver -- -L ./target/debug 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?**
+    ///
+    /// **Known problems:** None.
+    ///
+    /// **Example:**
+    ///
+    /// ```rust
+    /// // example code
+    /// ```
+    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].
+* `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 {}
+```
+
+Normally after declaring the lint, we have to run `cargo dev update_lints`,
+which updates some files, so Clippy knows about the new lint. Since we used
+`cargo dev new_lint ...` to generate the lint declaration, this was done
+automatically. While `update_lints` automates most of the things, it doesn't
+automate everything. We will have to register our lint pass manually in the
+`register_plugins` function in `clippy_lints/src/lib.rs`:
+
+```rust
+store.register_early_pass(|| box foo_functions::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 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_lints/src/utils/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.
+
+[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_lints/src/utils/diagnostics.rs
+
+## 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
+`tests/ui/update-all-references.sh` 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
+
+## 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
+
+## 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.
+    ///
+    /// **Known problems:** None. (Or describe where it could go wrong.)
+    ///
+    /// **Example:**
+    ///
+    /// ```rust,ignore
+    /// // Bad
+    /// Insert a short example of code that triggers the lint
+    ///
+    /// // Good
+    /// Insert a short example of improved code that doesn't trigger the lint
+    /// ```
+    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`
+
+## Cheatsheet
+
+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 (`implements_trait`, `match_path`, `snippet`, etc)
+* [Clippy diagnostics][diagnostics]
+* [The `if_chain` macro][if_chain]
+* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro]
+* [`Span`][span]
+* [`Applicability`][applicability]
+* [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 [Discord] or in the issue/PR.
+
+[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/mod.rs
+[if_chain]: https://docs.rs/if_chain/*/if_chain/
+[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
+[Discord]: https://discord.gg/rust-lang