Compare commits

...

20 Commits

Author SHA1 Message Date
Charlie Marsh
f1d3e3698a Bump version to 0.0.165 2022-12-06 00:03:30 -05:00
Charlie Marsh
080411bc89 Re-create ruff snapshots 2022-12-06 00:03:14 -05:00
Charlie Marsh
f2ad915224 Bump version to 0.0.164 2022-12-05 23:37:22 -05:00
Charlie Marsh
71543eeabc Rename rules mod to ruff (#1104) 2022-12-05 23:35:36 -05:00
Charlie Marsh
44025f1c92 Improve F841's Flake8 parity for unpacking assignments (#1103) 2022-12-05 23:34:40 -05:00
Charlie Marsh
971bf6d232 Run cargo fmt 2022-12-05 23:01:47 -05:00
Charlie Marsh
0acc47386a Use pyproject.toml parent as project root when explicitly provided (#1101) 2022-12-05 23:00:59 -05:00
Reiner Gerecke
982ac6b0ad Auto-generate options in README from field attributes (#1015) 2022-12-05 22:34:40 -05:00
Charlie Marsh
541440f7a8 Re-support F841 detection for single context managers (#1099) 2022-12-05 22:09:45 -05:00
Charlie Marsh
66dde46e03 Track nested imports without column number detection (#1097) 2022-12-05 21:16:44 -05:00
Charlie Marsh
1339e2a002 Bump version to 0.0.163 2022-12-05 20:45:24 -05:00
Charlie Marsh
38ad10f60d Treat nested classes and functions as "standard" siblings (#1095) 2022-12-05 20:45:12 -05:00
Charlie Marsh
88e78c5cde Add missing D415 fixture 2022-12-05 20:42:21 -05:00
Charlie Marsh
436aeed20a Implement autofix for D400 and D415 (#1094) 2022-12-05 20:24:56 -05:00
Charlie Marsh
b94169a8bb Don't autofix D210 by introducing a syntax error (#1093) 2022-12-05 19:13:22 -05:00
Charlie Marsh
995994be3e Bump version to 0.0.162 2022-12-05 19:07:44 -05:00
Charlie Marsh
f001305b2e Only autofix D205 by deleting blank lines (#1091) 2022-12-05 19:01:32 -05:00
Charlie Marsh
e88093541f Avoid wrapping import-star statements (#1089) 2022-12-05 18:39:16 -05:00
Charlie Marsh
da41a495f1 Remove extraneous plugin creation script 2022-12-05 18:31:19 -05:00
Charlie Marsh
55b7ec8f85 Ignore newline enforcement when imports break indentation boundaries (#1085) 2022-12-05 18:02:41 -05:00
68 changed files with 2445 additions and 453 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.161
rev: v0.0.165
hooks:
- id: ruff

17
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.161-dev.0"
version = "0.0.165-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.161"
version = "0.0.165"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1855,6 +1855,7 @@ dependencies = [
"rayon",
"regex",
"ropey",
"ruff_macros",
"rustc-hash",
"rustpython-ast",
"rustpython-common",
@@ -1873,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.161"
version = "0.0.165"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1889,6 +1890,16 @@ dependencies = [
"strum_macros",
]
[[package]]
name = "ruff_macros"
version = "0.0.161"
dependencies = [
"proc-macro2",
"quote",
"syn",
"textwrap",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.161"
version = "0.0.165"
edition = "2021"
rust-version = "1.65.0"
@@ -41,6 +41,7 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.161", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }

590
README.md
View File

@@ -145,7 +145,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.161
rev: v0.0.165
hooks:
- id: ruff
```
@@ -507,7 +507,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D400 | EndsInPeriod | First line should end with a period | |
| D400 | EndsInPeriod | First line should end with a period | 🛠 |
| D402 | NoSignature | First line should not be the function's signature | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
| D404 | NoThisPrefix | First word of the docstring should not be "This" | |
@@ -521,7 +521,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | 🛠 |
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | 🛠 |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
@@ -1280,6 +1280,49 @@ Summary
### Options
<!-- Sections automatically generated by `cargo dev generate-options`. -->
<!-- Begin auto-generated options sections. -->
#### [`allowed-confusables`](#allowed-confusables)
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`,
`RUF002`, and `RUF003`.
**Default value**: `[]`
**Type**: `Vec<char>`
**Example usage**:
```toml
[tool.ruff]
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217),
# which could be confused for "-", "p", and "*", respectively.
allowed-confusables = ["", "ρ", ""]
```
---
#### [`dummy-variable-rgx`](#dummy-variable-rgx)
A regular expression used to identify "dummy" variables, or those which should be
ignored when evaluating (e.g.) unused-variable checks. The default expression matches
`_`, `__`, and `_var`, but not `_var_`.
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"`
**Type**: `Regex`
**Example usage**:
```toml
[tool.ruff]
# Only ignore variables named "_".
dummy_variable_rgx = "^_$"
```
---
#### [`exclude`](#exclude)
A list of file patterns to exclude from linting.
@@ -1293,8 +1336,8 @@ Exclusions are based on globs, and can be either:
(to exclude any Python files in `directory`). Note that these paths are relative to the
project root (e.g., the directory containing your `pyproject.toml`).
Note that you'll typically want to use [`extend-exclude`](#extend-exclude) to modify the excluded
paths.
Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify
the excluded paths.
**Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]`
@@ -1327,28 +1370,6 @@ extend-exclude = ["tests", "src/bad.py"]
---
#### [`ignore`](#ignore)
A list of check code prefixes to ignore. Prefixes can specify exact checks (like `F841`), entire
categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
more specific prefixes override less specific prefixes.
**Default value**: `[]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# Skip unused variable checks (`F841`).
ignore = ["F841"]
```
---
#### [`extend-ignore`](#extend-ignore)
A list of check code prefixes to ignore, in addition to those specified by `ignore`.
@@ -1367,28 +1388,6 @@ extend-ignore = ["F841"]
---
#### [`select`](#select)
A list of check code prefixes to enable. Prefixes can specify exact checks (like `F841`), entire
categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
more specific prefixes override less specific prefixes.
**Default value**: `["E", "F"]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
select = ["E", "F", "B", "Q"]
```
---
#### [`extend-select`](#extend-select)
A list of check code prefixes to enable, in addition to those specified by `select`.
@@ -1421,8 +1420,8 @@ yet implemented in Ruff.
```toml
[tool.ruff]
# Avoiding flagging (and removing) `V101` from any `# noqa` directives, despite Ruff's lack of
# support for `vulture`.
# Avoiding flagging (and removing) `V101` from any `# noqa`
# directives, despite Ruff's lack of support for `vulture`.
external = ["V101"]
```
@@ -1430,8 +1429,8 @@ external = ["V101"]
#### [`fix`](#fix)
Enable autofix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix`
command-line flags).
Enable autofix behavior by-default when running `ruff` (overridden
by the `--fix` and `--no-fix` command-line flags).
**Default value**: `false`
@@ -1464,9 +1463,33 @@ fixable = ["E", "F"]
---
#### [`unfixable`](#unfixable)
#### [`format`](#format)
A list of check code prefixes to consider un-autofix-able.
The style in which violation messages should be formatted: `"text"` (default),
`"grouped"` (group messages by file), `"json"` (machine-readable), `"junit"`
(machine-readable XML), or `"github"` (GitHub Actions annotations).
**Default value**: `"text"`
**Type**: `SerializationType`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`ignore`](#ignore)
A list of check code prefixes to ignore. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
**Default value**: `[]`
@@ -1476,8 +1499,28 @@ A list of check code prefixes to consider un-autofix-able.
```toml
[tool.ruff]
# Disable autofix for unused imports (`F401`).
unfixable = ["F401"]
# Skip unused variable checks (`F841`).
ignore = ["F841"]
```
---
#### [`ignore-init-module-imports`](#ignore-init-module-imports)
Avoid automatically removing unused imports in `__init__.py` files. Such imports will
still be +flagged, but with a dedicated message suggesting that the import is either
added to the module' +`__all__` symbol, or re-exported with a redundant alias (e.g.,
`import os as os`).
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff]
ignore-init-module-imports = true
```
---
@@ -1500,87 +1543,10 @@ line-length = 120
---
#### [`dummy-variable-rgx`](#dummy-variable-rgx)
A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating
(e.g.) unused-variable checks.
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`)
**Type**: `Regex`
**Example usage**:
```toml
[tool.ruff]
# Only ignore variables named "_".
dummy-variable-rgx = "^_$"
```
---
#### [`allowed-confusables`](#allowed-confusables)
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`, `RUF002`,
and `RUF003`.
**Default value**: `[]`
**Type**: `Vec<char>`
**Example usage**:
```toml
[tool.ruff]
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and greek-small-letter-alpha (U+03B1),
# which could be confused for "-", "p", and "*", respectively.
allowed-confusables = ["", "ρ", ""]
```
---
#### [`ignore-init-module-imports`](#ignore-init-module-imports)
Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be
flagged, but with a dedicated message suggesting that the import is either added to the module's
`__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff]
ignore-init-module-imports = true
```
---
#### [`format`](#format)
The style in which violation messages should be formatted: `"text"` (default), `"grouped"`
(group messages by file), `"json"` (machine-readable), `"junit"` (machine-readable XML), or `"github"` (GitHub Actions annotations).
**Default value**: `"text"`
**Type**: `SerializationFormat`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`per-file-ignores`](#per-file-ignores)
A list of mappings from file pattern to check code prefixes to exclude, when considering any
matching files.
A list of mappings from file pattern to check code prefixes to exclude, when considering
any matching files.
**Default value**: `{}`
@@ -1598,10 +1564,32 @@ matching files.
---
#### [`select`](#select)
A list of check code prefixes to enable. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
**Default value**: `["E", "F"]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
select = ["E", "F", "B", "Q"]
```
---
#### [`show-source`](#show-source)
Whether to show source code snippets when reporting lint error violations (overridden by the
`--show-source` command-line flag).
Whether to show source code snippets when reporting lint error violations (overridden by
the `--show-source` command-line flag).
**Default value**: `false`
@@ -1637,9 +1625,9 @@ src = ["src", "test"]
#### [`target-version`](#target-version)
The Python version to target, e.g., when considering automatic code upgrades, like rewriting type
annotations. Note that the target version will _not_ be inferred from the _current_ Python version,
and instead must be specified explicitly (as seen below).
The Python version to target, e.g., when considering automatic code upgrades, like
rewriting type annotations. Note that the target version will _not_ be inferred from the
_current_ Python version, and instead must be specified explicitly (as seen below).
**Default value**: `"py310"`
@@ -1653,12 +1641,49 @@ and instead must be specified explicitly (as seen below).
target-version = "py37"
```
---
#### [`unfixable`](#unfixable)
A list of check code prefixes to consider un-autofix-able.
**Default value**: `[]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# Disable autofix for unused imports (`F401`).
unfixable = ["F401"]
```
---
### `flake8-annotations`
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-annotations]
allow-star-arg-any = true
```
---
#### [`mypy-init-return`](#mypy-init-return)
Whether to allow the omission of a return type hint for `__init__` if at least one argument is
annotated.
Whether to allow the omission of a return type hint for `__init__` if at least one
argument is annotated.
**Default value**: `false`
@@ -1675,8 +1700,8 @@ mypy-init-return = true
#### [`suppress-dummy-args`](#suppress-dummy-args)
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable regex (like
`_`).
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
regex (like `_`).
**Default value**: `false`
@@ -1693,7 +1718,8 @@ suppress-dummy-args = true
#### [`suppress-none-returning`](#suppress-none-returning)
Whether to suppress `ANN200`-level errors for functions that meet either of the following criteria:
Whether to suppress `ANN200`-level errors for functions that meet either of the
following criteria:
- Contain no `return` statement.
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
@@ -1711,27 +1737,12 @@ suppress-none-returning = true
---
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-annotations]
allow-star-arg-any = true
```
### `flake8-bugbear`
#### [`extend-immutable-calls`](#extend-immutable-calls)
Additional callable functions to consider "immutable" when evaluating, e.g., no-mutable-default-argument
checks (`B006`).
Additional callable functions to consider "immutable" when evaluating, e.g.,
`no-mutable-default-argument` checks (`B006`).
**Default value**: `[]`
@@ -1745,8 +1756,47 @@ checks (`B006`).
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
```
---
### `flake8-quotes`
#### [`avoid-escape`](#avoid-escape)
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
This minimizes the need to escape quotation marks within strings.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
# Don't bother trying to avoid escapes.
avoid-escape = false
```
---
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
**Default value**: `"double"`
**Type**: `Quote`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
docstring-quotes = "single"
```
---
#### [`inline-quotes`](#inline-quotes)
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
@@ -1781,47 +1831,12 @@ multiline-quotes = "single"
---
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
**Default value**: `"double"`
**Type**: `Quote`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
docstring-quotes = "single"
```
---
#### [`avoid-escape`](#avoid-escape)
Whether to avoid using single quotes if a string contains single quotes, or vice-versa with
double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the
need to escape quotation marks within strings.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
# Don't bother trying to avoid escapes.
avoid-escape = false
```
### `flake8-tidy-imports`
#### [`ban-relative-imports`](#ban-relative-imports)
Whether to ban all relative imports (`"all"`), or only those imports that extend into the parent
module and beyond (`"parents"`).
Whether to ban all relative imports (`"all"`), or only those imports that extend into
the parent module and beyond (`"parents"`).
**Default value**: `"parents"`
@@ -1835,61 +1850,10 @@ module and beyond (`"parents"`).
ban-relative-imports = "all"
```
---
### `isort`
#### [`known-first-party`](#known-first-party)
A list of modules to consider first-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
known-first-party = ["src"]
```
---
#### [`known-third-party`](#known-third-party)
A list of modules to consider third-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
known-third-party = ["fastapi"]
```
---
#### [`extra-standard-library`](#extra-standard-library)
A list of modules to consider standard-library, in addition to those known to Ruff in advance.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
extra-standard-library = ["path"]
```
---
#### [`combine-as-imports`](#combine-as-imports)
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
@@ -1908,11 +1872,29 @@ combine-as-imports = true
---
#### [`extra-standard-library`](#extra-standard-library)
A list of modules to consider standard-library, in addition to those known to Ruff in
advance.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
extra-standard-library = ["path"]
```
---
#### [`force-wrap-aliases`](#force-wrap-aliases)
Force `import from` statements with multiple members and at least one alias (e.g., `import A as B`)
to wrap such that every line contains exactly one member. For example, this formatting would be
retained, rather than condensing to a single line:
Force `import from` statements with multiple members and at least one alias (e.g.,
`import A as B`) to wrap such that every line contains exactly one member. For example,
this formatting would be retained, rather than condensing to a single line:
```py
from .utils import (
@@ -1932,6 +1914,44 @@ from .utils import (
force-wrap-aliases = true
```
---
#### [`known-first-party`](#known-first-party)
A list of modules to consider first-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
known-first-party = ["src"]
```
---
#### [`known-third-party`](#known-third-party)
A list of modules to consider third-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
known-third-party = ["src"]
```
---
### `mccabe`
#### [`max-complexity`](#max-complexity)
@@ -1950,8 +1970,30 @@ The maximum McCabe complexity to allow before triggering `C901` errors.
max-complexity = 5
```
---
### `pep8-naming`
#### [`classmethod-decorators`](#classmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be
treated as a class method. For example, Ruff will expect that any method decorated by a
decorator in this list takes a `cls` argument as its first argument.
**Default value**: `["classmethod"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.pep8-naming]
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
classmethod-decorators = ["classmethod", "pydantic.validator"]
```
---
#### [`ignore-names`](#ignore-names)
A list of names to ignore when considering `pep8-naming` violations.
@@ -1969,31 +2011,11 @@ ignore-names = ["callMethod"]
---
#### [`classmethod-decorators`](#classmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a class method. For example, Ruff will expect that any method decorated by a decorator in this list
takes a `cls` argument as its first argument.
**Default value**: `["classmethod"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.pep8-naming]
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
classmethod-decorators = ["classmethod", "pydantic.validator"]
```
---
#### [`staticmethod-decorators`](#staticmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a static method. For example, Ruff will expect that any method decorated by a decorator in this list
has no `self` or `cls` argument.
A list of decorators that, when applied to a method, indicate that the method should be
treated as a static method. For example, Ruff will expect that any method decorated by a
decorator in this list has no `self` or `cls` argument.
**Default value**: `["staticmethod"]`
@@ -2007,13 +2029,13 @@ has no `self` or `cls` argument.
staticmethod-decorators = ["staticmethod", "stcmthd"]
```
---
### `pyupgrade`
#### [`keep-runtime-typing`](#keep-runtime-typing)
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`)
rewrites even if a file imports `from __future__ import annotations`. Note that this setting is
only applicable when the target Python version is below 3.9 and 3.10 respectively.
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively.
**Default value**: `false`
@@ -2027,6 +2049,10 @@ only applicable when the target Python version is below 3.9 and 3.10 respectivel
keep-runtime-typing = true
```
---
<!-- End auto-generated options sections. -->
## License
MIT

View File

@@ -1,11 +0,0 @@
set -euxo pipefail
NAME=$1
mkdir -p src/$1
mkdir -p resources/test/fixtures/$1
touch src/$1/mod.rs
sed -i "" "s/mod flake8_print;/mod flake8_print; mod flake8_return;/g" src/lib.rs
sed -i "" "s|// flake8-print|// flake8-return\n// flake8-print|g" src/checks.rs

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.161"
version = "0.0.165"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.161"
version = "0.0.165"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.161-dev.0"
version = "0.0.165-dev.0"
edition = "2021"
[lib]

View File

@@ -14,3 +14,28 @@ y = 1
import os
import sys
"""Docstring"""
if True:
import os
def f():
pass
if True:
import os
def f():
pass
if True:
x = 1
import collections
import typing
class X: pass
if True:
x = 1
import collections
import typing
def f(): pass

View File

@@ -0,0 +1 @@
from .subscription import * # type: ignore # some very long comment explaining why this needs a type ignore

View File

@@ -571,3 +571,11 @@ def multiline_trailing_and_leading_space():
"""
pass
@expect('D210: No whitespaces allowed surrounding docstring text')
@expect("D400: First line should end with a period (not '\"')")
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not '\"')")
def endswith_quote():
"""Whitespace at the end, but also a quote" """

View File

@@ -0,0 +1,73 @@
def f():
"Here's a line without a period"
...
def f():
"""Here's a line without a period"""
...
def f():
"""
Here's a line without a period,
but here's the next line
"""
...
def f():
"""Here's a line without a period"""
...
def f():
"""
Here's a line without a period,
but here's the next line"""
...
def f():
"""
Here's a line without a period,
but here's the next line with trailing space """
...
def f():
r"Here's a line without a period"
...
def f():
r"""Here's a line without a period"""
...
def f():
r"""
Here's a line without a period,
but here's the next line
"""
...
def f():
r"""Here's a line without a period"""
...
def f():
r"""
Here's a line without a period,
but here's the next line"""
...
def f():
r"""
Here's a line without a period,
but here's the next line with trailing space """
...

View File

@@ -65,3 +65,8 @@ def f7():
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")
def f8():
with open("file") as f, open("") as ((a, b)):
print("hello")

View File

@@ -0,0 +1,24 @@
def f(tup):
x, y = tup # this does NOT trigger F841
def f():
x, y = 1, 2 # this triggers F841 as it's just a simple assignment where unpacking isn't needed
def f():
(x, y) = coords = 1, 2 # this does NOT trigger F841
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2 # this triggers F841 on coords
def f():
coords = (x, y) = 1, 2 # this triggers F841 on coords
def f():
(a, b) = (x, y) = 1, 2 # this triggers F841 on everything

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.161"
version = "0.0.165"
edition = "2021"
[dependencies]

View File

@@ -0,0 +1,125 @@
//! Generate a Markdown-compatible listing of configuration options.
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `README.md`).
#[arg(long)]
dry_run: bool,
}
fn emit_field(output: &mut String, field: &OptionField, group_name: Option<&str>) {
output.push_str(&format!("#### [`{0}`](#{0})\n", field.name));
output.push('\n');
output.push_str(field.doc);
output.push_str("\n\n");
output.push_str(&format!("**Default value**: `{}`\n", field.default));
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if group_name.is_some() {
format!(".{}", group_name.unwrap())
} else {
String::new()
},
field.example
));
output.push('\n');
}
pub fn main(cli: &Cli) -> Result<()> {
let mut output = String::new();
// Generate all the top-level fields.
for field in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, &field, None);
output.push_str("---\n\n");
}
// Generate all the sub-groups.
for group in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Group(group) = entry {
Some(group)
} else {
None
}
})
.sorted_by_key(|group| group.name)
{
output.push_str(&format!("### `{}`\n", group.name));
output.push('\n');
for field in group
.fields
.iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, field, Some(group.name));
output.push_str("---\n\n");
}
}
if cli.dry_run {
print!("{output}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(BEGIN_PRAGMA)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
// Extract the suffix.
let index = existing
.find(END_PRAGMA)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
}
Ok(())
}

