Skip to content

Latest commit

 

History

History
134 lines (95 loc) · 6.54 KB

CONTRIBUTING.md

File metadata and controls

134 lines (95 loc) · 6.54 KB

Contributing to Ruff

Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.

The basics

Ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration options), consider submitting an Issue outlining your proposed change.

If you're looking for a place to start, we recommend implementing a new lint rule (see: Adding a new lint rule, which will allow you to learn from and pattern-match against the examples in the existing codebase. Many lint rules are inspired by existing Python plugins, which can be used as a reference implementation.

As a concrete example: consider taking on one of the rules in flake8-simplify, and looking to the originating Python source for guidance.

Prerequisites

Ruff is written in Rust. You'll need to install the Rust toolchain for development.

You'll also need Insta to update snapshot tests:

cargo install cargo-insta

Development

After cloning the repository, run Ruff locally with:

cargo run resources/test/fixtures --no-cache

Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes both the lint and test validation checks.

For rustfmt and Clippy, we use nightly Rust, as it is stricter than stable Rust. (However, tests and builds use stable Rust.)

cargo +nightly fmt --all     # Auto-formatting...
cargo +nightly clippy --fix --workspace --all-targets --all-features -- -W clippy::pedantic  # Linting...
cargo test --all    # Testing...

These checks will run on GitHub Actions when you open your Pull Request, but running them locally will save you time and expedite the merge process.

Your Pull Request will be reviewed by a maintainer, which may involve a few rounds of iteration prior to merging.

Example: Adding a new lint rule

At a high level, the steps involved in adding a new lint rule are as follows:

  1. Create a file for your rule (e.g., src/rules/flake8_bugbear/rules/abstract_base_class.rs).
  2. In that file, define a violation struct. You can grep for define_violation! to see examples.
  3. Map the violation struct to a rule code in src/registry.rs (e.g., E402).
  4. Define the logic for triggering the violation in src/checkers/ast.rs (for AST-based checks), src/checkers/tokens.rs (for token-based checks), src/checkers/lines.rs (for text-based checks), or src/checkers/filesystem.rs (for filesystem-based checks).
  5. Add a test fixture.
  6. Update the generated files (documentation and generated code).

To define the violation, start by creating a dedicated file for your rule under the appropriate rule linter (e.g., src/rules/flake8_bugbear/rules/abstract_base_class.rs). That file should contain a struct defined via define_violation!, along with a function that creates the violation based on any required inputs. (Many of the existing examples live in src/violations.rs, but we're looking to place new rules in their own files.)

To trigger the violation, you'll likely want to augment the logic in src/checkers/ast.rs, which defines the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting diagnostics as it goes.

If you need to inspect the AST, you can run cargo +nightly dev print-ast with a Python file. Grep for the Check::new invocations to understand how other, similar rules are implemented.

To add a test fixture, create a file under resources/test/fixtures/[linter], named to match the code you defined earlier (e.g., resources/test/fixtures/pycodestyle/E402.py). This file should contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior of your lint rule.

Run cargo +nightly dev generate-all to generate the code for your new fixture. Then run Ruff locally with (e.g.) cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402.

Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new test_case macro in the relevant src/[linter]/mod.rs file. Then, run cargo test --all. Your test will fail, but you'll be prompted to follow-up with cargo insta review. Accept the generated snapshot, then commit the snapshot file alongside the rest of your changes.

Finally, regenerate the documentation and generated code with cargo +nightly dev generate-all.

Example: Adding a new configuration option

Ruff's user-facing settings live in a few different places.

First, the command-line options are defined via the Cli struct in src/cli.rs.

Second, the pyproject.toml options are defined in src/settings/options.rs (via the Options struct), src/settings/configuration.rs (via the Configuration struct), and src/settings/mod.rs (via the Settings struct). These represent, respectively: the schema used to parse the pyproject.toml file; an internal, intermediate representation; and the final, internal representation used to power Ruff.

To add a new configuration option, you'll likely want to modify these latter few files (along with cli.rs, if appropriate). If you want to pattern-match against an existing example, grep for dummy_variable_rgx, which defines a regular expression to match against acceptable unused variables (e.g., _).

Note that plugin-specific configuration options are defined in their own modules (e.g., src/flake8_unused_arguments/settings.rs).

You may also want to add the new configuration option to the flake8-to-ruff tool, which is responsible for converting flake8 configuration files to Ruff's TOML format. This logic lives in flake8_to_ruff/src/converter.rs.

Finally, regenerate the documentation and generated code with cargo +nightly dev generate-all.

Release process

As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub Actions, which automatically generates the appropriate wheels across architectures and publishes them to PyPI.

Ruff follows the semver versioning standard. However, as pre-1.0 software, even patch releases may contain non-backwards-compatible changes.