From cf8ba7ac2d6f5638399583e333d885e086968a36 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 --- .pre-commit-config.yaml | 4 + README.md | 100 ++-- crates/ruff_dev/src/generate_cli_help.rs | 38 +- .../rules/implicit.rs | 2 +- docs/configuration.md | 504 ++++++------------ docs/faq.md | 57 +- docs/formatter.md | 262 +++++++++ docs/formatter/black.md | 422 +++++++++++++++ docs/installation.md | 7 + ...editor-integrations.md => integrations.md} | 96 +++- docs/linter.md | 295 ++++++++++ docs/preview.md | 5 +- docs/tutorial.md | 91 +++- docs/usage.md | 109 ---- mkdocs.template.yml | 1 + scripts/generate_mkdocs.py | 11 +- 16 files changed, 1457 insertions(+), 547 deletions(-) create mode 100644 docs/formatter.md create mode 100644 docs/formatter/black.md rename docs/{editor-integrations.md => integrations.md} (78%) create mode 100644 docs/linter.md delete mode 100644 docs/usage.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3008412e55a5a7..f4c0a71f698af2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,6 +24,10 @@ repos: additional_dependencies: - mdformat-mkdocs - mdformat-admon + exclude: | + (?x)^( + docs/formatter/black.md + )$ - repo: /~https://github.com/igorshubovych/markdownlint-cli rev: v0.33.0 diff --git a/README.md b/README.md index f2eebf50bd9e66..cbab83dc5d5a7d 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 @arguments.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 @arguments.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.1.1 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 Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -select = ["E4", "E7", "E9", "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", @@ -207,27 +216,46 @@ exclude = [ # Same as Black. line-length = 88 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = ["E4", "E7", "E9", "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 +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" ``` 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 @@ -238,7 +266,7 @@ isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implement Rust as a first-party feature. By default, Ruff enables Flake8's `F` rules, along with a subset of the `E` rules, omitting any -stylistic rules that overlap with the use of a formatter, like +stylistic rules that overlap with the use of a formatter, like `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 @@ -330,7 +358,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 38c46e7141a939..14fff96dfb5dc1 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::settings::LinterSettings; /// 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 8ec8992fbebc02..899de5c2e73422 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 Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "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,46 @@ exclude = [ "node_modules", "venv", ] -per-file-ignores = {} # Same as Black. line-length = 88 +indent-width = 4 + +# Assume Python 3.8 +target-version = "py38" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "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" + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" ``` 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,45 +86,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. +For a complete enumeration of the available configuration options, see [_Settings_](settings.md). ## Using `ruff.toml` @@ -120,6 +119,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"] @@ -129,14 +129,104 @@ 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. + +Files can also be selectively excluded from linting or formatting by scoping the `exclude` and +`extend-exclude` settings to the tool-specific configuration tables. For example, the following +would prevent `ruff` from formatting `.pyi` files, but would continue to include them in linting: + +```toml +[tool.ruff.format] +extend-exclude = ["*.pyi"] +``` + +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 analyzed, 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 [Jupyter Notebooks](https://jupyter.org/). + +To opt in to linting and formatting Jupyter Notebook (`.ipynb`) files, add the `*.ipynb` pattern to +your [`extend-include`](settings.md#extend-include) setting, like so: + +```toml +[tool.ruff] +extend-include = ["*.ipynb"] +``` + +This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified +directories, then lint and format 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`. Similarly, +`ruff format /path/to/notebook.ipynb` will always format `notebook.ipynb`. + +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. + ## Command-line interface Some configuration options can be provided via the command-line, such as those related to rule @@ -180,7 +270,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) @@ -272,304 +362,50 @@ 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`. - -## 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 newer Python syntax, and more. - -To enable fixes, pass the `--fix` flag to `ruff check`: - -```shell -ruff check . --fix -``` - -By default, Ruff will fix all violations for which safe fixes are available; to determine -whether a rule supports fixing, see [_Rules_](rules.md). - -### Fix safety - -Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe fixes, but the meaning could be changed when applying unsafe fixes. - -For example, [`unnecessary-iterable-allocation-for-first-element`](rules/unnecessary-iterable-allocation-for-first-element.md) (`RUF015`) is a rule which checks for potentially unperformant use of `list(...)[0]`. The fix replaces this pattern with `next(iter(...))` which can result in a drastic speedup: - -```shell -$ python -m timeit "head = list(range(99999999))[0]" -1 loop, best of 5: 1.69 sec per loop -``` - -```shell -$ python -m timeit "head = next(iter(range(99999999)))" -5000000 loops, best of 5: 70.8 nsec per loop -``` - -However, when the collection is empty, this changes the raised exception from an `IndexError` to `StopIteration`: - -```shell -$ python -c 'list(range(0))[0]' -Traceback (most recent call last): - File "", line 1, in -IndexError: list index out of range -``` - -```shell -$ python -c 'next(iter(range(0)))[0]' -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -Since this could break error handling, this fix is categorized as unsafe. - -Ruff only enables safe fixes by default. Unsafe fixes can be enabled by settings [`unsafe-fixes`](settings.md#unsafe-fixes) in your configuration file or passing the `--unsafe-fixes` flag to `ruff check`: - -```shell -# Show unsafe fixes -ruff check . --unsafe-fixes - -# Apply unsafe fixes -ruff check . --fix --unsafe-fixes -``` - -The safety of fixes can be adjusted per rule using the [`extend-safe-fixes`](settings.md#extend-safe-fixes) and [`extend-unsafe-fixes`](settings.md#extend-unsafe-fixes) settings. - -For example, the following configuration would promote unsafe fixes for `F601` to safe fixes and demote safe fixes for `UP034` to unsafe fixes: - -```toml -[tool.ruff.lint] -extend-safe-fixes = ["F601"] -extend-unsafe-fixes = ["UP034"] -``` - -You may use prefixes to select rules as well, e.g., `F` can be used to promote fixes for all rules in Pyflakes to safe. - -!!! note - All fixes will always be displayed by Ruff when using the `json` output format. The safety of each fix is available under the `applicability` field. - -### Disabling fixes - -To limit the set of rules that Ruff should 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"] -``` - -Conversely, the following configuration would only enable fixes for `F401`: - -```toml -[tool.ruff.lint] -fixable = ["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 + --diff + Avoid writing any formatted files back; instead, exit with a non-zero status code and the difference between the current file and how the formatted file would look like + --config + Path to the `pyproject.toml` or `ruff.toml` file to use for configuration + --target-version + The minimum Python version that should be supported [possible values: py37, py38, py39, py310, py311, py312] + --preview + Enable preview mode; enables unstable formatting. Use `--no-preview` to disable + -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 + --exclude List of paths, used to omit files and/or directories from analysis + --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 2953408cb56c77..0eb4a5aa1d20b7 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 automated formatting. +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 automated formatting. 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. @@ -326,18 +342,19 @@ For a detailed explanation, see the [contributing guide](contributing.md). 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: +[`extend-include`](settings.md#extend-include) setting, like so: ```toml [tool.ruff] -include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"] +extend-include = ["*.ipynb"] ``` This will prompt Ruff to discover Jupyter Notebook (`.ipynb`) files in any specified -directories, and lint them accordingly. +directories, then lint and format 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`. +`ruff check /path/to/notebook.ipynb` will always lint `notebook.ipynb`. Similarly, +`ruff format /path/to/notebook.ipynb` will always format `notebook.ipynb`. Ruff also integrates with [nbQA](/~https://github.com/nbQA-dev/nbQA), a tool for running linters and code formatters over Jupyter Notebooks. @@ -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 labels fixes as "safe" and "unsafe". By default, Ruff will fix all violations for which safe fixes are available, while unsafe fixes can be enabled via the [`unsafe-fixes`](settings.md#unsafe-fixes) diff --git a/docs/formatter.md b/docs/formatter.md new file mode 100644 index 00000000000000..0bd3f2ad1cd4b7 --- /dev/null +++ b/docs/formatter.md @@ -0,0 +1,262 @@ +# 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, +and instead exit with a non-zero status code upon detecting any unformatted files. + +For the full list of supported options, run `ruff format --help`. + +## Philosophy + +The initial 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: [_Known deviations_](formatter/black.md)). 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 single quotes, a line width of 100, and +tab indentation, add the following to your `pyproject.toml`: + +```toml +[tool.ruff] +line-length = 100 + +[tool.ruff.format] +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: + +- [`tab-indentation`](rules/tab-indentation.md) (`W191`) +- [`indentation-with-invalid-multiple`](rules/indentation-with-invalid-multiple.md) (`E111`) +- [`indentation-with-invalid-multiple-comment`](rules/indentation-with-invalid-multiple-comment.md) (`E114`) +- [`over-indented`](rules/over-indented.md) (`E117`) +- [`indent-with-spaces`](rules/indent-with-spaces.md) (`D206`) +- [`triple-single-quotes`](rules/triple-single-quotes.md) (`D300`) +- [`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`) +- [`missing-trailing-comma`](rules/missing-trailing-comma.md) (`COM812`) +- [`prohibited-trailing-comma`](rules/prohibited-trailing-comma.md) (`COM819`) +- [`single-line-implicit-string-concatenation`](rules/single-line-implicit-string-concatenation.md) (`ISC001`) +- [`multi-line-implicit-string-concatenation`](rules/multi-line-implicit-string-concatenation.md) (`ISC002`) + +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: + +- [`force-single-line`](settings.md#isort-force-single-line) +- [`force-wrap-aliases`](settings.md#isort-force-wrap-aliases) +- [`lines-after-imports`](settings.md#isort-lines-after-imports) +- [`lines-between-types`](settings.md#isort-lines-between-types) +- [`split-on-trailing-comma`](settings.md#isort-split-on-trailing-comma) + +## 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 [known deviations](formatter/black.md), +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). + +### Deviations + +While the Ruff formatter aims to be a drop-in replacement for Black, it does differ from Black +in a few known ways. Some of these differences emerge from conscious attempts to improve upon +Black's code style, while others fall out of differences in the underlying implementations. + +For a complete enumeration of these intentional deviations, see [_Known deviations_](formatter/black.md). + +Unintentional deviations from Black are tracked in the [issue tracker](/~https://github.com/astral-sh/ruff/issues?q=is%3Aopen+is%3Aissue+label%3Aformatter). + +### 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 behind Ruff's own [`preview`](https://docs.astral.sh/ruff/settings/#preview) +flag. + +Black promotes some of its preview styling to stable at the end of each year. Ruff will similarly +implement formatting changes under the [`preview`](https://docs.astral.sh/ruff/settings/#preview) +flag, promoting them to stable through minor releases, in accordance with our [versioning policy](/~https://github.com/astral-sh/ruff/discussions/6998#discussioncomment-7016766). diff --git a/docs/formatter/black.md b/docs/formatter/black.md new file mode 100644 index 00000000000000..da8839a20edc67 --- /dev/null +++ b/docs/formatter/black.md @@ -0,0 +1,422 @@ +--- +title: "Known Deviations from Black" +hide: + - navigation +--- + +This document enumerates the known, intentional differences in code style between Black and Ruff's +formatter. + +For a list of unintentional deviations, see [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, + d, + ), +) +``` + +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 685b2df09fee3d..e60b177321ab66 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 --output-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..b99aa39a9ba7e8 --- /dev/null +++ b/docs/linter.md @@ -0,0 +1,295 @@ +# 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. + +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`. + +## 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 newer Python syntax, and more. + +To enable fixes, pass the `--fix` flag to `ruff check`: + +```shell +ruff check . --fix +``` + +By default, Ruff will fix all violations for which safe fixes are available; to determine +whether a rule supports fixing, see [_Rules_](rules.md). + +### Fix safety + +Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe fixes, but the meaning could be changed when applying unsafe fixes. + +For example, [`unnecessary-iterable-allocation-for-first-element`](rules/unnecessary-iterable-allocation-for-first-element.md) (`RUF015`) is a rule which checks for potentially unperformant use of `list(...)[0]`. The fix replaces this pattern with `next(iter(...))` which can result in a drastic speedup: + +```shell +$ python -m timeit "head = list(range(99999999))[0]" +1 loop, best of 5: 1.69 sec per loop +``` + +```shell +$ python -m timeit "head = next(iter(range(99999999)))" +5000000 loops, best of 5: 70.8 nsec per loop +``` + +However, when the collection is empty, this changes the raised exception from an `IndexError` to `StopIteration`: + +```shell +$ python -c 'list(range(0))[0]' +Traceback (most recent call last): + File "", line 1, in +IndexError: list index out of range +``` + +```shell +$ python -c 'next(iter(range(0)))[0]' +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +Since this could break error handling, this fix is categorized as unsafe. + +Ruff only enables safe fixes by default. Unsafe fixes can be enabled by settings [`unsafe-fixes`](settings.md#unsafe-fixes) in your configuration file or passing the `--unsafe-fixes` flag to `ruff check`: + +```shell +# Show unsafe fixes +ruff check . --unsafe-fixes + +# Apply unsafe fixes +ruff check . --fix --unsafe-fixes +``` + +The safety of fixes can be adjusted per rule using the [`extend-safe-fixes`](settings.md#extend-safe-fixes) and [`extend-unsafe-fixes`](settings.md#extend-unsafe-fixes) settings. + +For example, the following configuration would promote unsafe fixes for `F601` to safe fixes and demote safe fixes for `UP034` to unsafe fixes: + +```toml +[tool.ruff.lint] +extend-safe-fixes = ["F601"] +extend-unsafe-fixes = ["UP034"] +``` + +You may use prefixes to select rules as well, e.g., `F` can be used to promote fixes for all rules in Pyflakes to safe. + +!!! note + All fixes will always be displayed by Ruff when using the `json` output format. The safety of each fix is available under the `applicability` field. + +### Disabling fixes + +To limit the set of rules that Ruff should 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"] +``` + +Conversely, the following configuration would only enable fixes for `F401`: + +```toml +[tool.ruff.lint] +fixable = ["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 169a8d5a2d875d..0c369d42452187 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 @@ -64,7 +65,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 b5802df547955f..3730fca2c2b26a 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 Iterable @@ -23,16 +29,13 @@ import os def sum_even_numbers(numbers: Iterable[int]) -> int: """Given an iterable of integers, return the sum of all even numbers in the iterable.""" - 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 `ruff check`: +We can run the Ruff linter over our project via `ruff check`: ```shell ❯ ruff check . @@ -62,7 +65,34 @@ Running `git diff` shows the following: def sum_even_numbers(numbers: Iterable[int]) -> int: """Given an iterable of integers, return the sum of all even numbers in the iterable.""" - 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 via `ruff format`: + +```shell +❯ ruff format . +1 file reformatted +``` + +Running `git diff` shows that the `sum` call was reformatted to fit within the 88-character line +length limit: + +```diff +--- a/numbers.py ++++ b/numbers.py +@@ -3,7 +3,4 @@ from typing import Iterable + + def sum_even_numbers(numbers: Iterable[int]) -> int: + """Given an iterable of integers, return the sum of all even numbers in the iterable.""" +- 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 @@ -77,12 +107,14 @@ Let's create a `pyproject.toml` file in our project's root directory: ```toml [tool.ruff] +# Set the maximum line length to 79 characters. +line-length = 79 + +[tool.ruff.linter] # Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that # overlap with the use of a formatter, like Black, but we can override this behavior by # explicitly adding the rule. extend-select = ["E501"] -# Set the maximum line length to 79 characters. -line-length = 79 ``` Running Ruff again, we can see that it now enforces a line length of 79 characters: @@ -102,10 +134,12 @@ specifically, we'll want to make note of the minimum supported Python version: requires-python = ">=3.10" [tool.ruff] -# Add the `line-too-long` rule to the enforced rule set. -extend-select = ["E501"] # Set the maximum line length to 79 characters. line-length = 79 + +[tool.ruff.linter] +# Add the `line-too-long` rule to the enforced rule set. +extend-select = ["E501"] ``` ### Rule Selection @@ -130,7 +164,7 @@ rules, we can set our `pyproject.toml` to the following: [project] requires-python = ">=3.10" -[tool.ruff] +[tool.ruff.linter] extend-select = [ "UP", # pyupgrade ] @@ -153,13 +187,13 @@ all functions have docstrings: [project] requires-python = ">=3.10" -[tool.ruff] +[tool.ruff.linter] extend-select = [ "UP", # pyupgrade "D", # pydocstyle ] -[tool.ruff.pydocstyle] +[tool.ruff.linter.pydocstyle] convention = "google" ``` @@ -210,7 +244,7 @@ def sum_even_numbers(numbers: Iterable[int]) -> int: return sum(num for num in numbers if num % 2 == 0) ``` -For more in-depth instructions on ignoring errors, see [_Configuration_](configuration.md#error-suppression). +For more in-depth instructions on ignoring errors, please see [_Error suppression_](linter.md#error-suppression). ### Adding Rules @@ -241,24 +275,27 @@ index b9291c5ca..b9f15b8c1 100644 def sum_even_numbers(numbers: Iterable[int]) -> int: ``` -## 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.1.1 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 0b386ec1ff04cb..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.1.1 - hooks: - - id: ruff -``` - -Or, to enable fixes: - -```yaml -- repo: /~https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.1.1 - 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.1.1 - 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/mkdocs.template.yml b/mkdocs.template.yml index 7eeed400e8e229..23d417404a00ce 100644 --- a/mkdocs.template.yml +++ b/mkdocs.template.yml @@ -61,6 +61,7 @@ extra_css: - stylesheets/extra.css not_in_nav: | /rules/* + /formatter/* extra: analytics: provider: fathom diff --git a/scripts/generate_mkdocs.py b/scripts/generate_mkdocs.py index e2c12e55b3bf83..c90126382d63b4 100644 --- a/scripts/generate_mkdocs.py +++ b/scripts/generate_mkdocs.py @@ -22,14 +22,15 @@ 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("Versioning", "versioning.md", generated=False), + Section("Integrations", "integrations.md", generated=False), Section("FAQ", "faq.md", generated=False), Section("Contributing", "contributing.md", generated=True), ] @@ -42,7 +43,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" ),