View File

@@ -12,6 +12,7 @@
)]
pub mod generate_check_code_prefix;
pub mod generate_options;
pub mod generate_rules_table;
pub mod generate_source_code;
pub mod print_ast;

View File

@@ -14,8 +14,8 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_cst,
print_tokens,
generate_check_code_prefix, generate_options, generate_rules_table, generate_source_code,
print_ast, print_cst, print_tokens,
};
#[derive(Parser)]
@@ -32,6 +32,8 @@ enum Commands {
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli),
/// Run round-trip source code generation on a given Python file.
GenerateSourceCode(generate_source_code::Cli),
/// Print the AST for a given Python file.
@@ -48,6 +50,7 @@ fn main() -> Result<()> {
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,

13
ruff_macros/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "ruff_macros"
version = "0.0.161"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = { version = "1.0.47" }
quote = { version = "1.0.21" }
syn = { version = "1.0.103", features = ["derive", "parsing"] }
textwrap = { version = "0.16.0" }

177
ruff_macros/src/lib.rs Normal file
View File

@@ -0,0 +1,177 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::token::Comma;
use syn::{
parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput,
Field, Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
};
#[proc_macro_derive(ConfigurationOptions, attributes(option, option_group))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_impl(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput { ident, data, .. } = input;
match data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => {
let mut output = vec![];
for field in fields.named.iter() {
if let Some(attr) = field.attrs.iter().find(|a| a.path.is_ident("option")) {
output.push(handle_option(field, attr)?);
};
if field.attrs.iter().any(|a| a.path.is_ident("option_group")) {
output.push(handle_option_group(field)?);
};
}
Ok(quote! {
use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup, ConfigurationOptions};
#[automatically_derived]
impl ConfigurationOptions for #ident {
fn get_available_options() -> Vec<OptionEntry> {
vec![#(#output),*]
}
}
})
}
_ => Err(syn::Error::new(
ident.span(),
"Can only derive ConfigurationOptions from structs with named fields.",
)),
}
}
/// For a field with type `Option<Foobar>` where `Foobar` itself is a struct
/// deriving `ConfigurationOptions`, create code that calls retrieves options
/// from that group: `Foobar::get_available_options()`
fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
// unwrap is safe because we're only going over named fields
let ident = field.ident.as_ref().unwrap();
match &field.ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => match segments.first() {
Some(PathSegment {
ident: type_ident,
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
..
}) if type_ident == "Option" => {
let path = &args[0];
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
Ok(quote_spanned!(
ident.span() => OptionEntry::Group(OptionGroup {
name: #kebab_name,
fields: #path::get_available_options(),
})
))
}
_ => Err(syn::Error::new(
ident.span(),
"Expected `Option<_>` as type.",
)),
},
_ => Err(syn::Error::new(ident.span(), "Expected type.")),
}
}
/// Parse an `#[option(doc="...", default="...", value_type="...",
/// example="...")]` attribute and return data in the form of an `OptionField`.
fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::TokenStream> {
// unwrap is safe because we're only going over named fields
let ident = field.ident.as_ref().unwrap();
let FieldAttributes {
doc,
default,
value_type,
example,
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
Ok(quote_spanned!(
ident.span() => OptionEntry::Field(OptionField {
name: #kebab_name,
doc: &#doc,
default: &#default,
value_type: &#value_type,
example: &#example,
})
))
}
#[derive(Debug)]
struct FieldAttributes {
doc: String,
default: String,
value_type: String,
example: String,
}
impl Parse for FieldAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let doc = _parse_key_value(input, "doc")?;
input.parse::<Comma>()?;
let default = _parse_key_value(input, "default")?;
input.parse::<Comma>()?;
let value_type = _parse_key_value(input, "value_type")?;
input.parse::<Comma>()?;
let example = _parse_key_value(input, "example")?;
if !input.is_empty() {
input.parse::<Comma>()?;
}
Ok(FieldAttributes {
doc: textwrap::dedent(&doc).trim_matches('\n').to_string(),
default,
value_type,
example: textwrap::dedent(&example).trim_matches('\n').to_string(),
})
}
}
fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
let ident: proc_macro2::Ident = input.parse()?;
if ident != name {
return Err(syn::Error::new(
ident.span(),
format!("Expected `{name}` name"),
));
}
input.parse::<Token![=]>()?;
let value: Lit = input.parse()?;
match &value {
Lit::Str(v) => Ok(v.value()),
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
}
}

