From 72a64c678730445cfa1c4c4d3e72f6745f93ffe8 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 28 Sep 2023 17:42:01 -0400 Subject: [PATCH] Rework the documentation to incorporate the Ruff formatter Co-authored-by: konsti --- README.md | 100 ++- crates/ruff_dev/src/generate_cli_help.rs | 38 +- .../rules/implicit.rs | 2 +- docs/configuration.md | 385 ++++------- docs/faq.md | 51 +- docs/formatter.md | 653 ++++++++++++++++++ docs/installation.md | 7 + ...editor-integrations.md => integrations.md} | 96 ++- docs/linter.md | 229 ++++++ docs/preview.md | 5 +- docs/tutorial.md | 74 +- docs/usage.md | 109 --- scripts/generate_mkdocs.py | 11 +- 13 files changed, 1312 insertions(+), 448 deletions(-) create mode 100644 docs/formatter.md rename docs/{editor-integrations.md => integrations.md} (78%) create mode 100644 docs/linter.md delete mode 100644 docs/usage.md diff --git a/README.md b/README.md index e464c8978f61bc..fe72c9fc7c0d54 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [**Discord**](https://discord.gg/c9MhzV8aU5) | [**Docs**](https://docs.astral.sh/ruff/) | [**Playground**](https://play.ruff.rs/) -An extremely fast Python linter, written in Rust. +An extremely fast Python linter and code formatter, written in Rust.

@@ -24,17 +24,16 @@ An extremely fast Python linter, written in Rust. Linting the CPython codebase from scratch.

-- ⚡️ 10-100x faster than existing linters +- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black) - 🐍 Installable via `pip` - 🛠️ `pyproject.toml` support - 🤝 Python 3.12 compatibility +- ⚖️ Drop-in parity with [Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8), isort, and Black - 📦 Built-in caching, to avoid re-analyzing unchanged files - 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports) -- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/) -- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the - built-in Flake8 rule set -- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear -- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/editor-integrations/) for +- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/), with native re-implementations + of popular Flake8 plugins, like flake8-bugbear +- ⌨️ First-party [editor integrations](https://docs.astral.sh/ruff/integrations/) for [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp) - 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://docs.astral.sh/ruff/configuration/#pyprojecttoml-discovery) @@ -42,10 +41,10 @@ Ruff aims to be orders of magnitude faster than alternative tools while integrat functionality behind a single, common interface. Ruff can be used to replace [Flake8](https://pypi.org/project/flake8/) (plus dozens of plugins), -[isort](https://pypi.org/project/isort/), [pydocstyle](https://pypi.org/project/pydocstyle/), -[yesqa](https://github.com/asottile/yesqa), [eradicate](https://pypi.org/project/eradicate/), -[pyupgrade](https://pypi.org/project/pyupgrade/), and [autoflake](https://pypi.org/project/autoflake/), -all while executing tens or hundreds of times faster than any individual tool. +[Black](https://github.com/psf/black), [isort](https://pypi.org/project/isort/), +[pydocstyle](https://pypi.org/project/pydocstyle/), [pyupgrade](https://pypi.org/project/pyupgrade/), +[autoflake](https://pypi.org/project/autoflake/), and more, all while executing tens or hundreds of +times faster than any individual tool. Ruff is extremely actively developed and used in major open-source projects like: @@ -126,23 +125,41 @@ and with [a variety of other package managers](https://docs.astral.sh/ruff/insta ### Usage -To run Ruff, try any of the following: +To run Ruff as a linter, try any of the following: ```shell -ruff check . # Lint all files in the current directory (and any subdirectories) -ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories) -ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code` -ruff check path/to/code/to/file.py # Lint `file.py` +ruff check . # Lint all files in the current directory (and any subdirectories). +ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories). +ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code`. +ruff check path/to/code/to/file.py # Lint `file.py`. +ruff check @file_paths.txt # Lint using an input file, treating its contents as newline-delimited command-line arguments. ``` -Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: +Or, to run Ruff as a formatter: + +```shell +ruff format . # Format all files in the current directory (and any subdirectories). +ruff format path/to/code/ # Format all files in `/path/to/code` (and any subdirectories). +ruff format path/to/code/*.py # Format all `.py` files in `/path/to/code`. +ruff format path/to/code/to/file.py # Format `file.py`. +ruff format @file_paths.txt # Format using an input file, treating its contents as newline-delimited command-line arguments. +``` + +Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit): ```yaml +# Run the Ruff linter. - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.0.292 hooks: - id: ruff +# Run the Ruff formatter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff-format ``` Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or @@ -168,18 +185,10 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` [_Configuration_](https://docs.astral.sh/ruff/configuration/), or [_Settings_](https://docs.astral.sh/ruff/settings/) for a complete list of all configuration options). -If left unspecified, the default configuration is equivalent to: +If left unspecified, Ruff's default configuration is equivalent to: ```toml [tool.ruff] -# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. -select = ["E", "F"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] -unfixable = [] - # Exclude a variety of commonly ignored directories. exclude = [ ".bzr", @@ -208,26 +217,44 @@ exclude = [ # Same as Black. line-length = 88 +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default. +select = ["E", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -# Assume Python 3.8 -target-version = "py38" +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +magic-trailing-comma = "respect" -[tool.ruff.mccabe] -# Unlike Flake8, default to a complexity level of 10. -max-complexity = 10 +# Use Unix-style line endings. +line-ending = "lf" ``` Some configuration options can be provided via the command-line, such as those related to -rule enablement and disablement, file discovery, logging level, and more: +rule enablement and disablement, file discovery, and logging level: ```shell ruff check path/to/code/ --select F401 --select F403 --quiet ``` -See `ruff help` for more on Ruff's top-level commands, or `ruff help check` for more on the -linting command. +See `ruff help` for more on Ruff's top-level commands, or `ruff help check` and `ruff help format` +for more on the linting and formatting commands, respectively. ## Rules @@ -239,8 +266,7 @@ Rust as a first-party feature. By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category, and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those -stylistic rules made obsolete by the use of an autoformatter, like -[Black](https://github.com/psf/black). +stylistic rules made obsolete by the use of `ruff format` or [Black](https://github.com/psf/black). If you're just getting started with Ruff, **the default rule set is a great place to start**: it catches a wide variety of common errors (like unused imports) with zero configuration. @@ -331,7 +357,7 @@ In some cases, Ruff includes a "direct" Rust port of the corresponding tool. We're grateful to the maintainers of these tools for their work, and for all the value they've provided to the Python community. -Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter), +Ruff's formatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter), and again draws on both API and implementation details from [Rome](https://github.com/rome/tools), [Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black). diff --git a/crates/ruff_dev/src/generate_cli_help.rs b/crates/ruff_dev/src/generate_cli_help.rs index 420927495ffbde..1493a3814023bc 100644 --- a/crates/ruff_dev/src/generate_cli_help.rs +++ b/crates/ruff_dev/src/generate_cli_help.rs @@ -16,8 +16,11 @@ use crate::ROOT_DIR; const COMMAND_HELP_BEGIN_PRAGMA: &str = "\n"; const COMMAND_HELP_END_PRAGMA: &str = ""; -const SUBCOMMAND_HELP_BEGIN_PRAGMA: &str = "\n"; -const SUBCOMMAND_HELP_END_PRAGMA: &str = ""; +const CHECK_HELP_BEGIN_PRAGMA: &str = "\n"; +const CHECK_HELP_END_PRAGMA: &str = ""; + +const FORMAT_HELP_BEGIN_PRAGMA: &str = "\n"; +const FORMAT_HELP_END_PRAGMA: &str = ""; #[derive(clap::Args)] pub(crate) struct Args { @@ -56,11 +59,15 @@ pub(super) fn main(args: &Args) -> Result<()> { let command_help = trim_lines(&help_text()); // Generate `ruff help check`. - let subcommand_help = trim_lines(&check_help_text()); + let check_help = trim_lines(&subcommand_help_text("check")?); + + // Generate `ruff help format`. + let format_help = trim_lines(&subcommand_help_text("format")?); if args.mode.is_dry_run() { print!("{command_help}"); - print!("{subcommand_help}"); + print!("{check_help}"); + print!("{format_help}"); return Ok(()); } @@ -77,9 +84,15 @@ pub(super) fn main(args: &Args) -> Result<()> { )?; let new = replace_docs_section( &new, - &format!("```text\n{subcommand_help}\n```\n\n"), - SUBCOMMAND_HELP_BEGIN_PRAGMA, - SUBCOMMAND_HELP_END_PRAGMA, + &format!("```text\n{check_help}\n```\n\n"), + CHECK_HELP_BEGIN_PRAGMA, + CHECK_HELP_END_PRAGMA, + )?; + let new = replace_docs_section( + &new, + &format!("```text\n{format_help}\n```\n\n"), + FORMAT_HELP_BEGIN_PRAGMA, + FORMAT_HELP_END_PRAGMA, )?; match args.mode { @@ -104,18 +117,19 @@ fn help_text() -> String { args::Args::command().render_help().to_string() } -/// Returns the output of `ruff help check`. -fn check_help_text() -> String { +/// Returns the output of a given subcommand (e.g., `ruff help check`). +fn subcommand_help_text(subcommand: &str) -> Result { let mut cmd = args::Args::command(); // The build call is necessary for the help output to contain `Usage: ruff // check` instead of `Usage: check` see https://github.com/clap-rs/clap/issues/4685 cmd.build(); - cmd.find_subcommand_mut("check") - .expect("`check` subcommand not found") + Ok(cmd + .find_subcommand_mut(subcommand) + .with_context(|| format!("Unable to find subcommand `{subcommand}`"))? .render_help() - .to_string() + .to_string()) } #[cfg(test)] diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index b0a06b9ad99caa..e627cc2a699f58 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -20,7 +20,7 @@ use crate::rules::flake8_implicit_str_concat::settings::Settings; /// negatively affects code readability. /// /// In some cases, the implicit concatenation may also be unintentional, as -/// autoformatters are capable of introducing single-line implicit +/// code formatters are capable of introducing single-line implicit /// concatenations when collapsing long lines. /// /// ## Example diff --git a/docs/configuration.md b/docs/configuration.md index e97374b62014af..d994ec3047a992 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,8 +2,10 @@ Ruff can be configured through a `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file. -For a complete enumeration of the available configuration options, see -[_Settings_](settings.md). +Whether you're using Ruff as a linter, formatter, or both, the underlying configuration strategy and +semantics are the same. + +For a complete enumeration of the available configuration options, see [_Settings_](settings.md). ## Using `pyproject.toml` @@ -11,16 +13,6 @@ If left unspecified, Ruff's default configuration is equivalent to: ```toml [tool.ruff] -# Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -select = ["E", "F"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - # Exclude a variety of commonly ignored directories. exclude = [ ".bzr", @@ -45,22 +37,43 @@ exclude = [ "node_modules", "venv", ] -per-file-ignores = {} # Same as Black. line-length = 88 +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default. +select = ["E", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -# Assume Python 3.8 -target-version = "py38" +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +magic-trailing-comma = "respect" + +# Use Unix-style line endings. +line-ending = "lf" ``` As an example, the following would configure Ruff to: ```toml -[tool.ruff] +[tool.ruff.lint] # 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults. select = ["E", "F", "B"] @@ -70,47 +83,28 @@ ignore = ["E501"] # 3. Avoid trying to fix flake8-bugbear (`B`) violations. unfixable = ["B"] -# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. -[tool.ruff.per-file-ignores] +# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories. +[tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402"] -"path/to/file.py" = ["E402"] "**/{tests,docs,tools}/*" = ["E402"] + +[tool.ruff.format] +# 5. Use single quotes for non-triple-quoted strings. +quote-style = "single" ``` -Plugin configurations should be expressed as subsections, e.g.: +Linter plugin configurations are expressed as subsections, e.g.: ```toml -[tool.ruff] +[tool.ruff.lint] # Add "Q" to the list of enabled codes. select = ["E", "F", "Q"] -[tool.ruff.flake8-quotes] +[tool.ruff.lint.flake8-quotes] docstring-quotes = "double" ``` -For a complete enumeration of the available configuration options, see -[_Settings_](settings.md). - -Ruff mirrors Flake8's rule code system, in which each rule code consists of a one-to-three letter -prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the rule -(e.g., `F` for Pyflakes, `E` for pycodestyle, `ANN` for flake8-annotations). The set of enabled -rules is determined by the `select` and `ignore` options, which accept either the full code (e.g., -`F401`) or the prefix (e.g., `F`). - -As a special-case, Ruff also supports the `ALL` code, which enables all rules. Note that some -pydocstyle rules conflict (e.g., `D203` and `D211`) as they represent alternative docstring -formats. Ruff will automatically disable any conflicting rules when `ALL` is enabled. - -If you're wondering how to configure Ruff, here are some **recommended guidelines**: - -- Prefer `select` and `ignore` over `extend-select` and `extend-ignore`, to make your rule set - explicit. -- Use `ALL` with discretion. Enabling `ALL` will implicitly enable new rules whenever you upgrade. -- Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example, - you might consider expanding to `select = ["E", "F", "B"]` to enable the popular flake8-bugbear - extension. -- By default, Ruff's fixes are aggressive. If you find that it's too aggressive for your liking, - consider turning off fixes for specific rules or categories (see [_FAQ_](faq.md#ruff-tried-to-fix-something--but-it-broke-my-code)). +For a complete enumeration of the available configuration options, see [_Settings_](settings.md). ## Using `ruff.toml` @@ -122,6 +116,7 @@ For example, the `pyproject.toml` described above would be represented via the f `ruff.toml` (or `.ruff.toml`): ```toml +[lint] # Enable flake8-bugbear (`B`) rules. select = ["E", "F", "B"] @@ -131,14 +126,90 @@ ignore = ["E501"] # Avoid trying to fix flake8-bugbear (`B`) violations. unfixable = ["B"] -# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. -[per-file-ignores] +# Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories. +[lint.per-file-ignores] "__init__.py" = ["E402"] -"path/to/file.py" = ["E402"] +"**/{tests,docs,tools}/*" = ["E402"] + +[format] +# Use single quotes for non-triple-quoted strings. +quote-style = "single" ``` For a complete enumeration of the available configuration options, see [_Settings_](settings.md). +## `pyproject.toml` discovery + +Similar to [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy), +Ruff supports hierarchical configuration, such that the "closest" `pyproject.toml` file in the +directory hierarchy is used for every individual file, with all paths in the `pyproject.toml` file +(e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing that +`pyproject.toml` file. + +There are a few exceptions to these rules: + +1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any + `pyproject.toml` files that lack a `[tool.ruff]` section. +1. If a configuration file is passed directly via `--config`, those settings are used for _all_ + analyzed. files, and any relative paths in that configuration file (like `exclude` globs or + `src` paths) are resolved relative to the _current_ working directory. +1. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using + a default configuration. If a user-specific configuration file exists + at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default + configuration, with `${config_dir}` being determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) + crate, and all relative paths being again resolved relative to the _current working directory_. +1. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via + `--select`) will override the settings in _every_ resolved configuration file. + +Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy), +Ruff does not merge settings across configuration files; instead, the "closest" configuration file +is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff +supports an [`extend`](settings.md#extend) field, which allows you to inherit the settings from another +`pyproject.toml` file, like so: + +```toml +[tool.ruff] +# Extend the `pyproject.toml` file in the parent directory... +extend = "../pyproject.toml" + +# ...but use a different line length. +line-length = 100 +``` + +All of the above rules apply equivalently to `ruff.toml` and `.ruff.toml` files. If Ruff detects +multiple configuration files in the same directory, the `.ruff.toml` file will take precedence over +the `ruff.toml` file, and the `ruff.toml` file will take precedence over the `pyproject.toml` file. + +## Python file discovery + +When passed a path on the command-line, Ruff will automatically discover all Python files in that +path, taking into account the [`exclude`](settings.md#exclude) and [`extend-exclude`](settings.md#extend-exclude) +settings in each directory's `pyproject.toml` file. + +By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`, +`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](settings.md#respect-gitignore)). + +Files that are passed to `ruff` directly are always linted, regardless of the above criteria. +For example, `ruff check /path/to/excluded/file.py` will always lint `file.py`. + +## Jupyter Notebook discovery + +Ruff has built-in support for linting [Jupyter Notebooks](https://jupyter.org/). + +To opt in to linting Jupyter Notebook (`.ipynb`) files, add the `*.ipynb` pattern to your +[`include`](settings.md#include) setting, like so: + +```toml +[tool.ruff] +include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] +``` + +This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified +directories, and lint them accordingly. + +Alternatively, pass the notebook file(s) to `ruff` on the command-line directly. For example, +`ruff check /path/to/notebook.ipynb` will always lint `notebook.ipynb`. + ## Command-line interface Some configuration options can be provided via the command-line, such as those related to rule @@ -181,7 +252,7 @@ For help with a specific command, see: `ruff help `. Or `ruff help check` for more on the linting command: - + ```text Run Ruff on the given files or directories (default) @@ -271,210 +342,40 @@ Log levels: -s, --silent Disable all logging (but still exit with status code "1" upon detecting diagnostics) ``` - + -## `pyproject.toml` discovery +Or `ruff help format` for more on the formatting command: -Similar to [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy), -Ruff supports hierarchical configuration, such that the "closest" `pyproject.toml` file in the -directory hierarchy is used for every individual file, with all paths in the `pyproject.toml` file -(e.g., `exclude` globs, `src` paths) being resolved relative to the directory containing that -`pyproject.toml` file. - -There are a few exceptions to these rules: - -1. In locating the "closest" `pyproject.toml` file for a given path, Ruff ignores any - `pyproject.toml` files that lack a `[tool.ruff]` section. -1. If a configuration file is passed directly via `--config`, those settings are used for across - files. Any relative paths in that configuration file (like `exclude` globs or `src` paths) are - resolved relative to the _current working directory_. -1. If no `pyproject.toml` file is found in the filesystem hierarchy, Ruff will fall back to using - a default configuration. If a user-specific configuration file exists - at `${config_dir}/ruff/pyproject.toml`, that file will be used instead of the default - configuration, with `${config_dir}` being determined via the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) - crate, and all relative paths being again resolved relative to the _current working directory_. -1. Any `pyproject.toml`-supported settings that are provided on the command-line (e.g., via - `--select`) will override the settings in _every_ resolved configuration file. - -Unlike [ESLint](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#cascading-and-hierarchy), -Ruff does not merge settings across configuration files; instead, the "closest" configuration file -is used, and any parent configuration files are ignored. In lieu of this implicit cascade, Ruff -supports an [`extend`](settings.md#extend) field, which allows you to inherit the settings from another -`pyproject.toml` file, like so: - -```toml -# Extend the `pyproject.toml` file in the parent directory. -extend = "../pyproject.toml" -# But use a different line length. -line-length = 100 -``` - -All of the above rules apply equivalently to `ruff.toml` and `.ruff.toml` files. If Ruff detects -multiple configuration files in the same directory, the `.ruff.toml` file will take precedence over -the `ruff.toml` file, and the `ruff.toml` file will take precedence over the `pyproject.toml` file. - -## Python file discovery - -When passed a path on the command-line, Ruff will automatically discover all Python files in that -path, taking into account the [`exclude`](settings.md#exclude) and -[`extend-exclude`](settings.md#extend-exclude) settings in each directory's -`pyproject.toml` file. - -By default, Ruff will also skip any files that are omitted via `.ignore`, `.gitignore`, -`.git/info/exclude`, and global `gitignore` files (see: [`respect-gitignore`](settings.md#respect-gitignore)). - -Files that are passed to `ruff` directly are always linted, regardless of the above criteria. -For example, `ruff check /path/to/excluded/file.py` will always lint `file.py`. - -## Jupyter Notebook discovery - -Ruff has built-in support for linting [Jupyter Notebooks](https://jupyter.org/). - -To opt in to linting Jupyter Notebook (`.ipynb`) files, add the `*.ipynb` pattern to your -[`include`](settings.md#include) setting, like so: - -```toml -[tool.ruff] -include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] -``` + -This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified -directories, and lint them accordingly. - -Alternatively, pass the notebook file(s) to `ruff` on the command-line directly. For example, -`ruff check /path/to/notebook.ipynb` will always lint `notebook.ipynb`. - -## Rule selection - -The set of enabled rules is controlled via the [`select`](settings.md#select) and -[`ignore`](settings.md#ignore) settings, along with the -[`extend-select`](settings.md#extend-select) and -[`extend-ignore`](settings.md#extend-ignore) modifiers. - -To resolve the enabled rule set, Ruff may need to reconcile `select` and `ignore` from a variety -of sources, including the current `pyproject.toml`, any inherited `pyproject.toml` files, and the -CLI (e.g., `--select`). - -In those scenarios, Ruff uses the "highest-priority" `select` as the basis for the rule set, and -then applies any `extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options are given -higher priority than `pyproject.toml` options, and the current `pyproject.toml` file is given higher -priority than any inherited `pyproject.toml` files. - -For example, given the following `pyproject.toml` file: - -```toml -[tool.ruff] -select = ["E", "F"] -ignore = ["F401"] -``` - -Running `ruff check --select F401` would result in Ruff enforcing `F401`, and no other rules. - -Running `ruff check --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, -with the exception of `F401`. - -## Error suppression - -To omit a lint rule entirely, add it to the "ignore" list via [`ignore`](settings.md#ignore) -or [`extend-ignore`](settings.md#extend-ignore), either on the command-line -or in your `pyproject.toml` file. - -To ignore a violation inline, Ruff uses a `noqa` system similar to -[Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html). To ignore an individual -violation, add `# noqa: {code}` to the end of the line, like so: - -```python -# Ignore F841. -x = 1 # noqa: F841 - -# Ignore E741 and F841. -i = 1 # noqa: E741, F841 - -# Ignore _all_ violations. -x = 1 # noqa -``` - -For multi-line strings (like docstrings), -the `noqa` directive should come at the end of the string (after the closing triple quote), -and will apply to the entire string, like so: +```text +Run the Ruff formatter on the given files or directories -```python -"""Lorem ipsum dolor sit amet. +Usage: ruff format [OPTIONS] [FILES]... -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor. -""" # noqa: E501 -``` +Arguments: + [FILES]... List of files or directories to format -To ignore all violations across an entire file, add `# ruff: noqa` to any line in the file, like so: +Options: + --check Avoid writing any formatted files back; instead, exit with a non-zero status code if any files would have been modified, and zero otherwise + --config Path to the `pyproject.toml` or `ruff.toml` file to use for configuration + -h, --help Print help -```python -# ruff: noqa -``` +File selection: + --respect-gitignore Respect file exclusions via `.gitignore` and other standard ignore files. Use `--no-respect-gitignore` to disable + --force-exclude Enforce exclusions, even for paths passed to Ruff directly on the command-line. Use `--no-force-exclude` to disable -To ignore a specific rule across an entire file, add `# ruff: noqa: {code}` to any line in the file, -like so: +Miscellaneous: + --isolated Ignore all configuration files + --stdin-filename The name of the file when passing it through stdin -```python -# ruff: noqa: F841 +Log levels: + -v, --verbose Enable verbose logging + -q, --quiet Print diagnostics, but nothing else + -s, --silent Disable all logging (but still exit with status code "1" upon detecting diagnostics) ``` -Or see the [`per-file-ignores`](settings.md#per-file-ignores) configuration -setting, which enables the same functionality via a `pyproject.toml` file. - -Note that Ruff will also respect Flake8's `# flake8: noqa` directive, and will treat it as -equivalent to `# ruff: noqa`. - -### Automatic `noqa` management - -Ruff supports several workflows to aid in `noqa` management. - -First, Ruff provides a special rule code, `RUF100`, to enforce that your `noqa` directives are -"valid", in that the violations they _say_ they ignore are actually being triggered on that line -(and thus suppressed). You can run `ruff check /path/to/file.py --extend-select RUF100` to flag -unused `noqa` directives. - -Second, Ruff can _automatically remove_ unused `noqa` directives via its fix functionality. -You can run `ruff check /path/to/file.py --extend-select RUF100 --fix` to automatically remove -unused `noqa` directives. - -Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when -migrating a new codebase to Ruff. You can run `ruff check /path/to/file.py --add-noqa` to -automatically add `noqa` directives to all failing lines, with the appropriate rule codes. - -### Action comments - -Ruff respects isort's [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html) -(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which -enable selectively enabling and disabling import sorting for blocks of code and other inline -configuration. - -Ruff will also respect variants of these action comments with a `# ruff:` prefix -(e.g., `# ruff: isort: skip_file`, `# ruff: isort: on`, and so on). These variants more clearly -convey that the action comment is intended for Ruff, but are functionally equivalent to the -isort variants. - -See the [isort documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html) -for more. - -## Exit codes - -By default, Ruff exits with the following status codes: - -- `0` if no violations were found, or if all present violations were fixed automatically. -- `1` if violations were found. -- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an - internal error. - -This convention mirrors that of tools like ESLint, Prettier, and RuboCop. - -Ruff supports two command-line flags that alter its exit code behavior: - -- `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found. - Note that Ruff will still exit with a status code of `2` if it terminates abnormally. -- `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were - found, _even if_ all such violations were fixed automatically. Note that the use of - `--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after - fixing. + ## Shell autocompletion diff --git a/docs/faq.md b/docs/faq.md index fbf9265be84665..8c37387d225f07 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,12 +1,12 @@ # FAQ -## Is Ruff compatible with Black? +## Is the Ruff linter compatible with Black? -Yes. Ruff is compatible with [Black](https://github.com/psf/black) out-of-the-box, as long as -the `line-length` setting is consistent between the two. +Yes. The Ruff linter is compatible with [Black](https://github.com/psf/black) out-of-the-box, as +long as the `line-length` setting is consistent between the two. -As a project, Ruff is designed to be used alongside Black and, as such, will defer implementing -stylistic lint rules that are obviated by autoformatting. +Ruff is designed to be used alongside a formatter (like Ruff's own formatter, or Black) and, as +such, will defer implementing stylistic rules that are obviated by autoformatting. Note that Ruff and Black treat line-length enforcement a little differently. Black makes a best-effort attempt to adhere to the `line-length`, but avoids automatic line-wrapping in some cases @@ -14,10 +14,21 @@ best-effort attempt to adhere to the `line-length`, but avoids automatic line-wr the `line-length` setting. As such, if `E501` is enabled, Ruff can still trigger line-length violations even when Black is enabled. -## How does Ruff compare to Flake8? +## How does Ruff's formatter compare to Black? -(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to -automatically convert your existing configuration.) +The Ruff formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black). + +Specifically, the formatter is intended to emit near-identical output when run over Black-formatted +code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines +are formatted identically. When migrating an existing project from Black to Ruff, you should expect +to see a few differences on the margins, but the vast majority of your code should be unchanged. + +When run over _non_-Black-formatted code, the formatter makes some different decisions than Black, +and so more deviations should be expected, especially around the treatment of end-of-line comments. + +See [_Black compatibility_](formatter.md#black-compatibility) for more. + +## How does Ruff's linter compare to Flake8? Ruff can be used as a drop-in replacement for Flake8 when used (1) without or with a small number of plugins, (2) alongside Black, and (3) on Python 3 code. @@ -98,10 +109,10 @@ There are a few other minor incompatibilities between Ruff and the originating F code. (This is often solved by modifying the `src` property, e.g., to `src = ["src"]`, if your code is nested in a `src` directory.) -## How does Ruff compare to Pylint? +## How does Ruff's linter compare to Pylint? -At time of writing, Pylint implements ~409 total rules, while Ruff implements 440, of which at least -89 overlap with the Pylint rule set (you can find the mapping in [#970](https://github.com/astral-sh/ruff/issues/970)). +At time of writing, Pylint implements ~409 total rules, while Ruff implements over 700, of which at +least 172 overlap with the Pylint rule set (see: [#970](https://github.com/astral-sh/ruff/issues/970)). Pylint implements many rules that Ruff does not, and vice versa. For example, Pylint does more type inference than Ruff (e.g., Pylint can validate the number of arguments in a function call). As such, @@ -182,13 +193,18 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [pydocstyle](https://pypi.org/project/pydocstyle/) - [tryceratops](https://pypi.org/project/tryceratops/) -Ruff can also replace [isort](https://pypi.org/project/isort/), +Ruff can also replace [Black](https://pypi.org/project/black/), [isort](https://pypi.org/project/isort/), [yesqa](https://github.com/asottile/yesqa), [eradicate](https://pypi.org/project/eradicate/), and most of the rules implemented in [pyupgrade](https://pypi.org/project/pyupgrade/). If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, feel free to file an [issue](https://github.com/astral-sh/ruff/issues/new). +## Do I have to use Ruff's linter and formatter together? + +Nope! Ruff's linter and Formatter can be used independently of one another -- you can use +Ruff as a formatter, but not a linter, or vice versa. + ## What versions of Python does Ruff support? Ruff can lint code for any Python version from 3.7 onwards, including Python 3.12. @@ -209,7 +225,7 @@ pip install ruff Ruff ships with wheels for all major platforms, which enables `pip` to install Ruff without relying on Rust at all. -## Can I write my own plugins for Ruff? +## Can I write my own linter plugins for Ruff? Ruff does not yet support third-party plugins, though a plugin system is within-scope for the project. See [#283](https://github.com/astral-sh/ruff/issues/283) for more. @@ -334,7 +350,8 @@ include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] ``` This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified -directories, and lint them accordingly. +directories, and lint them accordingly. Formatter support is not yet available for Jupyter +Notebooks. Alternatively, pass the notebook file(s) to `ruff` on the command-line directly. For example, `ruff check /path/to/notebook.ipynb` will always lint `notebook.ipynb`. @@ -384,7 +401,7 @@ matter how they're provided, which avoids accidental incompatibilities and simpl By default, no `convention` is set, and so the enabled rules are determined by the `select` setting alone. -## What is preview? +## What is "preview"? Preview enables a collection of newer rules and fixes that are considered experimental or unstable. See the [preview documentation](preview.md) for more details; or, to see which rules are currently @@ -394,7 +411,7 @@ in preview, visit the [rules reference](rules.md). Run `ruff check /path/to/code.py --show-settings` to view the resolved settings for a given file. -## I want to use Ruff, but I don't want to use `pyproject.toml`. Is that possible? +## I want to use Ruff, but I don't want to use `pyproject.toml`. What are my options? Yes! In lieu of a `pyproject.toml` file, you can use a `ruff.toml` file for configuration. The two files are functionally equivalent and have an identical schema, with the exception that a `ruff.toml` @@ -434,7 +451,7 @@ On Windows, Ruff expects that file to be located at `C:\Users\Alice\AppData\Roam For more, see the [`dirs`](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) crate. -## Ruff tried to fix something — but it broke my code? +## Ruff tried to fix something — but it broke my code. What's going on? Ruff's fixes are a best-effort mechanism. Given the dynamic nature of Python, it's difficult to have _complete_ certainty when making changes to code, even for the seemingly trivial fixes. diff --git a/docs/formatter.md b/docs/formatter.md new file mode 100644 index 00000000000000..e0d1577051aa16 --- /dev/null +++ b/docs/formatter.md @@ -0,0 +1,653 @@ +# The Ruff Formatter + +The Ruff formatter is an extremely fast Python code formatter designed as a drop-in replacement for +[Black](https://pypi.org/project/black/), available as part of the `ruff` CLI (as of Ruff v0.0.289). + +## `ruff format` + +`ruff format` is the primary entrypoint for the formatter. It accepts a list of files or +directories, and formats all discovered Python files: + +```shell +ruff format . # Format all files in the current directory. +ruff format /path/to/file.py # Format a single file. +``` + +Similar to Black, running `ruff format /path/to/file.py` will format the given file or directory +in-place, while `ruff format --check /path/to/file.py` will avoid writing any formatted files back, +instead exiting with a non-zero status code if any files are not already formatted. + +For the full list of supported options, run `ruff format --help`. + +## Philosophy + +The goal of the Ruff Formatter is _not_ to innovate on code style, but rather, to innovate on +performance, and provide a unified toolchain across Ruff's linter, formatter, and any and all +future tools. + +As such, the formatter is designed as a drop-in replacement for [Black](https://github.com/psf/black), +but with an excessive focus on performance and direct integration with Ruff. Given Black's +popularity within the Python ecosystem, targeting Black compatibility ensures that formatter +adoption is minimally disruptive for the vast majority of projects. + +Specifically, the formatter is intended to emit near-identical output when run over existing +Black-formatted code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% +of lines are formatted identically. (See: [Black compatibility](#black-compatibility).) + +Given this focus on Black compatibility, the formatter thus adheres to [Black's (stable) code style](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html), +which aims for "consistency, generality, readability and reducing git diffs". To give you a sense +for the enforced code style, here's an example: + +```python +# Input +def _make_ssl_transport( + rawsock, protocol, sslcontext, waiter=None, + *, server_side=False, server_hostname=None, + extra=None, server=None, + ssl_handshake_timeout=None, + call_connection_made=True): + '''Make an SSL transport.''' + if waiter is None: + waiter = Future(loop=loop) + + if extra is None: + extra = {} + + ... + +# Ruff +def _make_ssl_transport( + rawsock, + protocol, + sslcontext, + waiter=None, + *, + server_side=False, + server_hostname=None, + extra=None, + server=None, + ssl_handshake_timeout=None, + call_connection_made=True, +): + """Make an SSL transport.""" + if waiter is None: + waiter = Future(loop=loop) + + if extra is None: + extra = {} + + ... +``` + +Like Black, the Ruff Formatter does _not_ support extensive code style configuration; however, +unlike Black, it _does_ support configuring the desired quote style, indent style, line endings, +and more. (See: [_Configuration_](#configuration).) + +While the formatter is designed to be a drop-in replacement for Black, it is not intended to be +used interchangeably with Black on an ongoing basis, as the formatter _does_ differ from +Black in a few conscious ways (see: [intentional deviations](#intentional-deviations)). In general, +deviations are limited to cases in which Ruff's behavior was deemed more consistent, or +significantly simpler to support (with negligible end-user impact) given the differences in the +underlying implementations between Black and Ruff. + +Going forward, the Ruff Formatter will support Black's preview style under Ruff's own +[preview](preview.md) mode. + +## Configuration + +The Ruff Formatter exposes a small set of configuration options, some of which are also supported +by Black (like line width), some of which are unique to Ruff (like quote and indentation style). + +For example, to configure the formatter to use double single quotes, a line width of 100, and +tab indentation, add the following to your `pyproject.toml`: + +```toml +[tool.ruff.format] +line-length = 100 +quote-style = "single" +indent-style = "tab" +``` + +For more on configuring Ruff via `pyproject.toml`, see [_Configuring Ruff_](configuration.md). + +Given the focus on Black compatibility (and unlike formatters like [YAPF](https://github.com/google/yapf)), +Ruff does not currently expose any configuration options to modify core formatting behavior outside +of these trivia-related settings. + +## Format suppression + +Like Black, Ruff supports `# fmt: on`, `# fmt: off`, and `# fmt: skip` pragma comments, which can +be used to temporarily disable formatting for a given code block. + +`# fmt: on` and `# fmt: off` comments are enforced at the statement level: + +```python +# fmt: off +not_formatted=3 +also_not_formatted=4 +# fmt: on +``` + +As such, adding `# fmt: on` and `# fmt: off` comments within expressions will have no effect. In +the following example, both list entries will be formatted, despite the `# fmt: off`: + +```python +[ + # fmt: off + '1', + # fmt: on + '2', +] +``` + +Instead, apply the `# fmt: off` comment to the entire statement: + +```python +# fmt: off +[ + '1', + '2', +] +# fmt: on +``` + +`# fmt: skip` comments suppress formatting for a preceding statement, case header, decorator, +function definition, or class definition: + +```python +if True: + pass +elif False: # fmt: skip + pass + +@Test +@Test2 # fmt: off +def test(): ... + +a = [1, 2, 3, 4, 5] # fmt: off + +def test(a, b, c, d, e, f) -> int: # fmt: skip + pass +``` + +Like Black, Ruff will _also_ recognize [YAPF](https://github.com/google/yapf)'s `# yapf: disable` and `# yapf: enable` pragma +comments, which are treated equivalently to `# fmt: off` and `# fmt: on`, respectively. + +## Conflicting lint rules + +Ruff's formatter is designed to be used alongside the linter. However, the linter includes +some rules that, when enabled, can cause conflicts with the formatter, leading to unexpected +behavior. + +When using Ruff as a formatter, we recommend disabling the following rules: + +- [`line-too-long`](rules/line-too-long.md) (`E501`) +- [`single-line-implicit-string-concatenation`](rules/single-line-implicit-string-concatenation.md) (`ISC001`) +- [`missing-trailing-comma`](rules/missing-trailing-comma.md) (`COM812`) +- [`prohibited-trailing-comma`](rules/prohibited-trailing-comma.md) (`COM819`) +- [`bad-quotes-inline-string`](rules/bad-quotes-inline-string.md) (`Q000`) +- [`bad-quotes-multiline-string`](rules/bad-quotes-multiline-string.md) (`Q001`) +- [`bad-quotes-docstring`](rules/bad-quotes-docstring.md) (`Q002`) +- [`avoidable-escaped-quote`](rules/avoidable-escaped-quote.md) (`Q003`) + +Similarly, we recommend disabling the following isort settings, which are incompatible with the +formatter's treatment of import statements when set to non-default values: + +- [`lines-after-imports`](https://docs.astral.sh/ruff/settings/#isort-lines-after-imports) +- [`lines-between-types`](https://docs.astral.sh/ruff/settings/#isort-lines-between-types) + +## Exit codes + +`ruff format` exits with the following status codes: + +- `0` if Ruff terminates successfully, regardless of whether any files were formatted. +- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an + internal error. + +Meanwhile, `ruff format --check` exits with the following status codes: + +- `0` if Ruff terminates successfully, and no files would be formatted if `--check` were not + specified. +- `1` if Ruff terminates successfully, and one or more files would be formatted if `--check` were + not specified. +- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an + internal error. + +## Black compatibility + +The formatter is designed to be a drop-in replacement for [Black](https://github.com/psf/black). + +Specifically, the formatter is intended to emit near-identical output when run over Black-formatted +code. When run over extensive Black-formatted projects like Django and Zulip, > 99.9% of lines +are formatted identically. When migrating an existing project from Black to Ruff, you should expect +to see a few differences on the margins, but the vast majority of your code should be unchanged. + +When run over _non_-Black-formatted code, the formatter makes some different decisions than Black, +and so more deviations should be expected, especially around the treatment of end-of-line comments. + +If you identify deviations in your project, spot-check them against the [intentional deviations](#intentional-deviations) +enumerated below, as well as the [unintentional deviations](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter) +filed in the issue tracker. If you've identified a new deviation, please [file an issue](https://github.com/astral-sh/ruff/issues/new). + +### Preview style + +Black gates formatting changes behind a [`preview`](https://black.readthedocs.io/en/stable/the_black_code_style/future_style.html#preview-style) +flag. The formatter does not yet support Black's preview style, though the intention is to support +it within the coming months. + +### Intentional deviations + +This section enumerates the known, intentional deviations between the Ruff formatter and Black's +stable style. (Unintentional deviations are tracked in the [issue tracker](https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter).) + +

Trailing end-of-line comments

+ +Black's priority is to fit an entire statement on a line, even if it contains end-of-line comments. +In such cases, Black collapses the statement, and moves the comment to the end of the collapsed +statement: + +```python +# Input +while ( + cond1 # almost always true + and cond2 # almost never true +): + print("Do something") + +# Black +while cond1 and cond2: # almost always true # almost never true + print("Do something") +``` + +Ruff, like [Prettier](https://prettier.io/), expands any statement that contains trailing +end-of-line comments. For example, Ruff would avoid collapsing the `while` test in the snippet +above. This ensures that the comments remain close to their original position and retain their +original intent, at the cost of retaining additional vertical space. + +This deviation only impacts unformatted code, in that Ruff's output should not deviate for code that +has already been formatted by Black. + +

Pragma comments are ignored when computing line width

+ +Pragma comments (`# type`, `# noqa`, `# pyright`, `# pylint`, etc.) are ignored when computing the width of a line. +This prevents Ruff from moving pragma comments around, thereby modifying their meaning and behavior: + +See Ruff's [pragma comment handling proposal](https://github.com/astral-sh/ruff/discussions/6670) +for details. + +This is similar to [Pyink](https://github.com/google/pyink) but a deviation from Black. Black avoids +splitting any lines that contain a `# type` comment ([#997](https://github.com/psf/black/issues/997)), +but otherwise avoids special-casing pragma comments. + +As Ruff expands trailing end-of-line comments, Ruff will also avoid moving pragma comments in cases +like the following, where moving the `# noqa` to the end of the line causes it to suppress errors +on both `first()` and `second()`: + +```python +# Input +[ + first(), # noqa + second() +] + +# Black +[first(), second()] # noqa + +# Ruff +[ + first(), # noqa + second(), +] +``` + +

Line width vs. line length

+ +Ruff uses the Unicode width of a line to determine if a line fits. Black's stable style uses +character width, while Black's preview style uses Unicode width for strings ([#3445](https://github.com/psf/black/pull/3445)), +and character width for all other tokens. Ruff's behavior is closer to Black's preview style than +Black's stable style, although Ruff _also_ uses Unicode width for identifiers and comments. + +

Walruses in slice expressions

+ +Black avoids inserting space around `:=` operators within slices. For example, the following adheres +to Black stable style: + +```python +# Input +x[y:=1] + +# Black +x[y:=1] +``` + +Ruff will instead add space around the `:=` operator: + +```python +# Input +x[y:=1] + +# Ruff +x[y := 1] +``` + +This will likely be incorporated into Black's preview style ([#3823](https://github.com/psf/black/pull/3823)). + +

global and nonlocal names are broken across multiple lines by continuations

+ +If a `global` or `nonlocal` statement includes multiple names, and exceeds the configured line +width, Ruff will break them across multiple lines using continuations: + +```python +# Input +global analyze_featuremap_layer, analyze_featuremapcompression_layer, analyze_latencies_post, analyze_motions_layer, analyze_size_model + +# Ruff +global \ + analyze_featuremap_layer, \ + analyze_featuremapcompression_layer, \ + analyze_latencies_post, \ + analyze_motions_layer, \ + analyze_size_model +``` + +

Newlines are inserted after all class docstrings

+ +Black typically enforces a single newline after a class docstring. However, it does not apply such +formatting if the docstring is single-quoted rather than triple-quoted, while Ruff enforces a +single newline in both cases: + +```python +# Input +class IntFromGeom(GEOSFuncFactory): + "Argument is a geometry, return type is an integer." + argtypes = [GEOM_PTR] + restype = c_int + errcheck = staticmethod(check_minus_one) + +# Black +class IntFromGeom(GEOSFuncFactory): + "Argument is a geometry, return type is an integer." + argtypes = [GEOM_PTR] + restype = c_int + errcheck = staticmethod(check_minus_one) + +# Ruff +class IntFromGeom(GEOSFuncFactory): + "Argument is a geometry, return type is an integer." + + argtypes = [GEOM_PTR] + restype = c_int + errcheck = staticmethod(check_minus_one) +``` + +

Trailing own-line comments on imports are not moved to the next line

+ +Black enforces a single empty line between an import and a trailing own-line comment. Ruff leaves +such comments in-place: + +```python +# Input +import os +# comment + +import sys + +# Black +import os + +# comment + +import sys + +# Ruff +import os +# comment + +import sys +``` + +

Parentheses around awaited collections are not preserved

+ +Black preserves parentheses around awaited collections: + +```python +await ([1, 2, 3]) +``` + +Ruff will instead remove them: + +```python +await [1, 2, 3] +``` + +This is more consistent to the formatting of other awaited expressions: Ruff and Black both +remove parentheses around, e.g., `await (1)`, only retaining them when syntactically required, +as in, e.g., `await (x := 1)`. + +

Implicit string concatenations in attribute accesses

+ +Given the following unformatted code: + +```python +print("aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb)) +``` + +Internally, Black's logic will first expand the outermost `print` call: + +```python +print( + "aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb) +) +``` + +Since the argument is _still_ too long, Black will then split on the operator with the highest split +precedence. In this case, Black splits on the implicit string concatenation, to produce the +following Black-formatted code: + +```python +print( + "aaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaa".format(bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb) +) +``` + +Ruff gives implicit concatenations a "lower" priority when breaking lines. As a result, Ruff +would instead format the above as: + +```python +print( + "aaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaa".format( + bbbbbbbbbbbbbbbbbb + bbbbbbbbbbbbbbbbbb + ) +) +``` + +In general, Black splits implicit string concatenations over multiple lines more often than Ruff, +even if those concatenations _can_ fit on a single line. Ruff instead avoids splitting such +concatenations unless doing so is necessary to fit within the configured line width. + +

Own-line comments on expressions don't cause the expression to expand

+ +Given an expression like: + +```python +( + # A comment in the middle + some_example_var and some_example_var not in some_example_var +) +``` + +Black associates the comment with `some_example_var`, thus splitting it over two lines: + +```python +( + # A comment in the middle + some_example_var + and some_example_var not in some_example_var +) +``` + +Ruff will instead associate the comment with the entire boolean expression, thus preserving the +initial formatting: + +```python +( + # A comment in the middle + some_example_var and some_example_var not in some_example_var +) +``` + +

Tuples are parenthesized when expanded

+ +Ruff tends towards parenthesizing tuples (with a few exceptions), while Black tends to remove tuple +parentheses more often. + +In particular, Ruff will always insert parentheses around tuples that expand over multiple lines: + +```python +# Input +(a, b), (c, d,) + +# Black +(a, b), ( + c, + d, +) + +# Ruff +( + (a, b), + ( + c, + c, + ), +) +``` + +There's one exception here. In `for` loops, both Ruff and Black will avoid inserting unnecessary +parentheses: + +```python +# Input +for a, f(b,) in c: + pass + +# Black +for a, f( + b, +) in c: + pass + +# Ruff +for a, f( + b, +) in c: + pass +``` + +

Single-element tuples are always parenthesized

+ +Ruff always inserts parentheses around single-element tuples, while Black will omit them in some +cases: + +```python +# Input +(a, b), + +# Black +(a, b), + +# Ruff +((a, b),) +``` + +Adding parentheses around single-element tuples adds visual distinction and helps avoid "accidental" +tuples created by extraneous trailing commas (see, e.g., [#17181](https://github.com/django/django/pull/17181)). + +

Trailing commas are inserted when expanding a function definition with a single argument

+ +When a function definition with a single argument is expanded over multiple lines, Black +will add a trailing comma in some cases, depending on whether the argument includes a type +annotation and/or a default value. + +For example, Black will add a trailing comma to the first and second function definitions below, +but not the third: + +```python +def func( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, +) -> None: + ... + + +def func( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=1, +) -> None: + ... + + +def func( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: Argument( + "network_messages.pickle", + help="The path of the pickle file that will contain the network messages", + ) = 1 +) -> None: + ... +``` + +Ruff will instead insert a trailing comma in all such cases for consistency. + +

Parentheses around call-chain assignment values are not preserved

+ +Given: + +```python +def update_emission_strength(): + ( + get_rgbw_emission_node_tree(self) + .nodes["Emission"] + .inputs["Strength"] + .default_value + ) = (self.emission_strength * 2) +``` + +Black will preserve the parentheses in `(self.emission_strength * 2)`, whereas Ruff will remove +them. + +Both Black and Ruff remove such parentheses in simpler assignments, like: + +```python +# Input +def update_emission_strength(): + value = (self.emission_strength * 2) + +# Black +def update_emission_strength(): + value = self.emission_strength * 2 + +# Ruff +def update_emission_strength(): + value = self.emission_strength * 2 +``` + +

Type annotations may be parenthesized when expanded

+ +Black will avoid parenthesizing type annotations in an annotated assignment, while Ruff will insert +parentheses in some cases. + +For example: + +```python +# Black +StartElementHandler: Callable[[str, dict[str, str]], Any] | Callable[[str, list[str]], Any] | Callable[ + [str, dict[str, str], list[str]], Any +] | None + +# Ruff +StartElementHandler: ( + Callable[[str, dict[str, str]], Any] + | Callable[[str, list[str]], Any] + | Callable[[str, dict[str, str], list[str]], Any] + | None +) +``` diff --git a/docs/installation.md b/docs/installation.md index 259946f85180e8..6456ade624ff94 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -6,6 +6,13 @@ Ruff is available as [`ruff`](https://pypi.org/project/ruff/) on PyPI: pip install ruff ``` +Once installed, you can run Ruff from the command line: + +```shell +ruff check . # Lint all files in the current directory. +ruff format . # Format all files in the current directory. +``` + For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff`](https://formulae.brew.sh/formula/ruff) on Homebrew: diff --git a/docs/editor-integrations.md b/docs/integrations.md similarity index 78% rename from docs/editor-integrations.md rename to docs/integrations.md index ce76e01fa39865..d7ad9deeaf0dab 100644 --- a/docs/editor-integrations.md +++ b/docs/integrations.md @@ -1,4 +1,4 @@ -# Editor Integrations +# Integrations ## VS Code (Official) @@ -7,6 +7,54 @@ which supports fix actions, import sorting, and more. ![Ruff VS Code extension](https://user-images.githubusercontent.com/1309177/205175763-cf34871d-5c05-4abf-9916-440afc82dbf8.gif) +## pre-commit + +Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit): + +```yaml +# Run the Ruff formatter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff-format +# Run the Ruff linter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff +``` + +To enable fixes, add the `--fix` argument to the linter: + +```yaml +# Run the Ruff linter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff + args: [ --fix, --exit-non-zero-on-fix ] +``` + +To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowed filetypes: + +```yaml +# Run the Ruff linter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff + types_or: [python, pyi, jupyter] +``` + +Ruff's lint hook should be placed after other formatting tools, such as Ruff's format hook, Black, +or isort, _unless_ you enable autofix, in which case, Ruff's pre-commit hook should run _before_ +Black, isort, and other formatting tools, as Ruff's autofix behavior can output code changes that +require reformatting. + ## Language Server Protocol (Official) Ruff supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) @@ -296,3 +344,49 @@ jobs: - name: Run Ruff run: ruff check --format=github . ``` + +Ruff can also be used as a GitHub Action via [`ruff-action`](https://github.com/chartboost/ruff-action). + +By default, `ruff-action` runs as a pass-fail test to ensure that a given repository doesn't contain +any lint rule violations as per its [configuration](https://github.com/astral-sh/ruff/blob/main/docs/configuration.md). +However, under-the-hood, `ruff-action` installs and runs `ruff` directly, so it can be used to +execute any supported `ruff` command (e.g., `ruff check --fix`). + +`ruff-action` supports all GitHub-hosted runners, and can be used with any published Ruff version +(i.e., any version available on [PyPI](https://pypi.org/project/ruff/)). + +To use `ruff-action`, create a file (e.g., `.github/workflows/ruff.yml`) inside your repository +with: + +```yaml +name: Ruff +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 +``` + +Alternatively, you can include `ruff-action` as a step in any other workflow file: + +```yaml + - uses: chartboost/ruff-action@v1 +``` + +`ruff-action` accepts optional configuration parameters via `with:`, including: + +- `version`: The Ruff version to install (default: latest). +- `options`: The command-line arguments to pass to Ruff (default: `"check"`). +- `src`: The source paths to pass to Ruff (default: `"."`). + +For example, to run `ruff check --select B ./src` using Ruff version `0.0.259`: + +```yaml +- uses: chartboost/ruff-action@v1 + with: + src: "./src" + version: 0.0.259 + args: --select B +``` diff --git a/docs/linter.md b/docs/linter.md new file mode 100644 index 00000000000000..f5bc9a90b86817 --- /dev/null +++ b/docs/linter.md @@ -0,0 +1,229 @@ +# The Ruff Linter + +The Ruff Linter is an extremely fast Python linter designed as a drop-in replacement for [Flake8](https://pypi.org/project/flake8/) +(plus dozens of plugins), [isort](https://pypi.org/project/isort/), [pydocstyle](https://pypi.org/project/pydocstyle/), +[pyupgrade](https://pypi.org/project/pyupgrade/), [autoflake](https://pypi.org/project/autoflake/), +and more. + +## `ruff check` + +`ruff check` is the primary entrypoint for the Ruff linter. It accepts a list of files or +directories, and lints all discovered Python files, optionally fixing any fixable errors: + +```shell +ruff check . # Lint all files in the current directory. +ruff check . --fix # Lint all files in the current directory, and fix any fixable errors. +ruff check . --watch # Lint all files in the current directory, and re-lint on change. +``` + +For the full list of supported options, run `ruff check --help`. + +## Rule selection + +The set of enabled rules is controlled via the [`select`](settings.md#select) and [`ignore`](settings.md#ignore) +settings, along with the [`extend-select`](settings.md#extend-select) and [`extend-ignore`](settings.md#extend-ignore) +modifiers. + +Ruff's linter mirrors Flake8's rule code system, in which each rule code consists of a one-to-three +letter prefix, followed by three digits (e.g., `F401`). The prefix indicates that "source" of the rule +(e.g., `F` for Pyflakes, `E` for pycodestyle, `ANN` for flake8-annotations). + +Rule selectors like [`select`](settings.md#select) and [`ignore`](settings.md#ignore) accept either +a full rule code (e.g., `F401`) or any valid prefix (e.g., `F`). For example, given the following +`pyproject.toml` file: + +```toml +[tool.ruff.lint] +select = ["E", "F"] +ignore = ["F401"] +``` + +Ruff would enable all rules with the `E` (pycodestyle) or `F` (Pyflakes) prefix, with the exception +of `F401`. (For more on configuring Ruff via `pyproject.toml`, see [_Configuring Ruff_](configuration.md).) + +As a special-case, Ruff also supports the `ALL` code, which enables all rules. Note that some +pydocstyle rules conflict (e.g., `D203` and `D211`) as they represent alternative docstring +formats. Ruff will automatically disable any conflicting rules when `ALL` is enabled. + +If you're wondering how to configure Ruff, here are some **recommended guidelines**: + +- Prefer `select` and `ignore` over `extend-select` and `extend-ignore`, to make your rule set + explicit. +- Use `ALL` with discretion. Enabling `ALL` will implicitly enable new rules whenever you upgrade. +- Start with a small set of rules (`select = ["E", "F"]`) and add a category at-a-time. For example, + you might consider expanding to `select = ["E", "F", "B"]` to enable the popular flake8-bugbear + extension. +- By default, Ruff's fixes are aggressive. If you find that it's too aggressive for your liking, + consider turning off fixes for specific rules or categories (see [_FAQ_](faq.md#ruff-tried-to-fix-something--but-it-broke-my-code-whats-going-on)). + +For example, a configuration that enables some of the most popular rules (without being too +pedantic) might look like the following: + +```toml +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +``` + +To resolve the enabled rule set, Ruff may need to reconcile `select` and `ignore` from a variety +of sources, including the current `pyproject.toml`, any inherited `pyproject.toml` files, and the +CLI (e.g., `--select`). + +In those scenarios, Ruff uses the "highest-priority" `select` as the basis for the rule set, and +then applies any `extend-select`, `ignore`, and `extend-ignore` adjustments. CLI options are given +higher priority than `pyproject.toml` options, and the current `pyproject.toml` file is given higher +priority than any inherited `pyproject.toml` files. + +For example, given the following `pyproject.toml` file: + +```toml +[tool.ruff.lint] +select = ["E", "F"] +ignore = ["F401"] +``` + +Running `ruff check --select F401` would result in Ruff enforcing `F401`, and no other rules. + +Running `ruff check --extend-select B` would result in Ruff enforcing the `E`, `F`, and `B` rules, +with the exception of `F401`. + +## Automatic fixes + +Ruff supports automatic fixes for a variety of lint errors. For example, Ruff can remove unused +imports, reformat docstrings, rewrite type annotations to use the latest PEP syntax, and more. + +To enable automatic fixes, pass the `--fix` flag to `ruff check`: + +```shell +ruff check . --fix +``` + +By default, Ruff will fix all violations for which automatic fixes are available. (To determine +whether a rule supports automatic fixing, see [_Rules_](rules.md).) To limit the set of rules +that Ruff should attempt to fix, use the [`fixable`](settings.md#fixable) and [`unfixable`](settings.md#unfixable) +settings, along with their [`extend-fixable`](settings.md#extend-fixable) and [`extend-unfixable`](settings.md#extend-unfixable) +variants. + +For example, the following configuration would enable fixes for all rules except +[`unused-imports`](rules/unused-import.md) (`F401`): + +```toml +[tool.ruff.lint] +fixable = ["ALL"] +unfixable = ["F401"] +``` + +## Error suppression + +Ruff supports several mechanisms for suppressing lint errors, be they false positives or +permissible violations. + +To omit a lint rule entirely, add it to the "ignore" list via the [`ignore`](settings.md#ignore) +or [`extend-ignore`](settings.md#extend-ignore) settings, either on the command-line +or in your `pyproject.toml` or `ruff.toml` file. + +To suppress a violation inline, Ruff uses a `noqa` system similar to [Flake8](https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html). +To ignore an individual violation, add `# noqa: {code}` to the end of the line, like so: + +```python +# Ignore F841. +x = 1 # noqa: F841 + +# Ignore E741 and F841. +i = 1 # noqa: E741, F841 + +# Ignore _all_ violations. +x = 1 # noqa +``` + +For multi-line strings (like docstrings), the `noqa` directive should come at the end of the string +(after the closing triple quote), and will apply to the entire string, like so: + +```python +"""Lorem ipsum dolor sit amet. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor. +""" # noqa: E501 +``` + +To ignore all violations across an entire file, add `# ruff: noqa` to any line in the file, like so: + +```python +# ruff: noqa +``` + +To ignore a specific rule across an entire file, add `# ruff: noqa: {code}` to any line in the file, +like so: + +```python +# ruff: noqa: F841 +``` + +Or see the [`per-file-ignores`](settings.md#per-file-ignores) setting, which enables the same +functionality from within your `pyproject.toml` or `ruff.toml` file. + +Note that Ruff will also respect Flake8's `# flake8: noqa` directive, and will treat it as +equivalent to `# ruff: noqa`. + +### Detecting unused suppression comments + +Ruff implements a special rule, [`unused-noqa`](https://docs.astral.sh/ruff/rules/unused-noqa/), +under the `RUF100` code, to enforce that your `noqa` directives are "valid", in that the violations +they _say_ they ignore are actually being triggered on that line (and thus suppressed). To flag +unused `noqa` directives, run: `ruff check /path/to/file.py --extend-select RUF100`. + +Ruff can also _remove_ any unused `noqa` directives via its fix functionality. To remove any +unused `noqa` directives, run: `ruff check /path/to/file.py --extend-select RUF100 --fix`. + +### Inserting necessary suppression comments + +Ruff can _automatically add_ `noqa` directives to all lines that contain violations, which is +useful when migrating a new codebase to Ruff. To automatically add `noqa` directives to all +relevant lines (with the appropriate rule codes), run: `ruff check /path/to/file.py --add-noqa`. + +### Action comments + +Ruff respects isort's [action comments](https://pycqa.github.io/isort/docs/configuration/action_comments.html) +(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `# isort: split`), which +enable selectively enabling and disabling import sorting for blocks of code and other inline +configuration. + +Ruff will also respect variants of these action comments with a `# ruff:` prefix +(e.g., `# ruff: isort: skip_file`, `# ruff: isort: on`, and so on). These variants more clearly +convey that the action comment is intended for Ruff, but are functionally equivalent to the +isort variants. + +See the [isort documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html) +for more. + +## Exit codes + +By default, `ruff check` exits with the following status codes: + +- `0` if no violations were found, or if all present violations were fixed automatically. +- `1` if violations were found. +- `2` if Ruff terminates abnormally due to invalid configuration, invalid CLI options, or an + internal error. + +This convention mirrors that of tools like ESLint, Prettier, and RuboCop. + +`ruff check` supports two command-line flags that alter its exit code behavior: + +- `--exit-zero` will cause Ruff to exit with a status code of `0` even if violations were found. + Note that Ruff will still exit with a status code of `2` if it terminates abnormally. +- `--exit-non-zero-on-fix` will cause Ruff to exit with a status code of `1` if violations were + found, _even if_ all such violations were fixed automatically. Note that the use of + `--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after + fixing. diff --git a/docs/preview.md b/docs/preview.md index d3573a3615afd2..f9f06e88e0a53d 100644 --- a/docs/preview.md +++ b/docs/preview.md @@ -3,7 +3,8 @@ Ruff includes an opt-in preview mode to provide an opportunity for community feedback and increase confidence that changes are a net-benefit before enabling them for everyone. -Preview mode enables a collection of newer rules and fixes that are considered experimental or unstable. +Preview mode enables a collection of newer lint rules, fixes, and formatter style changes that are +considered experimental or unstable,. ## Enabling preview mode @@ -50,7 +51,7 @@ To see which rules are currently in preview, visit the [rules reference](rules.m ## Selecting single preview rules When preview mode is enabled, selecting rule categories or prefixes will include all preview rules that match. -If you would prefer to opt-in to each preview rule individually, you can toggle the `explicit-preview-rules` +If you'd prefer to opt-in to each preview rule individually, you can toggle the `explicit-preview-rules` setting in your `pyproject.toml`: ```toml diff --git a/docs/tutorial.md b/docs/tutorial.md index 7f5e5506bdae8e..64600e9df2aca4 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,11 +1,17 @@ # Tutorial This tutorial will walk you through the process of integrating Ruff into your project. For a more -detailed overview, see [_Configuration_](configuration.md). +detailed overview, see [_Configuring Ruff_](configuration.md). ## Getting Started -Let's assume that our project structure looks like: +To start, we'll install Ruff through PyPI (or with your [preferred package manager](installation.md)): + +```shell +pip install ruff +``` + +Let's then assume that our project structure looks like: ```text numbers @@ -13,7 +19,7 @@ numbers └── numbers.py ``` -Where `numbers.py` contains the following code: +...where `numbers.py` contains the following code: ```py from typing import List @@ -23,16 +29,13 @@ import os def sum_even_numbers(numbers: List[int]) -> int: """Given a list of integers, return the sum of all even numbers in the list.""" - return sum(num for num in numbers if num % 2 == 0) -``` - -To start, we'll install Ruff through PyPI (or with your [preferred package manager](installation.md)): - -```shell -> pip install ruff + return sum( + num for num in numbers + if num % 2 == 0 + ) ``` -We can then run Ruff over our project via: +We can run the Ruff linter over our project with: ```shell ❯ ruff check . @@ -62,7 +65,33 @@ Running `git diff` shows the following: def sum_even_numbers(numbers: List[int]) -> int: """Given a list of integers, return the sum of all even numbers in the list.""" - return sum(num for num in numbers if num % 2 == 0) + return sum( + num for num in numbers + if num % 2 == 0 + ) +``` + +We can run the Ruff formatter over our project with: + +```shell +❯ ruff format . +1 file reformatted +``` + +Running `git diff` shows the following: + +```diff +--- a/numbers.py ++++ b/numbers.py +@@ -3,7 +3,4 @@ from typing import List + + def sum_even_numbers(numbers: List[int]) -> int: + """Given a list of integers, return the sum of all even numbers in the list.""" +- return sum( +- num for num in numbers +- if num % 2 == 0 +- ) ++ return sum(num for num in numbers if num % 2 == 0) ``` Thus far, we've been using Ruff's default configuration. Let's take a look at how we can customize @@ -100,7 +129,6 @@ requires-python = ">=3.10" [tool.ruff] # Decrease the maximum line length to 79 characters. line-length = 79 -src = ["src"] ``` ### Rule Selection @@ -205,8 +233,7 @@ def sum_even_numbers(numbers: List[int]) -> int: return sum(num for num in numbers if num % 2 == 0) ``` -For more in-depth instructions on ignoring errors, -please see [_Configuration_](configuration.md#error-suppression). +For more in-depth instructions on ignoring errors, please see [_Error suppression_](linter.md#error-suppression). ### Adding Rules @@ -239,24 +266,27 @@ index b9291c5ca..b9f15b8c1 100644 return sum(num for num in numbers if num % 2 == 0) ``` -## Continuous Integration +## Integrations This tutorial has focused on Ruff's command-line interface, but Ruff can also be used as a -[pre-commit](https://pre-commit.com) hook: +[pre-commit](https://pre-commit.com) hook via [`ruff-pre-commit`](https://github.com/astral-sh/ruff-pre-commit): ```yaml +# Run the Ruff linter. - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.0.292 hooks: - id: ruff +# Run the Ruff formatter. +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.291 + hooks: + - id: ruff-format ``` -See [_Usage_](usage.md) for more. - -## Editor Integrations - Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or alongside any other editor through the [Ruff LSP](https://github.com/astral-sh/ruff-lsp). -See [_Editor Integrations_](editor-integrations.md). +For more, see [_Integrations_](integrations.md). diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 81a56aba70672d..00000000000000 --- a/docs/usage.md +++ /dev/null @@ -1,109 +0,0 @@ -# Using Ruff - -To run Ruff, try any of the following: - -```shell -ruff check . # Lint all files in the current directory (and any subdirectories) -ruff check path/to/code/ # Lint all files in `/path/to/code` (and any subdirectories) -ruff check path/to/code/*.py # Lint all `.py` files in `/path/to/code` -ruff check path/to/code/to/file.py # Lint `file.py` -ruff check @file_paths.txt # Lint using an input file and treat its contents as command-line arguments (newline delimiter) -``` - -You can run Ruff in `--watch` mode to automatically re-run on-change: - -```shell -ruff check path/to/code/ --watch -``` - -## pre-commit - -Ruff can also be used as a [pre-commit](https://pre-commit.com) hook: - -```yaml -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.292 - hooks: - - id: ruff -``` - -Or, to enable fixes: - -```yaml -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.292 - hooks: - - id: ruff - args: [ --fix, --exit-non-zero-on-fix ] -``` - -Or, to run the hook on Jupyter Notebooks too: - -```yaml -- repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.292 - hooks: - - id: ruff - types_or: [python, pyi, jupyter] -``` - -Ruff's pre-commit hook should be placed after other formatting tools, such as Black and isort, -_unless_ you enable fixes, in which case, Ruff's pre-commit hook should run _before_ Black, isort, -and other formatting tools, as Ruff's fix behavior can output code changes that require -reformatting. - -## VS Code - -Ruff can also be used as a [VS Code extension](https://github.com/astral-sh/ruff-vscode) or -alongside any other editor through the [Ruff LSP](https://github.com/astral-sh/ruff-lsp). - -## GitHub Action - -Ruff can also be used as a GitHub Action via [`ruff-action`](https://github.com/chartboost/ruff-action). - -By default, `ruff-action` runs as a pass-fail test to ensure that a given repository doesn't contain -any lint rule violations as per its [configuration](https://github.com/astral-sh/ruff/blob/main/docs/configuration.md). -However, under-the-hood, `ruff-action` installs and runs `ruff` directly, so it can be used to -execute any supported `ruff` command (e.g., `ruff check --fix`). - -`ruff-action` supports all GitHub-hosted runners, and can be used with any published Ruff version -(i.e., any version available on [PyPI](https://pypi.org/project/ruff/)). - -To use `ruff-action`, create a file (e.g., `.github/workflows/ruff.yml`) inside your repository -with: - -```yaml -name: Ruff -on: [ push, pull_request ] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 -``` - -Alternatively, you can include `ruff-action` as a step in any other workflow file: - -```yaml - - uses: chartboost/ruff-action@v1 -``` - -`ruff-action` accepts optional configuration parameters via `with:`, including: - -- `version`: The Ruff version to install (default: latest). -- `options`: The command-line arguments to pass to Ruff (default: `"check"`). -- `src`: The source paths to pass to Ruff (default: `"."`). - -For example, to run `ruff check --select B ./src` using Ruff version `0.0.259`: - -```yaml -- uses: chartboost/ruff-action@v1 - with: - src: "./src" - version: 0.0.259 - args: --select B -``` diff --git a/scripts/generate_mkdocs.py b/scripts/generate_mkdocs.py index fca685680907a7..0ee594959cee0b 100644 --- a/scripts/generate_mkdocs.py +++ b/scripts/generate_mkdocs.py @@ -22,13 +22,14 @@ class Section(NamedTuple): SECTIONS: list[Section] = [ Section("Overview", "index.md", generated=True), Section("Tutorial", "tutorial.md", generated=False), - Section("Installation", "installation.md", generated=False), - Section("Usage", "usage.md", generated=False), - Section("Configuration", "configuration.md", generated=False), + Section("Installing Ruff", "installation.md", generated=False), + Section("The Ruff Linter", "linter.md", generated=False), + Section("The Ruff Formatter", "formatter.md", generated=False), + Section("Configuring Ruff", "configuration.md", generated=False), Section("Preview", "preview.md", generated=False), Section("Rules", "rules.md", generated=True), Section("Settings", "settings.md", generated=True), - Section("Editor Integrations", "editor-integrations.md", generated=False), + Section("Integrations", "integrations.md", generated=False), Section("FAQ", "faq.md", generated=False), Section("Contributing", "contributing.md", generated=True), ] @@ -41,7 +42,7 @@ class Section(NamedTuple): "configuration.md#pyprojecttoml-discovery" ), "https://docs.astral.sh/ruff/contributing/": "contributing.md", - "https://docs.astral.sh/ruff/editor-integrations/": "editor-integrations.md", + "https://docs.astral.sh/ruff/integrations/": "integrations.md", "https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8": ( "faq.md#how-does-ruff-compare-to-flake8" ),