Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2ad915224 | ||
|
|
71543eeabc | ||
|
|
44025f1c92 | ||
|
|
971bf6d232 | ||
|
|
0acc47386a | ||
|
|
982ac6b0ad | ||
|
|
541440f7a8 | ||
|
|
66dde46e03 | ||
|
|
1339e2a002 | ||
|
|
38ad10f60d | ||
|
|
88e78c5cde | ||
|
|
436aeed20a | ||
|
|
b94169a8bb | ||
|
|
995994be3e | ||
|
|
f001305b2e | ||
|
|
e88093541f | ||
|
|
da41a495f1 | ||
|
|
55b7ec8f85 |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.161
|
||||
rev: v0.0.164
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.161-dev.0"
|
||||
version = "0.0.164-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1821,7 +1821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.161"
|
||||
version = "0.0.164"
|
||||
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.164"
|
||||
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"
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.161"
|
||||
version = "0.0.164"
|
||||
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
590
README.md
@@ -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.164
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.161"
|
||||
version = "0.0.164"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.161"
|
||||
version = "0.0.164"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.161-dev.0"
|
||||
version = "0.0.164-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -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
|
||||
|
||||
1
resources/test/fixtures/isort/no_wrap_star.py
vendored
Normal file
1
resources/test/fixtures/isort/no_wrap_star.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from .subscription import * # type: ignore # some very long comment explaining why this needs a type ignore
|
||||
8
resources/test/fixtures/pydocstyle/D.py
vendored
8
resources/test/fixtures/pydocstyle/D.py
vendored
@@ -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" """
|
||||
|
||||
73
resources/test/fixtures/pydocstyle/D400.py
vendored
Normal file
73
resources/test/fixtures/pydocstyle/D400.py
vendored
Normal 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 """
|
||||
...
|
||||
@@ -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")
|
||||
24
resources/test/fixtures/pyflakes/F841_1.py
vendored
Normal file
24
resources/test/fixtures/pyflakes/F841_1.py
vendored
Normal 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
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.161"
|
||||
version = "0.0.164"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
125
ruff_dev/src/generate_options.rs
Normal file
125
ruff_dev/src/generate_options.rs
Normal 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(())
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
13
ruff_macros/Cargo.toml
Normal 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
177
ruff_macros/src/lib.rs
Normal 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")),
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2497,6 +2497,8 @@ impl CheckKind {
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DoNotAssignLambda
|
||||
| CheckKind::DuplicateHandlerException(..)
|
||||
| CheckKind::EndsInPeriod
|
||||
| CheckKind::EndsInPunctuation
|
||||
| CheckKind::GetAttrWithConstant
|
||||
| CheckKind::ImplicitReturn
|
||||
| CheckKind::ImplicitReturnValue
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
20
src/isort/snapshots/ruff__isort__tests__no_wrap_star.py.snap
Normal file
20
src/isort/snapshots/ruff__isort__tests__no_wrap_star.py.snap
Normal 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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@@ -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 =
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
43
src/pydocstyle/helpers.rs
Normal 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
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use crate::{
|
||||
|
||||
pub mod configuration;
|
||||
pub mod options;
|
||||
pub mod options_base;
|
||||
pub mod pyproject;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -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>>>,
|
||||
}
|
||||
|
||||
24
src/settings/options_base.rs
Normal file
24
src/settings/options_base.rs
Normal 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>;
|
||||
}
|
||||
Reference in New Issue
Block a user