View File

@@ -94,23 +94,132 @@ pub fn in_nested_block<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool
})
}
/// Check if a node represents an unpacking assignment.
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
let StmtKind::Assign { targets, value, .. } = &stmt.node else {
return false;
};
if !targets.iter().any(|child| {
matches!(
child.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
)
}) {
return false;
/// Returns `true` if `parent` contains `child`.
fn contains(parent: &Expr, child: &Expr) -> bool {
match &parent.node {
ExprKind::BoolOp { values, .. } => values.iter().any(|parent| contains(parent, child)),
ExprKind::NamedExpr { target, value } => contains(target, child) || contains(value, child),
ExprKind::BinOp { left, right, .. } => contains(left, child) || contains(right, child),
ExprKind::UnaryOp { operand, .. } => contains(operand, child),
ExprKind::Lambda { body, .. } => contains(body, child),
ExprKind::IfExp { test, body, orelse } => {
contains(test, child) || contains(body, child) || contains(orelse, child)
}
ExprKind::Dict { keys, values } => keys
.iter()
.chain(values.iter())
.any(|parent| contains(parent, child)),
ExprKind::Set { elts } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::ListComp { elt, .. } => contains(elt, child),
ExprKind::SetComp { elt, .. } => contains(elt, child),
ExprKind::DictComp { key, value, .. } => contains(key, child) || contains(value, child),
ExprKind::GeneratorExp { elt, .. } => contains(elt, child),
ExprKind::Await { value } => contains(value, child),
ExprKind::Yield { value } => value.as_ref().map_or(false, |value| contains(value, child)),
ExprKind::YieldFrom { value } => contains(value, child),
ExprKind::Compare {
left, comparators, ..
} => contains(left, child) || comparators.iter().any(|parent| contains(parent, child)),
ExprKind::Call {
func,
args,
keywords,
} => {
contains(func, child)
|| args.iter().any(|parent| contains(parent, child))
|| keywords
.iter()
.any(|keyword| contains(&keyword.node.value, child))
}
ExprKind::FormattedValue {
value, format_spec, ..
} => {
contains(value, child)
|| format_spec
.as_ref()
.map_or(false, |value| contains(value, child))
}
ExprKind::JoinedStr { values } => values.iter().any(|parent| contains(parent, child)),
ExprKind::Constant { .. } => false,
ExprKind::Attribute { value, .. } => contains(value, child),
ExprKind::Subscript { value, slice, .. } => {
contains(value, child) || contains(slice, child)
}
ExprKind::Starred { value, .. } => contains(value, child),
ExprKind::Name { .. } => parent == child,
ExprKind::List { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::Tuple { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::Slice { lower, upper, step } => {
lower.as_ref().map_or(false, |value| contains(value, child))
|| upper.as_ref().map_or(false, |value| contains(value, child))
|| step.as_ref().map_or(false, |value| contains(value, child))
}
}
}
/// Check if a node represents an unpacking assignment.
pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
match &parent.node {
StmtKind::With { items, .. } => items.iter().any(|item| {
if let Some(optional_vars) = &item.optional_vars {
if matches!(optional_vars.node, ExprKind::Tuple { .. }) {
if contains(optional_vars, child) {
return true;
}
}
}
false
}),
StmtKind::Assign { targets, value, .. } => {
// In `(a, b) = (1, 2)`, `(1, 2)` is the target, and it is a tuple.
let value_is_tuple = matches!(
&value.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
);
// In `(a, b) = coords = (1, 2)`, `(a, b)` and `coords` are the targets, and
// `(a, b`) is a tuple. (We use "tuple" as a placeholder for any
// unpackable expression.)
let targets_are_tuples = targets.iter().all(|item| {
matches!(
item.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
)
});
// If we're looking at `a` in `(a, b) = coords = (1, 2)`, then we should
// identify that the current expression is in a tuple.
let child_in_tuple = targets_are_tuples
|| targets.iter().any(|item| {
matches!(
item.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
) && contains(item, child)
});
// If our child is a tuple, and value is not, it's always an unpacking
// expression. Ex) `x, y = tup`
if child_in_tuple && !value_is_tuple {
return true;
}
// If our child isn't a tuple, but value is, it's never an unpacking expression.
// Ex) `coords = (1, 2)`
if !child_in_tuple && value_is_tuple {
return false;
}
// If our target and the value are both tuples, then it's an unpacking
// expression assuming there's at least one non-tuple child.
// Ex) Given `(x, y) = coords = 1, 2`, `(x, y)` is considered an unpacking
// expression. Ex) Given `(x, y) = (a, b) = 1, 2`, `(x, y)` isn't
// considered an unpacking expression.
if child_in_tuple && value_is_tuple {
return !targets_are_tuples;
}
false
}
_ => false,
}
!matches!(
&value.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
)
}
pub type LocatedCmpop<U = ()> = Located<Cmpop, U>;

View File

@@ -5,7 +5,6 @@ use std::path::Path;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Withitem;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -20,7 +19,7 @@ use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{helpers, operations, visitor};
use crate::checks::{Check, CheckCode, CheckKind, DeferralKeyword};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
@@ -82,7 +81,6 @@ pub struct Checker<'a> {
in_deferred_type_definition: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
seen_import_boundary: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
@@ -130,7 +128,6 @@ impl<'a> Checker<'a> {
in_deferred_type_definition: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: false,
@@ -2485,13 +2482,6 @@ where
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
}
fn visit_withitem(&mut self, withitem: &'b Withitem) {
let prev_in_withitem = self.in_withitem;
self.in_withitem = true;
walk_withitem(self, withitem);
self.in_withitem = prev_in_withitem;
}
}
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
@@ -2798,7 +2788,7 @@ impl<'a> Checker<'a> {
return;
}
if self.in_withitem || operations::is_unpacking_assignment(parent) {
if operations::is_unpacking_assignment(parent, expr) {
self.add_binding(
id,
Binding {

View File

@@ -4,9 +4,9 @@ use rustpython_parser::lexer::{LexResult, Tok};
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::ruff::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
pub fn check_tokens(
locator: &SourceCodeLocator,
@@ -37,7 +37,7 @@ pub fn check_tokens(
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. } | Tok::Comment) {
checks.extend(rules::checks::ambiguous_unicode_character(
checks.extend(ruff::checks::ambiguous_unicode_character(
locator,
start,
end,

View File

@@ -2497,6 +2497,8 @@ impl CheckKind {
| CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda
| CheckKind::DuplicateHandlerException(..)
| CheckKind::EndsInPeriod
| CheckKind::EndsInPunctuation
| CheckKind::GetAttrWithConstant
| CheckKind::ImplicitReturn
| CheckKind::ImplicitReturnValue

View File

@@ -1,22 +1,51 @@
//! Settings for the `flake-annotations` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
/// Allow omission of a return type hint for `__init__` if at least one
/// argument is annotated.
#[option(
doc = r#"
Whether to allow the omission of a return type hint for `__init__` if at least one
argument is annotated.
"#,
default = "false",
value_type = "bool",
example = "mypy-init-return = true"
)]
pub mypy_init_return: Option<bool>,
/// Suppress ANN000-level errors for dummy arguments, like `_`.
#[option(
doc = r#"
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
regex (like `_`).
"#,
default = "false",
value_type = "bool",
example = "suppress-dummy-args = true"
)]
pub suppress_dummy_args: Option<bool>,
/// Suppress ANN200-level errors for functions that meet one of the
/// following criteria:
/// - Contain no `return` statement
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
#[option(
doc = r#"
Whether to suppress `ANN200`-level errors for functions that meet either of the
following criteria:
- Contain no `return` statement.
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
"#,
default = "false",
value_type = "bool",
example = "suppress-none-returning = true"
)]
pub suppress_none_returning: Option<bool>,
/// Suppress ANN401 for dynamically typed *args and **kwargs.
#[option(
doc = "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` \
arguments.",
default = "false",
value_type = "bool",
example = "allow-star-arg-any = true"
)]
pub allow_star_arg_any: Option<bool>,
}

View File

@@ -1,10 +1,23 @@
//! Settings for the `flake8-bugbear` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Additional callable functions to consider "immutable" when evaluating, e.g.,
`no-mutable-default-argument` checks (`B006`).
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
"#
)]
pub extend_immutable_calls: Option<Vec<String>>,
}

