Skip to content

Latest commit

 

History

History
125 lines (88 loc) · 5.92 KB

CONTRIBUTING.md

File metadata and controls

125 lines (88 loc) · 5.92 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:

cargo +nightly fmt --all     # Auto-formatting...
cargo +nightly clippy --all  # Linting...
cargo +nightly 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

There are four phases to adding a new lint rule:

  1. Define the violation struct in src/violations.rs (e.g., ModuleImportNotAtTopOfFile).
  2. Map the violation struct to a rule code in src/registry.rs (e.g., E402).
  3. 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).
  4. Add a test fixture.
  5. Update the generated files (documentation and generated code).

To define the violation, open up src/violations.rs, and define a new struct using the define_violation! macro. There are plenty of examples in that file, so feel free to pattern-match against the existing structs.

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/[origin], 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/[origin]/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.