View File

@@ -1,5 +1,6 @@
//! Settings for the `flake8-quotes` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -9,12 +10,55 @@ pub enum Quote {
Double,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
inline-quotes = "single"
"#
)]
pub inline_quotes: Option<Quote>,
#[option(
doc = r#"
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
multiline-quotes = "single"
"#
)]
pub multiline_quotes: Option<Quote>,
#[option(
doc = r#"
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
docstring-quotes = "single"
"#
)]
pub docstring_quotes: Option<Quote>,
#[option(
doc = r#"
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
This minimizes the need to escape quotation marks within strings.
"#,
default = r#"true"#,
value_type = "bool",
example = r#"
# Don't bother trying to avoid escapes.
avoid-escape = false
"#
)]
pub avoid_escape: Option<bool>,
}

View File

@@ -1,5 +1,6 @@
//! Settings for the `flake8-tidy-imports` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -9,9 +10,21 @@ pub enum Strictness {
All,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Whether to ban all relative imports (`"all"`), or only those imports that extend into
the parent module and beyond (`"parents"`).
"#,
default = r#""parents""#,
value_type = "Strictness",
example = r#"
# Disallow all relative imports.
ban-relative-imports = "all"
"#
)]
pub ban_relative_imports: Option<Strictness>,
}

View File

@@ -97,7 +97,7 @@ pub(crate) fn ignores_from_path<'a>(
/// Convert any path to an absolute path (based on the current working
/// directory).
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
pub fn normalize_path(path: &Path) -> PathBuf {
if let Ok(path) = path.absolutize() {
return path.to_path_buf();
}
@@ -105,7 +105,7 @@ pub(crate) fn normalize_path(path: &Path) -> PathBuf {
}
/// Convert any path to an absolute path (based on the specified project root).
pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
if let Ok(path) = path.absolutize_from(project_root) {
return path.to_path_buf();
}
@@ -113,7 +113,7 @@ pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
}
/// Convert an absolute path to be relative to the current working directory.
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
pub fn relativize_path(path: &Path) -> Cow<str> {
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return path.to_string_lossy();
}

View File

@@ -42,6 +42,15 @@ pub fn format_import_from(
force_wrap_aliases: bool,
is_first: bool,
) -> String {
if aliases.len() == 1
&& aliases
.iter()
.all(|(alias, _)| alias.name == "*" && alias.asname.is_none())
{
let (single_line, ..) = format_single_line(import_from, comments, aliases, is_first);
return single_line;
}
// We can only inline if: (1) none of the aliases have atop comments, and (3)
// only the last alias (if any) has inline comments.
if aliases
@@ -58,7 +67,7 @@ pub fn format_import_from(
{
let (single_line, import_length) =
format_single_line(import_from, comments, aliases, is_first);
if import_length <= line_length {
if import_length <= line_length || aliases.iter().any(|(alias, _)| alias.name == "*") {
return single_line;
}
}

View File

@@ -561,6 +561,7 @@ mod tests {
#[test_case(Path::new("insert_empty_lines.py"))]
#[test_case(Path::new("leading_prefix.py"))]
#[test_case(Path::new("no_reorder_within_section.py"))]
#[test_case(Path::new("no_wrap_star.py"))]
#[test_case(Path::new("order_by_type.py"))]
#[test_case(Path::new("order_relative_imports_by_level.py"))]
#[test_case(Path::new("preserve_comment_order.py"))]

View File

@@ -51,7 +51,11 @@ pub fn check_imports(
// Special-cases: there's leading or trailing content in the import block.
let has_leading_content = match_leading_content(block.imports.first().unwrap(), locator);
let has_trailing_content = match_trailing_content(block.imports.last().unwrap(), locator);
let num_trailing_lines = count_trailing_lines(block.imports.last().unwrap(), locator);
let num_trailing_lines = if block.trailer.is_none() {
0
} else {
count_trailing_lines(block.imports.last().unwrap(), locator)
};
// Generate the sorted import block.
let expected = format_imports(

View File

@@ -2,15 +2,79 @@
use std::collections::BTreeSet;
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
option.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
combine-as-imports = true
"#
)]
pub combine_as_imports: Option<bool>,
#[option(
doc = r#"
Force `import from` statements with multiple members and at least one alias (e.g.,
`import A as B`) to wrap such that every line contains exactly one member. For example,
this formatting would be retained, rather than condensing to a single line:
```py
from .utils import (
test_directory as test_directory,
test_id as test_id
)
```
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
force-wrap-aliases = true
"#
)]
pub force_wrap_aliases: Option<bool>,
#[option(
doc = r#"
A list of modules to consider first-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
known-first-party = ["src"]
"#
)]
pub known_first_party: Option<Vec<String>>,
#[option(
doc = r#"
A list of modules to consider third-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
known-third-party = ["src"]
"#
)]
pub known_third_party: Option<Vec<String>>,
#[option(
doc = r#"
A list of modules to consider standard-library, in addition to those known to Ruff in
advance.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
extra-standard-library = ["path"]
"#
)]
pub extra_standard_library: Option<Vec<String>>,
}

View File

@@ -47,4 +47,34 @@ expression: checks
end_location:
row: 16
column: 0
- kind: UnsortedImports
location:
row: 33
column: 0
end_location:
row: 35
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 33
column: 0
end_location:
row: 35
column: 0
- kind: UnsortedImports
location:
row: 39
column: 0
end_location:
row: 41
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 39
column: 0
end_location:
row: 41
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 2
column: 0
fix:
content: "from .subscription import * # type: ignore # some very long comment explaining why this needs a type ignore\n"
location:
row: 1
column: 0
end_location:
row: 2
column: 0

View File

@@ -23,6 +23,7 @@ pub struct ImportTracker<'a> {
blocks: Vec<Block<'a>>,
directives: &'a IsortDirectives,
split_index: usize,
nested: bool,
}
impl<'a> ImportTracker<'a> {
@@ -31,6 +32,7 @@ impl<'a> ImportTracker<'a> {
directives,
blocks: vec![Block::default()],
split_index: 0,
nested: false,
}
}
@@ -60,12 +62,16 @@ where
// Track manual splits.
while self.split_index < self.directives.splits.len() {
if stmt.location.row() >= self.directives.splits[self.split_index] {
self.finalize(Some(match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
self.finalize(Some(if self.nested {
Trailer::Sibling
} else {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}));
self.split_index += 1;
} else {
@@ -81,16 +87,22 @@ where
{
self.track_import(stmt);
} else {
self.finalize(Some(match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
self.finalize(Some(if self.nested {
Trailer::Sibling
} else {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}));
}
// Track scope.
let prev_nested = self.nested;
self.nested = true;
match &stmt.node {
StmtKind::FunctionDef { body, .. } => {
for stmt in body {
@@ -198,6 +210,7 @@ where
}
_ => {}
}
self.nested = prev_nested;
}
fn visit_annotation(&mut self, _: &'b Expr) {}
@@ -219,11 +232,16 @@ where
fn visit_comprehension(&mut self, _: &'b Comprehension) {}
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
let prev_nested = self.nested;
self.nested = true;
let ExcepthandlerKind::ExceptHandler { body, .. } = &excepthandler.node;
for stmt in body {
self.visit_stmt(stmt);
}
self.finalize(None);
self.nested = prev_nested;
}
fn visit_arguments(&mut self, _: &'b Arguments) {}

View File

@@ -70,7 +70,7 @@ mod pygrep_hooks;
mod pylint;
mod python;
mod pyupgrade;
mod rules;
mod ruff;
mod rustpython_helpers;
pub mod settings;
pub mod source_code_locator;

View File

@@ -133,7 +133,7 @@ pub(crate) fn check_path(
Ok(checks)
}
const MAX_ITERATIONS: usize = 100;
const MAX_ITERATIONS: usize = 1;
/// Lint the source code at the given `Path`.
pub fn lint_path(

View File

@@ -17,6 +17,7 @@ use std::process::ExitCode;
use std::sync::mpsc::channel;
use std::time::Instant;
use ::ruff::autofix::fixer;
use ::ruff::checks::{CheckCode, CheckKind};
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
use ::ruff::fs::iter_python_files;
@@ -29,7 +30,7 @@ use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
use ::ruff::{cache, commands};
use ::ruff::{cache, commands, fs};
use anyhow::Result;
use clap::{CommandFactory, Parser};
use colored::Colorize;
@@ -37,7 +38,6 @@ use log::{debug, error};
use notify::{recommended_watcher, RecursiveMode, Watcher};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ruff::autofix::fixer;
use rustpython_ast::Location;
use walkdir::DirEntry;
@@ -210,10 +210,12 @@ fn inner_main() -> Result<ExitCode> {
}
// Find the project root and pyproject.toml.
let project_root = pyproject::find_project_root(&cli.files);
let pyproject = cli
.config
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
let config: Option<PathBuf> = cli.config;
let project_root = config.as_ref().map_or_else(
|| pyproject::find_project_root(&cli.files),
|config| config.parent().map(fs::normalize_path),
);
let pyproject = config.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
// Reconcile configuration from pyproject.toml and command-line arguments.
let mut configuration =

View File

@@ -1,10 +1,20 @@
//! Settings for the `mccabe` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = "The maximum McCabe complexity to allow before triggering `C901` errors.",
default = "10",
value_type = "usize",
example = r#"
# Flag errors (`C901`) whenever the complexity level exceeds 5.
max-complexity = 5
"#
)]
pub max_complexity: Option<usize>,
}

View File

@@ -1,5 +1,6 @@
//! Settings for the `pep8-naming` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
const IGNORE_NAMES: [&str; 12] = [
@@ -21,11 +22,47 @@ const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
A list of names to ignore when considering `pep8-naming` violations.
"#,
default = r#"["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]"#,
value_type = "Vec<String>",
example = r#"
ignore-names = ["callMethod"]
"#
)]
pub ignore_names: Option<Vec<String>>,
#[option(
doc = r#"
A list of decorators that, when applied to a method, indicate that the method should be
treated as a class method. For example, Ruff will expect that any method decorated by a
decorator in this list takes a `cls` argument as its first argument.
"#,
default = r#"["classmethod"]"#,
value_type = "Vec<String>",
example = r#"
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
classmethod-decorators = ["classmethod", "pydantic.validator"]
"#
)]
pub classmethod_decorators: Option<Vec<String>>,
#[option(
doc = r#"
A list of decorators that, when applied to a method, indicate that the method should be
treated as a static method. For example, Ruff will expect that any method decorated by a
decorator in this list has no `self` or `cls` argument.
"#,
default = r#"["staticmethod"]"#,
value_type = "Vec<String>",
example = r#"
# Allow a shorthand alias, `@stcmthd`, to trigger static method treatment.
staticmethod-decorators = ["staticmethod", "stcmthd"]
"#
)]
pub staticmethod_decorators: Option<Vec<String>>,
}

43
src/pydocstyle/helpers.rs Normal file
View File

@@ -0,0 +1,43 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::docstrings::constants;
use crate::SourceCodeLocator;
/// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
if let Some(first_line) = locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(str::to_lowercase)
{
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()
.chain(constants::SINGLE_QUOTE_PREFIXES)
{
if first_line.starts_with(pattern) {
return Some(pattern);
}
}
}
None
}
/// Return the index of the first logical line in a string.
pub fn logical_line(content: &str) -> Option<usize> {
// Find the first logical line.
let mut logical_line = None;
for (i, line) in content.lines().enumerate() {
if line.trim().is_empty() {
// Empty line. If this is the line _after_ the first logical line, stop.
if logical_line.is_some() {
break;
}
} else {
// Non-empty line. Store the index.
logical_line = Some(i);
}
}
logical_line
}

View File

@@ -1,3 +1,4 @@
mod helpers;
pub mod plugins;
#[cfg(test)]
@@ -36,7 +37,8 @@ mod tests {
#[test_case(CheckCode::D214, Path::new("sections.py"); "D214")]
#[test_case(CheckCode::D215, Path::new("sections.py"); "D215")]
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
#[test_case(CheckCode::D400, Path::new("D.py"); "D400")]
#[test_case(CheckCode::D400, Path::new("D.py"); "D400_0")]
#[test_case(CheckCode::D400, Path::new("D400.py"); "D400_1")]
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]
#[test_case(CheckCode::D403, Path::new("D.py"); "D403")]
#[test_case(CheckCode::D404, Path::new("D.py"); "D404")]

View File

@@ -16,6 +16,7 @@ use crate::docstrings::constants;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::sections::{section_contexts, SectionContext};
use crate::docstrings::styles::SectionStyle;
use crate::pydocstyle::helpers::{leading_quote, logical_line};
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
/// D100, D101, D102, D103, D104, D105, D106, D107
@@ -388,15 +389,17 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
}
}
// Insert one blank line after the summary (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.location.row() + summary_line + 1, 0),
Location::new(
docstring.location.row() + summary_line + 1 + blanks_count,
0,
),
));
if blanks_count > 1 {
// Insert one blank line after the summary (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.location.row() + summary_line + 1, 0),
Location::new(
docstring.location.row() + summary_line + 1 + blanks_count,
0,
),
));
}
}
checker.add_check(check);
}
@@ -619,18 +622,11 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
Range::from_located(docstring),
);
if checker.patch(check.kind.code()) {
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(str::to_lowercase)
{
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()
.chain(constants::SINGLE_QUOTE_PREFIXES)
{
if first_line.starts_with(pattern) {
if let Some(pattern) = leading_quote(docstring, checker.locator) {
if let Some(quote) = pattern.chars().last() {
// If removing whitespace would lead to an invalid string of quote
// characters, avoid applying the fix.
if !trimmed.ends_with(quote) {
check.amend(Fix::replacement(
trimmed.to_string(),
Location::new(
@@ -642,7 +638,6 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
docstring.location.column() + pattern.len() + line.chars().count(),
),
));
break;
}
}
}
@@ -741,16 +736,30 @@ pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
} = &docstring.node else {
return;
};
let Some(string) = string.trim().lines().next() else {
return;
};
if string.ends_with('.') {
return;
};
checker.add_check(Check::new(
CheckKind::EndsInPeriod,
Range::from_located(docstring),
));
if let Some(index) = logical_line(string) {
let line = string.lines().nth(index).unwrap();
let trimmed = line.trim_end();
if !trimmed.ends_with('.') {
let mut check = Check::new(CheckKind::EndsInPeriod, Range::from_located(docstring));
// Best-effort autofix: avoid adding a period after other punctuation marks.
if checker.patch(&CheckCode::D400) && !trimmed.ends_with(':') && !trimmed.ends_with(';')
{
if let Some((row, column)) = if index == 0 {
leading_quote(docstring, checker.locator).map(|pattern| {
(
docstring.location.row(),
docstring.location.column() + pattern.len() + trimmed.chars().count(),
)
})
} else {
Some((docstring.location.row() + index, trimmed.chars().count()))
} {
check.amend(Fix::insertion(".".to_string(), Location::new(row, column)));
}
}
checker.add_check(check);
};
}
}
/// D402
@@ -868,16 +877,31 @@ pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
} = &docstring.node else {
return
};
let Some(string) = string.trim().lines().next() else {
return
};
if string.ends_with('.') || string.ends_with('!') || string.ends_with('?') {
return;
if let Some(index) = logical_line(string) {
let line = string.lines().nth(index).unwrap();
let trimmed = line.trim_end();
if !(trimmed.ends_with('.') || trimmed.ends_with('!') || trimmed.ends_with('?')) {
let mut check =
Check::new(CheckKind::EndsInPunctuation, Range::from_located(docstring));
// Best-effort autofix: avoid adding a period after other punctuation marks.
if checker.patch(&CheckCode::D415) && !trimmed.ends_with(':') && !trimmed.ends_with(';')
{
if let Some((row, column)) = if index == 0 {
leading_quote(docstring, checker.locator).map(|pattern| {
(
docstring.location.row(),
docstring.location.column() + pattern.len() + trimmed.chars().count(),
)
})
} else {
Some((docstring.location.row() + index, trimmed.chars().count()))
} {
check.amend(Fix::insertion(".".to_string(), Location::new(row, column)));
}
}
checker.add_check(check);
};
}
checker.add_check(Check::new(
CheckKind::EndsInPunctuation,
Range::from_located(docstring),
));
}
/// D418

View File

@@ -9,14 +9,7 @@ expression: checks
end_location:
row: 203
column: 7
fix:
content: "\n"
location:
row: 201
column: 0
end_location:
row: 201
column: 0
fix: ~
- kind: BlankLineAfterSummary
location:
row: 210

View File

@@ -47,4 +47,12 @@ expression: checks
end_location:
row: 299
column: 36
- kind: NoSurroundingWhitespace
location:
row: 581
column: 4
end_location:
row: 581
column: 51
fix: ~

View File

@@ -9,7 +9,14 @@ expression: checks
end_location:
row: 355
column: 17
fix: ~
fix:
content: "."
location:
row: 355
column: 14
end_location:
row: 355
column: 14
- kind: EndsInPeriod
location:
row: 406
@@ -17,7 +24,14 @@ expression: checks
end_location:
row: 406
column: 39
fix: ~
fix:
content: "."
location:
row: 406
column: 36
end_location:
row: 406
column: 36
- kind: EndsInPeriod
location:
row: 410
@@ -25,7 +39,14 @@ expression: checks
end_location:
row: 410
column: 24
fix: ~
fix:
content: "."
location:
row: 410
column: 21
end_location:
row: 410
column: 21
- kind: EndsInPeriod
location:
row: 416
@@ -33,7 +54,14 @@ expression: checks
end_location:
row: 416
column: 24
fix: ~
fix:
content: "."
location:
row: 416
column: 21
end_location:
row: 416
column: 21
- kind: EndsInPeriod
location:
row: 422
@@ -41,7 +69,14 @@ expression: checks
end_location:
row: 422
column: 49
fix: ~
fix:
content: "."
location:
row: 422
column: 46
end_location:
row: 422
column: 46
- kind: EndsInPeriod
location:
row: 429
@@ -49,7 +84,14 @@ expression: checks
end_location:
row: 429
column: 63
fix: ~
fix:
content: "."
location:
row: 429
column: 60
end_location:
row: 429
column: 60
- kind: EndsInPeriod
location:
row: 470
@@ -57,7 +99,14 @@ expression: checks
end_location:
row: 470
column: 24
fix: ~
fix:
content: "."
location:
row: 470
column: 21
end_location:
row: 470
column: 21
- kind: EndsInPeriod
location:
row: 475
@@ -65,7 +114,14 @@ expression: checks
end_location:
row: 475
column: 24
fix: ~
fix:
content: "."
location:
row: 475
column: 21
end_location:
row: 475
column: 21
- kind: EndsInPeriod
location:
row: 480
@@ -73,7 +129,14 @@ expression: checks
end_location:
row: 480
column: 24
fix: ~
fix:
content: "."
location:
row: 480
column: 21
end_location:
row: 480
column: 21
- kind: EndsInPeriod
location:
row: 487
@@ -81,7 +144,14 @@ expression: checks
end_location:
row: 487
column: 24
fix: ~
fix:
content: "."
location:
row: 487
column: 21
end_location:
row: 487
column: 21
- kind: EndsInPeriod
location:
row: 509
@@ -89,7 +159,14 @@ expression: checks
end_location:
row: 509
column: 34
fix: ~
fix:
content: "."
location:
row: 509
column: 31
end_location:
row: 509
column: 31
- kind: EndsInPeriod
location:
row: 514
@@ -97,7 +174,14 @@ expression: checks
end_location:
row: 514
column: 33
fix: ~
fix:
content: "."
location:
row: 514
column: 30
end_location:
row: 514
column: 30
- kind: EndsInPeriod
location:
row: 520
@@ -105,5 +189,27 @@ expression: checks
end_location:
row: 520
column: 32
fix: ~
fix:
content: "."
location:
row: 520
column: 29
end_location:
row: 520
column: 29
- kind: EndsInPeriod
location:
row: 581
column: 4
end_location:
row: 581
column: 51
fix:
content: "."
location:
row: 581
column: 47
end_location:
row: 581
column: 47

View File

@@ -0,0 +1,185 @@
---
source: src/pydocstyle/mod.rs
expression: checks
---
- kind: EndsInPeriod
location:
row: 2
column: 4
end_location:
row: 2
column: 36
fix:
content: "."
location:
row: 2
column: 35
end_location:
row: 2
column: 35
- kind: EndsInPeriod
location:
row: 7
column: 4
end_location:
row: 7
column: 40
fix:
content: "."
location:
row: 7
column: 37
end_location:
row: 7
column: 37
- kind: EndsInPeriod
location:
row: 12
column: 4
end_location:
row: 15
column: 7
fix:
content: "."
location:
row: 14
column: 28
end_location:
row: 14
column: 28
- kind: EndsInPeriod
location:
row: 20
column: 4
end_location:
row: 20
column: 40
fix:
content: "."
location:
row: 20
column: 37
end_location:
row: 20
column: 37
- kind: EndsInPeriod
location:
row: 25
column: 4
end_location:
row: 27
column: 31
fix:
content: "."
location:
row: 27
column: 28
end_location:
row: 27
column: 28
- kind: EndsInPeriod
location:
row: 32
column: 4
end_location:
row: 34
column: 52
fix:
content: "."
location:
row: 34
column: 48
end_location:
row: 34
column: 48
- kind: EndsInPeriod
location:
row: 40
column: 4
end_location:
row: 40
column: 37
fix:
content: "."
location:
row: 40
column: 36
end_location:
row: 40
column: 36
- kind: EndsInPeriod
location:
row: 45
column: 4
end_location:
row: 45
column: 41
fix:
content: "."
location:
row: 45
column: 38
end_location:
row: 45
column: 38
- kind: EndsInPeriod
location:
row: 50
column: 4
end_location:
row: 53
column: 7
fix:
content: "."
location:
row: 52
column: 28
end_location:
row: 52
column: 28
- kind: EndsInPeriod
location:
row: 58
column: 4
end_location:
row: 58
column: 41
fix:
content: "."
location:
row: 58
column: 38
end_location:
row: 58
column: 38
- kind: EndsInPeriod
location:
row: 63
column: 4
end_location:
row: 65
column: 31
fix:
content: "."
location:
row: 65
column: 28
end_location:
row: 65
column: 28
- kind: EndsInPeriod
location:
row: 70
column: 4
end_location:
row: 72
column: 52
fix:
content: "."
location:
row: 72
column: 48
end_location:
row: 72
column: 48

View File

@@ -9,7 +9,14 @@ expression: checks
end_location:
row: 355
column: 17
fix: ~
fix:
content: "."
location:
row: 355
column: 14
end_location:
row: 355
column: 14
- kind: EndsInPunctuation
location:
row: 406
@@ -17,7 +24,14 @@ expression: checks
end_location:
row: 406
column: 39
fix: ~
fix:
content: "."
location:
row: 406
column: 36
end_location:
row: 406
column: 36
- kind: EndsInPunctuation
location:
row: 410
@@ -25,7 +39,14 @@ expression: checks
end_location:
row: 410
column: 24
fix: ~
fix:
content: "."
location:
row: 410
column: 21
end_location:
row: 410
column: 21
- kind: EndsInPunctuation
location:
row: 416
@@ -33,7 +54,14 @@ expression: checks
end_location:
row: 416
column: 24
fix: ~
fix:
content: "."
location:
row: 416
column: 21
end_location:
row: 416
column: 21
- kind: EndsInPunctuation
location:
row: 422
@@ -41,7 +69,14 @@ expression: checks
end_location:
row: 422
column: 49
fix: ~
fix:
content: "."
location:
row: 422
column: 46
end_location:
row: 422
column: 46
- kind: EndsInPunctuation
location:
row: 429
@@ -49,7 +84,14 @@ expression: checks
end_location:
row: 429
column: 63
fix: ~
fix:
content: "."
location:
row: 429
column: 60
end_location:
row: 429
column: 60
- kind: EndsInPunctuation
location:
row: 470
@@ -57,7 +99,14 @@ expression: checks
end_location:
row: 470
column: 24
fix: ~
fix:
content: "."
location:
row: 470
column: 21
end_location:
row: 470
column: 21
- kind: EndsInPunctuation
location:
row: 475
@@ -65,7 +114,14 @@ expression: checks
end_location:
row: 475
column: 24
fix: ~
fix:
content: "."
location:
row: 475
column: 21
end_location:
row: 475
column: 21
- kind: EndsInPunctuation
location:
row: 480
@@ -73,7 +129,14 @@ expression: checks
end_location:
row: 480
column: 24
fix: ~
fix:
content: "."
location:
row: 480
column: 21
end_location:
row: 480
column: 21
- kind: EndsInPunctuation
location:
row: 487
@@ -81,7 +144,14 @@ expression: checks
end_location:
row: 487
column: 24
fix: ~
fix:
content: "."
location:
row: 487
column: 21
end_location:
row: 487
column: 21
- kind: EndsInPunctuation
location:
row: 509
@@ -89,7 +159,14 @@ expression: checks
end_location:
row: 509
column: 34
fix: ~
fix:
content: "."
location:
row: 509
column: 31
end_location:
row: 509
column: 31
- kind: EndsInPunctuation
location:
row: 520
@@ -97,5 +174,27 @@ expression: checks
end_location:
row: 520
column: 32
fix: ~
fix:
content: "."
location:
row: 520
column: 29
end_location:
row: 520
column: 29
- kind: EndsInPunctuation
location:
row: 581
column: 4
end_location:
row: 581
column: 51
fix:
content: "."
location:
row: 581
column: 47
end_location:
row: 581
column: 47

View File

@@ -71,7 +71,8 @@ mod tests {
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
#[test_case(CheckCode::F841, Path::new("F841.py"); "F841")]
#[test_case(CheckCode::F841, Path::new("F841_0.py"); "F841_0")]
#[test_case(CheckCode::F841, Path::new("F841_1.py"); "F841_1")]
#[test_case(CheckCode::F901, Path::new("F901.py"); "F901")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
@@ -90,7 +91,7 @@ mod tests {
#[test]
fn f841_dummy_variable_rgx() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/pyflakes/F841.py"),
Path::new("./resources/test/fixtures/pyflakes/F841_0.py"),
&settings::Settings {
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
..settings::Settings::for_rule(CheckCode::F841)

View File

@@ -47,6 +47,15 @@ expression: checks
row: 21
column: 9
fix: ~
- kind:
UnusedVariable: baz
location:
row: 26
column: 13
end_location:
row: 26
column: 16
fix: ~
- kind:
UnusedVariable: b
location:
@@ -56,4 +65,13 @@ expression: checks
row: 51
column: 9
fix: ~
- kind:
UnusedVariable: f
location:
row: 71
column: 25
end_location:
row: 71
column: 26
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
UnusedVariable: x
location:
row: 6
column: 4
end_location:
row: 6
column: 5
fix: ~
- kind:
UnusedVariable: y
location:
row: 6
column: 7
end_location:
row: 6
column: 8
fix: ~
- kind:
UnusedVariable: coords
location:
row: 16
column: 13
end_location:
row: 16
column: 19
fix: ~
- kind:
UnusedVariable: coords
location:
row: 20
column: 4
end_location:
row: 20
column: 10
fix: ~
- kind:
UnusedVariable: a
location:
row: 24
column: 5
end_location:
row: 24
column: 6
fix: ~
- kind:
UnusedVariable: b
location:
row: 24
column: 8
end_location:
row: 24
column: 9
fix: ~
- kind:
UnusedVariable: x
location:
row: 24
column: 14
end_location:
row: 24
column: 15
fix: ~
- kind:
UnusedVariable: y
location:
row: 24
column: 17
end_location:
row: 24
column: 18
fix: ~

View File

@@ -38,6 +38,15 @@ expression: checks
row: 21
column: 9
fix: ~
- kind:
UnusedVariable: baz
location:
row: 26
column: 13
end_location:
row: 26
column: 16
fix: ~
- kind:
UnusedVariable: _
location:
@@ -74,4 +83,13 @@ expression: checks
row: 51
column: 9
fix: ~
- kind:
UnusedVariable: f
location:
row: 71
column: 25
end_location:
row: 71
column: 26
fix: ~

View File

@@ -1,10 +1,22 @@
//! Settings for the `pyupgrade` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
"#
)]
pub keep_runtime_typing: Option<bool>,
}

View File

@@ -0,0 +1,59 @@
---
source: src/ruff/mod.rs
expression: checks
---
- kind:
AmbiguousUnicodeCharacterString:
- 𝐁
- B
location:
row: 1
column: 5
end_location:
row: 1
column: 6
fix:
content: B
location:
row: 1
column: 5
end_location:
row: 1
column: 6
- kind:
AmbiguousUnicodeCharacterDocstring:
-
- )
location:
row: 6
column: 55
end_location:
row: 6
column: 56
fix:
content: )
location:
row: 6
column: 55
end_location:
row: 6
column: 56
- kind:
AmbiguousUnicodeCharacterComment:
-
- /
location:
row: 7
column: 61
end_location:
row: 7
column: 62
fix:
content: /
location:
row: 7
column: 61
end_location:
row: 7
column: 62

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -0,0 +1,185 @@
---
source: src/ruff/mod.rs
expression: checks
---
- kind:
UnusedNOQA: ~
location:
row: 9
column: 11
end_location:
row: 9
column: 17
fix:
content: ""
location:
row: 9
column: 9
end_location:
row: 9
column: 17
- kind:
UnusedNOQA:
- E501
location:
row: 13
column: 11
end_location:
row: 13
column: 23
fix:
content: ""
location:
row: 13
column: 9
end_location:
row: 13
column: 23
- kind:
UnusedNOQA:
- F841
- E501
location:
row: 16
column: 11
end_location:
row: 16
column: 29
fix:
content: ""
location:
row: 16
column: 9
end_location:
row: 16
column: 29
- kind:
UnusedNOQA:
- F841
- W191
location:
row: 19
column: 11
end_location:
row: 19
column: 29
fix:
content: ""
location:
row: 19
column: 9
end_location:
row: 19
column: 29
- kind:
UnusedNOQA:
- F841
- V101
location:
row: 22
column: 11
end_location:
row: 22
column: 29
fix:
content: ""
location:
row: 22
column: 9
end_location:
row: 22
column: 29
- kind:
UnusedNOQA:
- E501
location:
row: 26
column: 9
end_location:
row: 26
column: 21
fix:
content: ""
location:
row: 26
column: 9
end_location:
row: 26
column: 21
- kind:
UnusedVariable: d
location:
row: 29
column: 4
end_location:
row: 29
column: 5
fix: ~
- kind:
UnusedNOQA:
- E501
location:
row: 29
column: 32
end_location:
row: 29
column: 44
fix:
content: ""
location:
row: 29
column: 9
end_location:
row: 29
column: 44
- kind:
UnusedNOQA:
- F841
location:
row: 55
column: 5
end_location:
row: 55
column: 23
fix:
content: "# noqa: E501"
location:
row: 55
column: 5
end_location:
row: 55
column: 23
- kind:
UnusedNOQA:
- E501
location:
row: 63
column: 5
end_location:
row: 63
column: 17
fix:
content: ""
location:
row: 63
column: 3
end_location:
row: 63
column: 17
- kind:
UnusedNOQA: ~
location:
row: 71
column: 5
end_location:
row: 71
column: 11
fix:
content: ""
location:
row: 71
column: 3
end_location:
row: 71
column: 11

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -23,6 +23,7 @@ use crate::{
pub mod configuration;
pub mod options;
pub mod options_base;
pub mod pyproject;
pub mod types;

View File

@@ -1,5 +1,6 @@
//! Options that the user can provide via pyproject.toml.
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
@@ -10,36 +11,277 @@ use crate::{
pep8_naming, pyupgrade,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`,
`RUF002`, and `RUF003`.
"#,
default = r#"[]"#,
value_type = "Vec<char>",
example = r#"
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217),
# which could be confused for "-", "p", and "*", respectively.
allowed-confusables = ["", "ρ", ""]
"#
)]
pub allowed_confusables: Option<Vec<char>>,
#[option(
doc = r#"
A regular expression used to identify "dummy" variables, or those which should be
ignored when evaluating (e.g.) unused-variable checks. The default expression matches
`_`, `__`, and `_var`, but not `_var_`.
"#,
default = r#""^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$""#,
value_type = "Regex",
example = r#"
# Only ignore variables named "_".
dummy_variable_rgx = "^_$"
"#
)]
pub dummy_variable_rgx: Option<String>,
#[option(
doc = r#"
A list of file patterns to exclude from linting.
Exclusions are based on globs, and can be either:
- Single-path patterns, like `.mypy_cache` (to exclude any directory named `.mypy_cache` in the
tree), `foo.py` (to exclude any file named `foo.py`), or `foo_*.py` (to exclude any file matching
`foo_*.py` ).
- Relative patterns, like `directory/foo.py` (to exclude that specific file) or `directory/*.py`
(to exclude any Python files in `directory`). Note that these paths are relative to the
project root (e.g., the directory containing your `pyproject.toml`).
Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify
the excluded paths.
"#,
default = r#"[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]"#,
value_type = "Vec<FilePattern>",
example = r#"
exclude = [".venv"]
"#
)]
pub exclude: Option<Vec<String>>,
#[option(
doc = "A list of file patterns to omit from linting, in addition to those specified by \
`exclude`.",
default = "[]",
value_type = "Vec<FilePattern>",
example = r#"
# In addition to the standard set of exclusions, omit all tests, plus a specific file.
extend-exclude = ["tests", "src/bad.py"]
"#
)]
pub extend_exclude: Option<Vec<String>>,
#[option(
doc = "A list of check code prefixes to ignore, in addition to those specified by \
`ignore`.",
default = "[]",
value_type = "Vec<CheckCodePrefix>",
example = r#"
# Skip unused variable checks (`F841`).
extend-ignore = ["F841"]
"#
)]
pub extend_ignore: Option<Vec<CheckCodePrefix>>,
#[option(
doc = "A list of check code prefixes to enable, in addition to those specified by \
`select`.",
default = "[]",
value_type = "Vec<CheckCodePrefix>",
example = r#"
# On top of the default `select` (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
extend-select = ["B", "Q"]
"#
)]
pub extend_select: Option<Vec<CheckCodePrefix>>,
#[option(
doc = r#"
A list of check codes that are unsupported by Ruff, but should be preserved when (e.g.)
validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not
yet implemented in Ruff.
"#,
default = "[]",
value_type = "Vec<String>",
example = r#"
# Avoiding flagging (and removing) `V101` from any `# noqa`
# directives, despite Ruff's lack of support for `vulture`.
external = ["V101"]
"#
)]
pub external: Option<Vec<String>>,
#[option(
doc = r#"
Enable autofix behavior by-default when running `ruff` (overridden
by the `--fix` and `--no-fix` command-line flags).
"#,
default = "false",
value_type = "bool",
example = "fix = true"
)]
pub fix: Option<bool>,
#[option(
doc = "A list of check code prefixes to consider autofix-able.",
default = r#"["A", "ANN", "B", "BLE", "C", "D", "E", "F", "FBT", "I", "M", "N", "Q", "RUF", "S", "T", "U", "W", "YTT"]"#,
value_type = "Vec<CheckCodePrefix>",
example = r#"
# Only allow autofix behavior for `E` and `F` checks.
fixable = ["E", "F"]
"#
)]
pub fixable: Option<Vec<CheckCodePrefix>>,
#[option(
doc = r#"
The style in which violation messages should be formatted: `"text"` (default),
`"grouped"` (group messages by file), `"json"` (machine-readable), `"junit"`
(machine-readable XML), or `"github"` (GitHub Actions annotations).
"#,
default = r#""text""#,
value_type = "SerializationType",
example = r#"
# Group violations by containing file.
format = "grouped"
"#
)]
pub format: Option<SerializationFormat>,
#[option(
doc = r"
A list of check code prefixes to ignore. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
",
default = "[]",
value_type = "Vec<CheckCodePrefix>",
example = r#"
# Skip unused variable checks (`F841`).
ignore = ["F841"]
"#
)]
pub ignore: Option<Vec<CheckCodePrefix>>,
#[option(
doc = r#"
Avoid automatically removing unused imports in `__init__.py` files. Such imports will
still be +flagged, but with a dedicated message suggesting that the import is either
added to the module' +`__all__` symbol, or re-exported with a redundant alias (e.g.,
`import os as os`).
"#,
default = "false",
value_type = "bool",
example = r#"
ignore-init-module-imports = true
"#
)]
pub ignore_init_module_imports: Option<bool>,
#[option(
doc = "The line length to use when enforcing long-lines violations (like E501).",
default = "88",
value_type = "usize",
example = r#"
# Allow lines to be as long as 120 characters.
line-length = 120
"#
)]
pub line_length: Option<usize>,
#[option(
doc = r#"
A list of check code prefixes to enable. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
"#,
default = r#"["E", "F"]"#,
value_type = "Vec<CheckCodePrefix>",
example = r#"
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
select = ["E", "F", "B", "Q"]
"#
)]
pub select: Option<Vec<CheckCodePrefix>>,
#[option(
doc = r#"
Whether to show source code snippets when reporting lint error violations (overridden by
the `--show-source` command-line flag).
"#,
default = "false",
value_type = "bool",
example = r#"
# By default, always show source code snippets.
show-source = true
"#
)]
pub show_source: Option<bool>,
#[option(
doc = "The source code paths to consider, e.g., when resolving first- vs. third-party \
imports.",
default = r#"["."]"#,
value_type = "Vec<PathBuf>",
example = r#"
# Allow imports relative to the "src" and "test" directories.
src = ["src", "test"]
"#
)]
pub src: Option<Vec<String>>,
#[option(
doc = r#"
The Python version to target, e.g., when considering automatic code upgrades, like
rewriting type annotations. Note that the target version will _not_ be inferred from the
_current_ Python version, and instead must be specified explicitly (as seen below).
"#,
default = r#""py310""#,
value_type = "PythonVersion",
example = r#"
# Always generate Python 3.7-compatible code.
target-version = "py37"
"#
)]
pub target_version: Option<PythonVersion>,
#[option(
doc = "A list of check code prefixes to consider un-autofix-able.",
default = "[]",
value_type = "Vec<CheckCodePrefix>",
example = r#"
# Disable autofix for unused imports (`F401`).
unfixable = ["F401"]
"#
)]
pub unfixable: Option<Vec<CheckCodePrefix>>,
// Plugins
#[option_group]
pub flake8_annotations: Option<flake8_annotations::settings::Options>,
#[option_group]
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,
#[option_group]
pub flake8_quotes: Option<flake8_quotes::settings::Options>,
#[option_group]
pub flake8_tidy_imports: Option<flake8_tidy_imports::settings::Options>,
#[option_group]
pub isort: Option<isort::settings::Options>,
#[option_group]
pub mccabe: Option<mccabe::settings::Options>,
#[option_group]
pub pep8_naming: Option<pep8_naming::settings::Options>,
#[option_group]
pub pyupgrade: Option<pyupgrade::settings::Options>,
// Tables are required to go last.
#[option(
doc = r#"
A list of mappings from file pattern to check code prefixes to exclude, when considering
any matching files.
"#,
default = "{}",
value_type = "HashMap<String, Vec<CheckCodePrefix>>",
example = r#"
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
[tool.ruff.per-file-ignores]
"__init__.py" = ["E402"]
"path/to/file.py" = ["E402"]
"#
)]
pub per_file_ignores: Option<FxHashMap<String, Vec<CheckCodePrefix>>>,
}

View File

@@ -0,0 +1,24 @@
#[derive(Debug)]
pub struct OptionGroup {
pub name: &'static str,
pub fields: Vec<OptionEntry>,
}
#[derive(Debug)]
pub struct OptionField {
pub name: &'static str,
pub doc: &'static str,
pub default: &'static str,
pub value_type: &'static str,
pub example: &'static str,
}
#[derive(Debug)]
pub enum OptionEntry {
Field(OptionField),
Group(OptionGroup),
}
pub trait ConfigurationOptions {
fn get_available_options() -> Vec<OptionEntry>;
}