Compare commits

...

40 Commits

Author SHA1 Message Date
Charlie Marsh
cc2110449c Run cargo dev commands 2022-12-24 15:06:12 -05:00
Charlie Marsh
f6ca49e05f Bump version to 0.0.193 2022-12-24 14:56:26 -05:00
Charlie Marsh
9a7331b2e2 Annotate RUF100 messages with unmatched, disabled, and unknown codes (#1365) 2022-12-24 14:55:55 -05:00
Edgar R. M
4888afd423 Generate JSON schema for Ruff options (#1329) 2022-12-24 14:10:22 -05:00
Sawbez
0dc523b081 Add autofix for W605 [InvalidEscapeSequence] (#1361) 2022-12-24 13:46:28 -05:00
Harutaka Kawamura
63772e335d Fix B025 location (#1360) 2022-12-24 12:22:11 -05:00
Reiner Gerecke
7f4ff1e38f Fix false-positive in RET504 when referencing globals (#1358) 2022-12-24 12:02:57 -05:00
Reiner Gerecke
32ebc1d227 Don't trigger E721 when comparing with None (#1356) 2022-12-24 04:45:40 -05:00
Sawbez
4ded155dc0 Add autofix for W292 [NoNewLineAtEndOfFile] (#1354) 2022-12-23 23:14:17 -05:00
Harutaka Kawamura
201e1250de Update RustPython to use the correct BinOp location (#1355) 2022-12-23 22:58:39 -05:00
Reiner Gerecke
102b049a32 Add cache-dir to command-line and pyproject.toml (#1351) 2022-12-23 22:58:29 -05:00
Charlie Marsh
74f49eda64 Bump compatibility to 3.11 (#1352) 2022-12-23 12:12:11 -05:00
Reiner Gerecke
9da3e2cca1 Implement "native literals" check from pyupgrade (#1350) 2022-12-23 11:40:32 -05:00
Charlie Marsh
e290050821 Avoid enabling all EM checks at once (#1349) 2022-12-23 08:25:07 -05:00
Charlie Marsh
bc9ed0a4ef Tweak LSP docs 2022-12-22 22:16:32 -05:00
Charlie Marsh
20b9b44973 Link to ruff-lsp docs 2022-12-22 21:53:11 -05:00
Charlie Marsh
6e5a553235 Update Neovim instructions 2022-12-22 21:52:30 -05:00
Charlie Marsh
2a08a63f17 Add a link to the PyCharm plugin (#1345) 2022-12-22 21:50:24 -05:00
Colin Delahunty
d4290e6721 Update CONTRIBUTING.md (#1344) 2022-12-22 21:04:19 -05:00
Charlie Marsh
51bda28a7d Bump version to 0.0.192 2022-12-22 17:31:31 -05:00
Reiner Gerecke
cc26051b7a Implement "datetime.UTC alias" check from pyupgrade (#1341) 2022-12-22 17:21:36 -05:00
Charlie Marsh
3ac5a9aa31 Respect --force-exclude for files passed via stdin (#1342) 2022-12-22 16:40:15 -05:00
Charlie Marsh
451047c30d Exclude directly-passed files nested in excluded subdirectories 2022-12-22 15:08:11 -05:00
Charlie Marsh
6907df489b Extend false-positive list for flake8-boolean-trap (#1338) 2022-12-22 10:56:04 -05:00
Charlie Marsh
970f882b03 Set force-exclude for pre-commit in README (#1337) 2022-12-22 10:51:20 -05:00
Charlie Marsh
3eff9a2860 Allow unittest methods in flake8-boolean-trap (#1333) 2022-12-22 08:40:22 -05:00
Charlie Marsh
a4a24a0ef3 Add some more repositories to the user list (#1328) 2022-12-21 22:16:47 -05:00
Charlie Marsh
48e3c046b0 Fix integration tests 2022-12-21 21:25:37 -05:00
Charlie Marsh
03e4f5be8a Bump version to 0.0.191 2022-12-21 21:16:21 -05:00
Charlie Marsh
99657b7d92 Implement E401 ("multiple imports on one line") (#1326) 2022-12-21 21:15:57 -05:00
Charlie Marsh
40377aa1fc Move number of errors to the bottom of the output summary (#1325) 2022-12-21 21:04:26 -05:00
Charlie Marsh
2a37017e8c Add src to Settings hash 2022-12-21 21:01:20 -05:00
Charlie Marsh
ff66d08cef Run generate-options 2022-12-21 20:58:14 -05:00
Charlie Marsh
dad8035eef Support shell expansion in src field (#1324) 2022-12-21 20:57:20 -05:00
Charlie Marsh
bf5fec342c Support shell expansion in extend paths (#1323) 2022-12-21 20:46:38 -05:00
Charlie Marsh
66a6c81ebf Infer package roots when running via stdin (#1321) 2022-12-21 20:30:10 -05:00
Charlie Marsh
5c70f5044b Improve debug logging in flake8-to-ruff (#1320) 2022-12-21 20:05:48 -05:00
Charlie Marsh
953d141ab2 Support code redirects in flake8-to-ruff (#1318) 2022-12-21 19:31:20 -05:00
Charlie Marsh
07dba46039 Extract line length from pyproject.toml Black section (#1317) 2022-12-21 19:05:18 -05:00
Ran Benita
3b02da9d7b Fix false positive DTZ001 on datetime(2000, 1, 1, 0, 0, 0, 0, utc) (#1308) 2022-12-21 19:03:36 -05:00
107 changed files with 3872 additions and 954 deletions

View File

@@ -42,6 +42,9 @@ jobs:
- run: ./target/release/ruff_dev generate-check-code-prefix
- run: git diff --quiet src/checks_gen.rs || echo "::error file=src/checks_gen.rs::This file is outdated. You may have to rerun 'cargo dev generate-check-code-prefix'."
- run: git diff --exit-code -- README.md src/checks_gen.rs
- run: ./target/release/ruff_dev generate-json-schema
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. You may have to rerun 'cargo dev generate-json-schema'."
- run: git diff --exit-code -- ruff.schema.json
cargo_fmt:
name: "cargo fmt"
@@ -131,7 +134,7 @@ jobs:
override: true
- uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.11"
- run: pip install maturin
- uses: actions/cache@v3
env:

View File

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

View File

@@ -48,8 +48,8 @@ prior to merging.
There are four phases to adding a new lint rule:
1. Define the rule in `src/checks.rs`.
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks),
`src/check_tokens.rs` (for token-based checks), or `src/check_lines.rs` (for text-based checks).
2. Define the _logic_ for triggering the rule in `src/checkers/ast.rs` (for AST-based checks),
`src/checkers/tokens.rs` (for token-based checks), or `src/checkers/lines.rs` (for text-based checks).
3. Add a test fixture.
4. Update the generated files (documentation and generated code).
@@ -105,7 +105,8 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
lives in `flake8_to_ruff/src/converter.rs`.
To update the documentation for supported configuration options, run `cargo dev generate-options`.
Run `cargo dev generate-options` to update the documentation for supported configuration options,
and `cargo dev generate-json-schema` to update the JSON schema for `tool.ruff` in `pyproject.toml`.
## Release process

78
Cargo.lock generated
View File

@@ -644,6 +644,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "dyn-clone"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
[[package]]
name = "either"
version = "1.8.0"
@@ -724,7 +730,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.190-dev.0"
version = "0.0.193-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -735,6 +741,8 @@ dependencies = [
"rustc-hash",
"serde",
"serde_json",
"strum",
"strum_macros",
"toml",
]
@@ -1845,7 +1853,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.190"
version = "0.0.193"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1886,8 +1894,10 @@ dependencies = [
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"schemars",
"serde",
"serde_json",
"shellexpand",
"strum",
"strum_macros",
"test-case",
@@ -1901,7 +1911,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.190"
version = "0.0.193"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1913,13 +1923,15 @@ dependencies = [
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"schemars",
"serde_json",
"strum",
"strum_macros",
]
[[package]]
name = "ruff_macros"
version = "0.0.190"
version = "0.0.193"
dependencies = [
"proc-macro2",
"quote",
@@ -1962,7 +1974,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -1972,7 +1984,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -1995,7 +2007,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"bincode",
"bitflags",
@@ -2012,7 +2024,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"ahash",
"anyhow",
@@ -2055,6 +2067,30 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
dependencies = [
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@@ -2104,16 +2140,36 @@ dependencies = [
]
[[package]]
name = "serde_json"
version = "1.0.89"
name = "serde_derive_internals"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shellexpand"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd1c7ddea665294d484c39fd0c0d2b7e35bbfe10035c5fe1854741a57f6880e1"
dependencies = [
"dirs 4.0.0",
]
[[package]]
name = "similar"
version = "2.2.1"

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.190"
version = "0.0.193"
edition = "2021"
rust-version = "1.65.0"
@@ -43,13 +43,15 @@ 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.190", path = "ruff_macros" }
ruff_macros = { version = "0.0.193", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
schemars = { version = "0.8.11" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
shellexpand = { version = "3.0.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" }

351
README.md
View File

@@ -17,7 +17,7 @@ An extremely fast Python linter, written in Rust.
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🤝 Python 3.11 compatibility
- 🛠️ `pyproject.toml` support
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
@@ -39,9 +39,11 @@ Ruff is extremely actively developed and used in major open-source projects like
- [Bokeh](https://github.com/bokeh/bokeh)
- [Zulip](https://github.com/zulip/zulip)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Saleor](https://github.com/saleor/saleor)
- [Hatch](https://github.com/pypa/hatch)
- [Jupyter Server](https://github.com/jupyter-server/jupyter_server)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [Synapse](https://github.com/matrix-org/synapse)
- [Ibis](https://github.com/ibis-project/ibis)
- [Saleor](https://github.com/saleor/saleor)
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
@@ -158,11 +160,13 @@ ruff path/to/code/ --watch
Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.190
hooks:
- id: ruff
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.193'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
args: ["--force-exclude"]
```
## Configuration
@@ -314,6 +318,8 @@ Options:
Show violations with source code
--respect-gitignore
Respect file exclusions via `.gitignore` and other standard ignore files
--force-exclude
Enforce exclusions, even for paths passed to Ruff directly on the command-line
--show-files
See the files Ruff will be run against with the current settings
--show-settings
@@ -332,6 +338,8 @@ Options:
The name of the file when passing it through stdin
--explain <EXPLAIN>
Explain a rule
--cache-dir <CACHE_DIR>
Path to the cache directory
-h, --help
Print help information
-V, --version
@@ -517,6 +525,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| E401 | MultipleImportsOnOneLine | Multiple imports on one line | |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | 🛠 |
@@ -531,8 +540,8 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
| E902 | IOError | IOError: `...` | |
| E999 | SyntaxError | SyntaxError: `...` | |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | 🛠 |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | 🛠 |
### mccabe (C90)
@@ -623,6 +632,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
| UP016 | RemoveSixCompat | Unnecessary `six` compatibility usage | 🛠 |
| UP017 | DatetimeTimezoneUTC | Use `datetime.UTC` alias | 🛠 |
| UP018 | NativeLiterals | Unnecessary call to `str` and `bytes` | 🛠 |
### pep8-naming (N)
@@ -946,7 +957,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
| RUF100 | UnusedNOQA | Unused `noqa` directive | 🛠 |
| RUF100 | UnusedNOQA | Unused blanket `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->
@@ -1012,19 +1023,17 @@ local configs = require 'lspconfig.configs'
if not configs.ruff_lsp then
configs.ruff_lsp = {
default_config = {
cmd = { "ruff-lsp" },
filetypes = {'python'},
root_dir = require('lspconfig').util.find_git_ancestor,
settings = {
ruff_lsp = {
-- Any extra CLI arguments for `ruff` go here.
args = {}
cmd = { 'ruff-lsp' },
filetypes = { 'python' },
root_dir = require('lspconfig').util.find_git_ancestor,
init_options = {
settings = {
args = {}
}
}
}
}
}
end
require('lspconfig').ruff_lsp.setup {
on_attach = on_attach,
}
@@ -1034,6 +1043,8 @@ Upon successful installation, you should see Ruff's diagnostics surfaced directl
![Code Actions available in Neovim](https://user-images.githubusercontent.com/1309177/208278707-25fa37e4-079d-4597-ad35-b95dba066960.png)
To use `ruff-lsp` with other editors, including Sublime Text and Helix, see the [`ruff-lsp` documentation](https://github.com/charliermarsh/ruff-lsp#installation-and-usage).
### Language Server Protocol (Unofficial)
Ruff is also available as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff)
@@ -1072,24 +1083,15 @@ require'lspconfig'.pylsp.setup {
}
```
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
add a new tool with the following configuration:
![Install Ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### Vim & Neovim
Ruff can be integrated into any editor that supports the Language Server Protocol via [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp)
(see: [Language Server Protocol](#language-server-protocol-official)).
(see: [Language Server Protocol](#language-server-protocol-official)), including Vim and Neovim.
Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
It's recommended that you use [`ruff-lsp`](https://github.com/charliermarsh/ruff-lsp), the
officially supported LSP server for Ruff.
However, Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
extension for `coc.nvim`.
<details>
@@ -1106,7 +1108,6 @@ tools:
format-command: 'ruff --stdin-filename ${INPUT} --config ~/myconfigs/linters/ruff.toml --fix --exit-zero --quiet -'
format-stdin: true
```
</details>
<details>
@@ -1143,9 +1144,26 @@ null_ls.setup({
}
})
```
</details>
### PyCharm (External Tool)
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
in PyCharm. Open the Preferences pane, then navigate to "Tools", then "External Tools". From there,
add a new tool with the following configuration:
![Install Ruff as an External Tool](https://user-images.githubusercontent.com/1309177/193155720-336e43f0-1a8d-46b4-bc12-e60f9ae01f7e.png)
Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### PyCharm (Unofficial)
Ruff is also available as the [Ruff](https://plugins.jetbrains.com/plugin/20574-ruff) plugin on the
IntelliJ Marketplace (maintained by @koxudaxi).
### GitHub Actions
GitHub Actions has everything you need to run Ruff out-of-the-box:
@@ -1161,7 +1179,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
@@ -1219,7 +1237,7 @@ natively, including:
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (18/33)
- [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
@@ -1276,7 +1294,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),
[`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (3/10), and a subset of the rules
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33).
implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (17/33).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
@@ -1563,8 +1581,8 @@ Summary
#### [`allowed-confusables`](#allowed-confusables)
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`,
`RUF002`, and `RUF003`.
A list of allowed "confusable" Unicode characters to ignore when
enforcing `RUF001`, `RUF002`, and `RUF003`.
**Default value**: `[]`
@@ -1581,11 +1599,37 @@ allowed-confusables = ["", "ρ", ""]
---
#### [`cache-dir`](#cache-dir)
A path to the cache directory.
By default, Ruff stores cache results in a `.ruff_cache` directory in
the current project root.
However, Ruff will also respect the `RUFF_CACHE_DIR` environment
variable, which takes precedence over that default.
This setting will override even the `RUFF_CACHE_DIR` environment
variable, if set.
**Default value**: `.ruff_cache`
**Type**: `PathBuf`
**Example usage**:
```toml
[tool.ruff]
cache-dir = "~/.cache/ruff"
```
---
#### [`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_`.
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]+?))$"`
@@ -1607,15 +1651,16 @@ 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`).
- 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.
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"]`
@@ -1632,11 +1677,13 @@ exclude = [".venv"]
#### [`extend`](#extend)
A path to a local `pyproject.toml` file to merge into this configuration.
A path to a local `pyproject.toml` file to merge into this
configuration. User home directory and environment variables will be
expanded.
To resolve the current `pyproject.toml` file, Ruff will first resolve this base
configuration file, then merge in any properties defined in the current configuration
file.
To resolve the current `pyproject.toml` file, Ruff will first resolve
this base configuration file, then merge in any properties defined
in the current configuration file.
**Default value**: `None`
@@ -1656,7 +1703,8 @@ line-length = 100
#### [`extend-exclude`](#extend-exclude)
A list of file patterns to omit from linting, in addition to those specified by `exclude`.
A list of file patterns to omit from linting, in addition to those
specified by `exclude`.
**Default value**: `[]`
@@ -1674,7 +1722,8 @@ extend-exclude = ["tests", "src/bad.py"]
#### [`extend-ignore`](#extend-ignore)
A list of check code prefixes to ignore, in addition to those specified by `ignore`.
A list of check code prefixes to ignore, in addition to those specified
by `ignore`.
**Default value**: `[]`
@@ -1692,7 +1741,8 @@ extend-ignore = ["F841"]
#### [`extend-select`](#extend-select)
A list of check code prefixes to enable, in addition to those specified by `select`.
A list of check code prefixes to enable, in addition to those specified
by `select`.
**Default value**: `[]`
@@ -1710,9 +1760,10 @@ extend-select = ["B", "Q"]
#### [`external`](#external)
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.
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**: `[]`
@@ -1767,14 +1818,16 @@ fixable = ["E", "F"]
#### [`force-exclude`](#force-exclude)
Whether to enforce `exclude` and `extend-exclude` patterns, even for paths that are
passed to Ruff explicitly. Typically, Ruff will lint any paths passed in directly, even
if they would typically be excluded. Setting `force-exclude = true` will cause Ruff to
Whether to enforce `exclude` and `extend-exclude` patterns, even for
paths that are passed to Ruff explicitly. Typically, Ruff will lint
any paths passed in directly, even if they would typically be
excluded. Setting `force-exclude = true` will cause Ruff to
respect these exclusions unequivocally.
This is useful for [`pre-commit`](https://pre-commit.com/), which explicitly passes all
changed files to the [`ruff-pre-commit`](https://github.com/charliermarsh/ruff-pre-commit)
plugin, regardless of whether they're marked as excluded by Ruff's own settings.
plugin, regardless of whether they're marked as excluded by Ruff's own
settings.
**Default value**: `false`
@@ -1791,9 +1844,10 @@ force-exclude = 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).
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"`
@@ -1811,11 +1865,13 @@ 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.
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.
When breaking ties between enabled and disabled checks (via `select` and
`ignore`, respectively), more specific prefixes override less
specific prefixes.
**Default value**: `[]`
@@ -1833,10 +1889,11 @@ 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`).
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`
@@ -1853,7 +1910,8 @@ ignore-init-module-imports = true
#### [`line-length`](#line-length)
The line length to use when enforcing long-lines violations (like E501).
The line length to use when enforcing long-lines violations (like
`E501`).
**Default value**: `88`
@@ -1871,8 +1929,8 @@ line-length = 120
#### [`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**: `{}`
@@ -1892,8 +1950,9 @@ any matching files.
#### [`respect-gitignore`](#respect-gitignore)
Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`,
`.git/info/exclude`, and global `gitignore` files. Enabled by default.
Whether to automatically exclude files that are ignored by `.ignore`,
`.gitignore`, `.git/info/exclude`, and global `gitignore` files.
Enabled by default.
**Default value**: `true`
@@ -1910,11 +1969,13 @@ respect_gitignore = false
#### [`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.
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.
When breaking ties between enabled and disabled checks (via `select` and
`ignore`, respectively), more specific prefixes override less
specific prefixes.
**Default value**: `["E", "F"]`
@@ -1932,8 +1993,8 @@ 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`
@@ -1951,7 +2012,8 @@ show-source = true
#### [`src`](#src)
The source code paths to consider, e.g., when resolving first- vs. third-party imports.
The source code paths to consider, e.g., when resolving first- vs.
third-party imports.
As an example: given a Python package structure like:
@@ -1965,12 +2027,15 @@ my_package/
bar.py
```
The `src` directory should be included in `source` (e.g., `source = ["src"]`), such that
when resolving imports, `my_package.foo` is considered a first-party import.
The `src` directory should be included in `source` (e.g., `source =
["src"]`), such that when resolving imports, `my_package.foo` is
considered a first-party import.
This field supports globs. For example, if you have a series of Python packages in
a `python_modules` directory, `src = ["python_modules/*"]` would expand to incorporate
all of the packages in that directory.
This field supports globs. For example, if you have a series of Python
packages in a `python_modules` directory, `src =
["python_modules/*"]` would expand to incorporate all of the
packages in that directory. User home directory and environment
variables will also be expanded.
**Default value**: `["."]`
@@ -1988,9 +2053,10 @@ 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"`
@@ -2028,7 +2094,8 @@ unfixable = ["F401"]
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
Whether to suppress `ANN401` for dynamically typed `*args` and
`**kwargs` arguments.
**Default value**: `false`
@@ -2045,8 +2112,8 @@ 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`
@@ -2063,8 +2130,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`
@@ -2081,11 +2148,12 @@ 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).
- Explicit `return` statement(s) all return `None` (explicitly or
implicitly).
**Default value**: `false`
@@ -2104,8 +2172,8 @@ suppress-none-returning = true
#### [`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**: `[]`
@@ -2144,7 +2212,8 @@ max-string-length = 20
#### [`aliases`](#aliases)
The conventional aliases for imports. These aliases can be extended by the `extend_aliases` option.
The conventional aliases for imports. These aliases can be extended by
the `extend_aliases` option.
**Default value**: `{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}`
@@ -2166,7 +2235,8 @@ seaborn = "sns"
#### [`extend-aliases`](#extend-aliases)
A mapping of modules to their conventional import aliases. These aliases will be added to the `aliases` mapping.
A mapping of modules to their conventional import aliases. These aliases
will be added to the `aliases` mapping.
**Default value**: `{}`
@@ -2186,8 +2256,8 @@ A mapping of modules to their conventional import aliases. These aliases will be
#### [`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).
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`
@@ -2206,7 +2276,8 @@ avoid-escape = false
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
Quote style to prefer for docstrings (either "single" (`'`) or "double"
(`"`)).
**Default value**: `"double"`
@@ -2223,7 +2294,8 @@ docstring-quotes = "single"
#### [`inline-quotes`](#inline-quotes)
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
Quote style to prefer for inline strings (either "single" (`'`) or
"double" (`"`)).
**Default value**: `"double"`
@@ -2240,7 +2312,8 @@ inline-quotes = "single"
#### [`multiline-quotes`](#multiline-quotes)
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
Quote style to prefer for multiline strings (either "single" (`'`) or
"double" (`"`)).
**Default value**: `"double"`
@@ -2259,8 +2332,8 @@ multiline-quotes = "single"
#### [`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"`
@@ -2317,8 +2390,8 @@ 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.
A list of modules to consider standard-library, in addition to those
known to Ruff in advance.
**Default value**: `[]`
@@ -2335,9 +2408,10 @@ 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 (
@@ -2346,9 +2420,10 @@ from .utils import (
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
Note that this setting is only effective when combined with
`combine-as-imports = true`. When `combine-as-imports` isn't
enabled, every aliased `import from` will be given its own line, in
which case, wrapping is not necessary.
**Default value**: `false`
@@ -2366,8 +2441,8 @@ combine-as-imports = 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.
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**: `[]`
@@ -2384,8 +2459,8 @@ 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.
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**: `[]`
@@ -2424,9 +2499,10 @@ max-complexity = 5
#### [`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.
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"]`
@@ -2461,9 +2537,10 @@ ignore-names = ["callMethod"]
#### [`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"]`
@@ -2483,7 +2560,11 @@ staticmethod-decorators = ["staticmethod", "stcmthd"]
#### [`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`

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.190"
version = "0.0.193"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.190"
version = "0.0.193"
dependencies = [
"anyhow",
"bincode",
@@ -2028,7 +2028,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2038,7 +2038,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2061,7 +2061,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"bincode",
"bitflags",
@@ -2078,7 +2078,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=c01f014b1269eedcf4bdb45d5fbc62ae2beecf31#c01f014b1269eedcf4bdb45d5fbc62ae2beecf31"
source = "git+https://github.com/RustPython/RustPython.git?rev=1b6cb170e925a43d605b3fed9f6b878e63e47744#1b6cb170e925a43d605b3fed9f6b878e63e47744"
dependencies = [
"ahash",
"anyhow",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.190-dev.0"
version = "0.0.193-dev.0"
edition = "2021"
[lib]
@@ -16,6 +16,8 @@ ruff = { path = "..", default-features = false }
rustc-hash = { version = "1.1.0" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
toml = { version = "0.5.9" }
[dev-dependencies]

View File

@@ -0,0 +1,65 @@
[build-system]
requires = [
# The minimum setuptools version is specific to the PEP 517 backend,
# and may be stricter than the version required in `setup.cfg`
"setuptools>=40.6.0,!=60.9.0",
"wheel",
# Must be kept in sync with the `install_requirements` in `setup.cfg`
"cffi>=1.12; platform_python_implementation != 'PyPy'",
"setuptools-rust>=0.11.4",
]
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 79
target-version = ["py36"]
[tool.pytest.ini_options]
addopts = "-r s --capture=no --strict-markers --benchmark-disable"
markers = [
"skip_fips: this test is not executed in FIPS mode",
"supported: parametrized test requiring only_if and skip_message",
]
[tool.mypy]
show_error_codes = true
check_untyped_defs = true
no_implicit_reexport = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_unused_configs = true
strict_equality = true
[[tool.mypy.overrides]]
module = [
"pretend"
]
ignore_missing_imports = true
[tool.coverage.run]
branch = true
relative_files = true
source = [
"cryptography",
"tests/",
]
[tool.coverage.paths]
source = [
"src/cryptography",
"*.tox/*/lib*/python*/site-packages/cryptography",
"*.tox\\*\\Lib\\site-packages\\cryptography",
"*.tox/pypy/site-packages/cryptography",
]
tests =[
"tests/",
"*tests\\",
]
[tool.coverage.report]
exclude_lines = [
"@abc.abstractmethod",
"@abc.abstractproperty",
"@typing.overload",
"if typing.TYPE_CHECKING",
]

View File

@@ -0,0 +1,91 @@
[metadata]
name = cryptography
version = attr: cryptography.__version__
description = cryptography is a package which provides cryptographic recipes and primitives to Python developers.
long_description = file: README.rst
long_description_content_type = text/x-rst
license = BSD-3-Clause OR Apache-2.0
url = https://github.com/pyca/cryptography
author = The Python Cryptographic Authority and individual contributors
author_email = cryptography-dev@python.org
project_urls =
Documentation=https://cryptography.io/
Source=https://github.com/pyca/cryptography/
Issues=https://github.com/pyca/cryptography/issues
Changelog=https://cryptography.io/en/latest/changelog/
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
License :: OSI Approved :: BSD License
Natural Language :: English
Operating System :: MacOS :: MacOS X
Operating System :: POSIX
Operating System :: POSIX :: BSD
Operating System :: POSIX :: Linux
Operating System :: Microsoft :: Windows
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Security :: Cryptography
[options]
python_requires = >=3.6
include_package_data = True
zip_safe = False
package_dir =
=src
packages = find:
# `install_requires` must be kept in sync with `pyproject.toml`
install_requires =
cffi >=1.12
[options.packages.find]
where = src
exclude =
_cffi_src
_cffi_src.*
[options.extras_require]
test =
pytest>=6.2.0
pytest-benchmark
pytest-cov
pytest-subtests
pytest-xdist
pretend
iso8601
pytz
hypothesis>=1.11.4,!=3.79.2
docs =
sphinx >= 1.6.5,!=1.8.0,!=3.1.0,!=3.1.1,!=5.2.0,!=5.2.0.post0
sphinx_rtd_theme
docstest =
pyenchant >= 1.6.11
twine >= 1.12.0
sphinxcontrib-spelling >= 4.0.1
sdist =
setuptools_rust >= 0.11.4
pep8test =
black
flake8
flake8-import-order
pep8-naming
# This extra is for OpenSSH private keys that use bcrypt KDF
# Versions: v3.1.3 - ignore_few_rounds, v3.1.5 - abi3
ssh =
bcrypt >= 3.1.5
[flake8]
ignore = E203,E211,W503,W504,N818
exclude = .tox,*.egg,.git,_build,.hypothesis
select = E,W,F,N,I
application-import-names = cryptography,cryptography_vectors,tests

View File

@@ -12,6 +12,7 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",

View File

@@ -0,0 +1,32 @@
//! Extract Black configuration settings from a pyproject.toml.
use std::path::Path;
use anyhow::Result;
use ruff::settings::types::PythonVersion;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Black {
#[serde(alias = "line-length", alias = "line_length")]
pub line_length: Option<usize>,
#[serde(alias = "target-version", alias = "target_version")]
pub target_version: Option<Vec<PythonVersion>>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Tools {
black: Option<Black>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Pyproject {
tool: Option<Tools>,
}
pub fn parse_black_options<P: AsRef<Path>>(path: P) -> Result<Option<Black>> {
let contents = std::fs::read_to_string(path)?;
Ok(toml::from_str::<Pyproject>(&contents)?
.tool
.and_then(|tool| tool.black))
}

View File

@@ -11,13 +11,20 @@ use ruff::{
pep8_naming,
};
use crate::black::Black;
use crate::plugin::Plugin;
use crate::{parser, plugin};
pub fn convert(
flake8: &HashMap<String, Option<String>>,
config: &HashMap<String, HashMap<String, Option<String>>>,
black: Option<&Black>,
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract the Flake8 section.
let flake8 = config
.get("flake8")
.expect("Unable to find flake8 section in INI file");
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
for (key, value) in flake8 {
@@ -54,10 +61,15 @@ pub fn convert(
plugin::resolve_select(
flake8,
&plugins.unwrap_or_else(|| {
plugin::infer_plugins_from_options(flake8)
.into_iter()
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
.collect()
let from_options = plugin::infer_plugins_from_options(flake8);
if !from_options.is_empty() {
eprintln!("Inferred plugins from settings: {from_options:#?}");
}
let from_codes = plugin::infer_plugins_from_codes(&referenced_codes);
if !from_codes.is_empty() {
eprintln!("Inferred plugins from referenced check codes: {from_codes:#?}");
}
from_options.into_iter().chain(from_codes).collect()
}),
)
});
@@ -236,6 +248,19 @@ pub fn convert(
options.pep8_naming = Some(pep8_naming);
}
// Extract any settings from the existing `pyproject.toml`.
if let Some(black) = black {
if let Some(line_length) = &black.line_length {
options.line_length = Some(*line_length);
}
if let Some(target_version) = &black.target_version {
if let Some(target_version) = target_version.iter().min() {
options.target_version = Some(*target_version);
}
}
}
// Create the pyproject.toml.
Ok(Pyproject::new(options))
}
@@ -255,7 +280,11 @@ mod tests {
#[test]
fn it_converts_empty() -> Result<()> {
let actual = convert(&HashMap::from([]), None)?;
let actual = convert(
&HashMap::from([("flake8".to_string(), HashMap::default())]),
None,
None,
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
@@ -283,6 +312,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -303,7 +333,11 @@ mod tests {
#[test]
fn it_converts_dashes() -> Result<()> {
let actual = convert(
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
&HashMap::from([(
"flake8".to_string(),
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
)]),
None,
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
@@ -333,6 +367,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -353,7 +388,11 @@ mod tests {
#[test]
fn it_converts_underscores() -> Result<()> {
let actual = convert(
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
&HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
)]),
None,
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
@@ -383,6 +422,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -403,7 +443,11 @@ mod tests {
#[test]
fn it_ignores_parse_errors() -> Result<()> {
let actual = convert(
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
&HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
)]),
None,
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
@@ -433,6 +477,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -453,7 +498,11 @@ mod tests {
#[test]
fn it_converts_plugin_options() -> Result<()> {
let actual = convert(
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
&HashMap::from([(
"flake8".to_string(),
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
)]),
None,
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
@@ -483,6 +532,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -509,9 +559,13 @@ mod tests {
fn it_converts_docstring_conventions() -> Result<()> {
let actual = convert(
&HashMap::from([(
"docstring-convention".to_string(),
Some("numpy".to_string()),
"flake8".to_string(),
HashMap::from([(
"docstring-convention".to_string(),
Some("numpy".to_string()),
)]),
)]),
None,
Some(vec![Plugin::Flake8Docstrings]),
)?;
let expected = Pyproject::new(Options {
@@ -577,6 +631,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,
@@ -597,7 +652,11 @@ mod tests {
#[test]
fn it_infers_plugins_if_omitted() -> Result<()> {
let actual = convert(
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
&HashMap::from([(
"flake8".to_string(),
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
)]),
None,
None,
)?;
let expected = Pyproject::new(Options {
@@ -628,6 +687,7 @@ mod tests {
src: None,
target_version: None,
unfixable: None,
cache_dir: None,
flake8_annotations: None,
flake8_bugbear: None,
flake8_errmsg: None,

View File

@@ -11,6 +11,7 @@
clippy::too_many_lines
)]
pub mod black;
pub mod converter;
mod parser;
pub mod plugin;

View File

@@ -17,6 +17,7 @@ use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use configparser::ini::Ini;
use flake8_to_ruff::black::parse_black_options;
use flake8_to_ruff::converter;
use flake8_to_ruff::plugin::Plugin;
@@ -26,10 +27,14 @@ use flake8_to_ruff::plugin::Plugin;
long_about = None
)]
struct Cli {
/// Path to the Flake8 configuration file (e.g., 'setup.cfg', 'tox.ini', or
/// '.flake8').
/// Path to the Flake8 configuration file (e.g., `setup.cfg`, `tox.ini`, or
/// `.flake8`).
#[arg(required = true)]
file: PathBuf,
/// Optional path to a `pyproject.toml` file, used to ensure compatibility
/// with Black.
#[arg(long)]
pyproject: Option<PathBuf>,
/// List of plugins to enable.
#[arg(long, value_delimiter = ',')]
plugin: Option<Vec<Plugin>>,
@@ -43,13 +48,15 @@ fn main() -> Result<()> {
ini.set_multiline(true);
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
// Extract the Flake8 section.
let flake8 = config
.get("flake8")
.expect("Unable to find flake8 section in INI file");
// Read the pyproject.toml file.
let black = cli
.pyproject
.map(parse_black_options)
.transpose()?
.flatten();
// Create the pyproject.toml.
let pyproject = converter::convert(flake8, cli.plugin)?;
// Create Ruff's pyproject.toml section.
let pyproject = converter::convert(&config, black.as_ref(), cli.plugin)?;
println!("{}", toml::to_string_pretty(&pyproject)?);
Ok(())

View File

@@ -3,6 +3,7 @@ use std::str::FromStr;
use anyhow::{bail, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use ruff::checks::PREFIX_REDIRECTS;
use ruff::checks_gen::CheckCodePrefix;
use ruff::settings::types::PatternPrefixPair;
use rustc_hash::FxHashMap;
@@ -18,7 +19,9 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
if code.is_empty() {
continue;
}
if let Ok(code) = CheckCodePrefix::from_str(code) {
if let Some(code) = PREFIX_REDIRECTS.get(code) {
codes.push(code.clone());
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
codes.push(code);
} else {
eprintln!("Unsupported prefix code: {code}");
@@ -83,16 +86,22 @@ impl State {
fn parse(&self) -> Vec<PatternPrefixPair> {
let mut codes: Vec<PatternPrefixPair> = vec![];
for code in &self.codes {
match CheckCodePrefix::from_str(code) {
Ok(code) => {
for filename in &self.filenames {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
prefix: code.clone(),
});
}
if let Some(code) = PREFIX_REDIRECTS.get(code.as_str()) {
for filename in &self.filenames {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
prefix: code.clone(),
});
}
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
} else if let Ok(code) = CheckCodePrefix::from_str(code) {
for filename in &self.filenames {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
prefix: code.clone(),
});
}
} else {
eprintln!("Unsupported prefix code: {code}");
}
}
codes

View File

@@ -1,10 +1,11 @@
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::str::FromStr;
use anyhow::anyhow;
use ruff::checks_gen::CheckCodePrefix;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
Flake8Annotations,
Flake8Bandit,
@@ -42,7 +43,7 @@ impl FromStr for Plugin {
"flake8-datetimez" => Ok(Plugin::Flake8Datetimez),
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
@@ -58,6 +59,37 @@ impl FromStr for Plugin {
}
}
impl fmt::Debug for Plugin {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
Plugin::Flake8Annotations => "flake8-annotations",
Plugin::Flake8Bandit => "flake8-bandit",
Plugin::Flake8BlindExcept => "flake8-blind-except",
Plugin::Flake8Bugbear => "flake8-bugbear",
Plugin::Flake8Builtins => "flake8-builtins",
Plugin::Flake8Comprehensions => "flake8-comprehensions",
Plugin::Flake8Datetimez => "flake8-datetimez",
Plugin::Flake8Debugger => "flake8-debugger",
Plugin::Flake8Docstrings => "flake8-docstrings",
Plugin::Flake8Eradicate => "flake8-eradicate",
Plugin::Flake8ErrMsg => "flake8-errmsg",
Plugin::Flake8Print => "flake8-print",
Plugin::Flake8Quotes => "flake8-quotes",
Plugin::Flake8Return => "flake8-return",
Plugin::Flake8Simplify => "flake8-simplify",
Plugin::Flake8TidyImports => "flake8-tidy-imports",
Plugin::McCabe => "mccabe",
Plugin::PandasVet => "pandas-vet",
Plugin::PEP8Naming => "pep8-naming",
Plugin::Pyupgrade => "pyupgrade",
}
)
}
}
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
@@ -78,11 +110,11 @@ impl Plugin {
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Return => CheckCodePrefix::RET,
Plugin::Flake8Simplify => CheckCodePrefix::SIM,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::Flake8TidyImports => CheckCodePrefix::TID25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PandasVet => CheckCodePrefix::PD,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
Plugin::Pyupgrade => CheckCodePrefix::UP,
}
}
@@ -424,7 +456,6 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin
Plugin::Flake8TidyImports,
Plugin::PandasVet,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]
.into_iter()
.filter(|plugin| {

View File

@@ -12,6 +12,7 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",

View File

@@ -55,3 +55,5 @@ a.get("hello", False)
{}.pop(True, False)
dict.fromkeys(("world",), True)
{}.deploy(True, False)
getattr(someobj, attrname, False)
mylist.index(True)

View File

@@ -6,6 +6,9 @@ datetime.datetime(2000, 1, 1, 0, 0, 0)
# none args
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, None)
# not none arg
datetime.datetime(2000, 1, 1, 0, 0, 0, 0, datetime.timezone.utc)
# no kwargs
datetime.datetime(2000, 1, 1, fold=1)

View File

@@ -237,3 +237,15 @@ def close(self):
any_failed = True
report(traceback.format_exc())
return any_failed
def global_assignment():
global X
X = 1
return X
def nonlocal_assignment():
X = 1
def inner():
nonlocal X
X = 1
return X

View File

@@ -0,0 +1,61 @@
#: E401
import os, sys
#: Okay
import os
import sys
from subprocess import Popen, PIPE
from myclass import MyClass
from foo.bar.yourclass import YourClass
import myclass
import foo.bar.yourclass
#: Okay
__all__ = ['abc']
import foo
#: Okay
__version__ = "42"
import foo
#: Okay
__author__ = "Simon Gomizelj"
import foo
#: Okay
try:
import foo
except ImportError:
pass
else:
print('imported foo')
finally:
print('made attempt to import foo')
import bar
#: Okay
with warnings.catch_warnings():
warnings.filterwarnings("ignore", DeprecationWarning)
import foo
import bar
#: Okay
if False:
import foo
elif not True:
import bar
else:
import mwahaha
import bar
#: E402
VERSION = '1.2.3'
import foo
#: E402
import foo
a = 1
import bar

View File

@@ -15,7 +15,7 @@ import types
if type(res) is not types.ListType:
pass
#: E721
assert type(res) == type(False) or type(res) == type(None)
assert type(res) == type(False)
#: E721
assert type(res) == type([])
#: E721
@@ -52,3 +52,5 @@ if isinstance(res, types.MethodType):
pass
if type(a) != type(b) or type(a) == type(ccc):
pass
assert type(res) == type(None)

View File

@@ -1,2 +1,2 @@
def fn() -> None:
pass
print("Newline present (no W292)")

View File

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,11 @@
import datetime
import datetime as dt
from datetime import timezone
from datetime import timezone as tz
print(datetime.timezone(-1))
print(timezone.utc)
print(tz.utc)
print(datetime.timezone.utc)
print(dt.timezone.utc)

View File

@@ -0,0 +1,25 @@
# These remain unchanged
str(1)
str(*a)
str("foo", *a)
str(**k)
str("foo", **k)
str("foo", encoding="UTF-8")
str("foo"
"bar")
bytes("foo", encoding="UTF-8")
bytes(*a)
bytes("foo", *a)
bytes("foo", **a)
bytes(b"foo"
b"bar")
# These become string or byte literals
str()
str("foo")
str("""
foo""")
bytes()
bytes(b"foo")
bytes(b"""
foo""")

View File

@@ -15,8 +15,8 @@ def f() -> None:
# Invalid
d = 1 # noqa: F841, E501
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Invalid (and unimplemented or not enabled)
d = 1 # noqa: F841, W191, F821
# Invalid (but external)
d = 1 # noqa: F841, V101

View File

@@ -9,14 +9,14 @@ Running from the repo root should pick up and enforce the appropriate settings f
```
∴ cargo run resources/test/project/
Found 7 error(s).
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
Found 7 error(s).
6 potentially fixable with the --fix option.
```
@@ -24,14 +24,14 @@ Running from the project directory itself should exhibit the same behavior:
```
∴ (cd resources/test/project/ && cargo run .)
Found 7 error(s).
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
src/file.py:1:8: F401 `os` imported but unused
src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
project/file.py:1:8: F401 `os` imported but unused
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
Found 7 error(s).
6 potentially fixable with the --fix option.
```
@@ -40,9 +40,9 @@ files:
```
∴ (cd resources/test/project/examples/docs && cargo run .)
Found 2 error(s).
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
Found 2 error(s).
1 potentially fixable with the --fix option.
```
@@ -51,8 +51,6 @@ file paths from the current working directory:
```
∴ (cargo run -- --config=resources/test/project/pyproject.toml resources/test/project/)
Found 11 error(s).
resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
@@ -61,9 +59,9 @@ resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but un
resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
resources/test/project/src/file.py:1:8: F401 `os` imported but unused
resources/test/project/src/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
11 potentially fixable with the --fix option.
resources/test/project/project/file.py:1:8: F401 `os` imported but unused
Found 9 error(s).
9 potentially fixable with the --fix option.
```
Running from a parent directory should this "ignore" the `exclude` (hence, `concepts/file.py` gets
@@ -71,11 +69,11 @@ included in the output):
```
∴ (cd resources/test/project/examples && cargo run -- --config=docs/pyproject.toml .)
Found 4 error(s).
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
Found 4 error(s).
1 potentially fixable with the --fix option.
```
@@ -83,7 +81,14 @@ Passing an excluded directory directly should report errors in the contained fil
```
∴ cargo run resources/test/project/examples/excluded/
Found 1 error(s).
resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
Found 1 error(s).
1 potentially fixable with the --fix option.
```
Unless we `--force-exclude`:
```
∴ cargo run resources/test/project/examples/excluded/ --force-exclude
∴ cargo run resources/test/project/examples/excluded/script.py --force-exclude
```

1194
ruff.schema.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.190"
version = "0.0.193"
edition = "2021"
[dependencies]
@@ -11,8 +11,10 @@ itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "c01f014b1269eedcf4bdb45d5fbc62ae2beecf31" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }

View File

@@ -10,7 +10,7 @@ use anyhow::{ensure, Result};
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
use ruff::checks::{CheckCode, PREFIX_REDIRECTS};
use strum::IntoEnumIterator;
#[derive(Parser)]
@@ -40,18 +40,7 @@ pub fn main(cli: &Cli) -> Result<()> {
}
// Add any prefix aliases (e.g., "U" to "UP").
for (alias, source) in PREFIX_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
.get(&(*source).to_string())
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
// Add any check code aliases (e.g., "U001" to "UP001").
for (alias, check_code) in CODE_REDIRECTS.iter() {
for (alias, check_code) in PREFIX_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
@@ -76,7 +65,8 @@ pub fn main(cli: &Cli) -> Result<()> {
.derive("Ord")
.derive("Clone")
.derive("Serialize")
.derive("Deserialize");
.derive("Deserialize")
.derive("JsonSchema");
for prefix in prefix_to_codes.keys() {
gen = gen.push_variant(Variant::new(prefix.to_string()));
}
@@ -105,7 +95,7 @@ pub fn main(cli: &Cli) -> Result<()> {
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
@@ -117,18 +107,6 @@ pub fn main(cli: &Cli) -> Result<()> {
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ one_time_warning!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target,
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
@@ -161,8 +139,7 @@ pub fn main(cli: &Cli) -> Result<()> {
_ => panic!("Invalid prefix: {prefix}"),
};
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => SuffixLength::{},",
specificity
"CheckCodePrefix::{prefix} => SuffixLength::{specificity},"
));
}
gen.line("}");
@@ -175,6 +152,8 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push_str("use colored::Colorize;");
output.push('\n');
output.push_str("use schemars::JsonSchema;");
output.push('\n');
output.push_str("use serde::{Deserialize, Serialize};");
output.push('\n');
output.push_str("use strum_macros::{AsRefStr, EnumString};");

View File

@@ -0,0 +1,30 @@
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use ruff::settings::options::Options;
use schemars::schema_for;
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `ruff.schema.json`).
#[arg(long)]
dry_run: bool,
}
pub fn main(cli: &Cli) -> Result<()> {
let schema = schema_for!(Options);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
if cli.dry_run {
println!("{schema_string}");
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("ruff.schema.json");
fs::write(file, schema_string.as_bytes())?;
}
Ok(())
}

View File

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

View File

@@ -14,8 +14,8 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_check_code_prefix, generate_options, generate_rules_table, generate_source_code,
print_ast, print_cst, print_tokens,
generate_check_code_prefix, generate_json_schema, generate_options, generate_rules_table,
generate_source_code, print_ast, print_cst, print_tokens,
};
#[derive(Parser)]
@@ -30,6 +30,8 @@ struct Cli {
enum Commands {
/// Generate the `CheckCodePrefix` enum.
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
/// Generate JSON schema for the TOML configuration file.
GenerateJSONSchema(generate_json_schema::Cli),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options.
@@ -48,6 +50,7 @@ fn main() -> Result<()> {
let cli = Cli::parse();
match &cli.command {
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
Commands::GenerateJSONSchema(args) => generate_json_schema::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)?,

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.190"
version = "0.0.193"
edition = "2021"
[lib]

View File

@@ -13,13 +13,14 @@
use quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
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))]
#[proc_macro_derive(ConfigurationOptions, attributes(option, doc, option_group))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
@@ -39,11 +40,28 @@ fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
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)?);
let docs: Vec<&Attribute> = field
.attrs
.iter()
.filter(|attr| attr.path.is_ident("doc"))
.collect();
if docs.is_empty() {
return Err(syn::Error::new(
field.span(),
"Missing documentation for field",
));
}
if let Some(attr) = field.attrs.iter().find(|attr| attr.path.is_ident("option")) {
output.push(handle_option(field, attr, docs)?);
};
if field.attrs.iter().any(|a| a.path.is_ident("option_group")) {
if field
.attrs
.iter()
.any(|attr| attr.path.is_ident("option_group"))
{
output.push(handle_option_group(field)?);
};
}
@@ -70,8 +88,10 @@ fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
/// 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();
let ident = field
.ident
.as_ref()
.expect("Expected to handle named fields");
match &field.ty {
Type::Path(TypePath {
@@ -103,17 +123,49 @@ fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
}
}
/// Parse a `doc` attribute into it a string literal.
fn parse_doc(doc: &Attribute) -> syn::Result<String> {
let doc = doc
.parse_meta()
.map_err(|e| syn::Error::new(doc.span(), e))?;
match doc {
syn::Meta::NameValue(syn::MetaNameValue {
lit: Lit::Str(lit_str),
..
}) => Ok(lit_str.value()),
_ => Err(syn::Error::new(doc.span(), "Expected doc attribute.")),
}
}
/// 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();
fn handle_option(
field: &Field,
attr: &Attribute,
docs: Vec<&Attribute>,
) -> syn::Result<proc_macro2::TokenStream> {
// Convert the list of `doc` attributes into a single string.
let doc = textwrap::dedent(
&docs
.into_iter()
.map(parse_doc)
.collect::<syn::Result<Vec<_>>>()?
.join("\n"),
)
.trim_matches('\n')
.to_string();
let ident = field
.ident
.as_ref()
.expect("Expected to handle named fields");
let FieldAttributes {
doc,
default,
value_type,
example,
..
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
@@ -130,7 +182,6 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
#[derive(Debug)]
struct FieldAttributes {
doc: String,
default: String,
value_type: String,
example: String,
@@ -138,8 +189,6 @@ struct FieldAttributes {
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")?;
@@ -150,7 +199,6 @@ impl Parse for FieldAttributes {
}
Ok(FieldAttributes {
doc: textwrap::dedent(&doc).trim_matches('\n').to_string(),
default,
value_type,
example: textwrap::dedent(&example).trim_matches('\n').to_string(),

View File

@@ -3,7 +3,7 @@ use std::fs;
use std::fs::{create_dir_all, File, Metadata};
use std::hash::{Hash, Hasher};
use std::io::Write;
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::Result;
use filetime::FileTime;
@@ -36,8 +36,12 @@ struct CheckResult {
messages: Vec<Message>,
}
fn cache_dir() -> &'static Path {
Path::new(CACHE_DIR.as_ref().map_or(".ruff_cache", String::as_str))
/// Return the cache directory for a given project root. Defers to the
/// `RUFF_CACHE_DIR` environment variable, if set.
pub fn cache_dir(project_root: &Path) -> PathBuf {
CACHE_DIR
.as_ref()
.map_or_else(|| project_root.join(".ruff_cache"), PathBuf::from)
}
fn content_dir() -> &'static Path {
@@ -53,10 +57,8 @@ fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode)
hasher.finish()
}
/// Initialize the cache directory.
pub fn init() -> Result<()> {
let path = cache_dir();
/// Initialize the cache at the specified `Path`.
pub fn init(path: &Path) -> Result<()> {
// Create the cache directories.
create_dir_all(path.join(content_dir()))?;
@@ -75,15 +77,15 @@ pub fn init() -> Result<()> {
Ok(())
}
fn write_sync(key: u64, value: &[u8]) -> Result<(), std::io::Error> {
fn write_sync(cache_dir: &Path, key: u64, value: &[u8]) -> Result<(), std::io::Error> {
fs::write(
cache_dir().join(content_dir()).join(format!("{key:x}")),
cache_dir.join(content_dir()).join(format!("{key:x}")),
value,
)
}
fn read_sync(key: u64) -> Result<Vec<u8>, std::io::Error> {
fs::read(cache_dir().join(content_dir()).join(format!("{key:x}")))
fn read_sync(cache_dir: &Path, key: u64) -> Result<Vec<u8>, std::io::Error> {
fs::read(cache_dir.join(content_dir()).join(format!("{key:x}")))
}
/// Get a value from the cache.
@@ -98,7 +100,7 @@ pub fn get<P: AsRef<Path>>(
return None;
};
let encoded = read_sync(cache_key(path, settings, autofix)).ok()?;
let encoded = read_sync(&settings.cache_dir, cache_key(path, settings, autofix)).ok()?;
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
Ok(CheckResult {
metadata: CacheMetadata { mtime },
@@ -135,6 +137,7 @@ pub fn set<P: AsRef<Path>>(
messages,
};
if let Err(e) = write_sync(
&settings.cache_dir,
cache_key(path, settings, autofix),
&bincode::serialize(&check_result).unwrap(),
) {

View File

@@ -628,6 +628,15 @@ where
}
}
StmtKind::Import { names } => {
if self.settings.enabled.contains(&CheckCode::E401) {
if names.len() > 1 {
self.add_check(Check::new(
CheckKind::MultipleImportsOnOneLine,
Range::from_located(stmt),
));
}
}
if self.settings.enabled.contains(&CheckCode::E402) {
if self.seen_import_boundary && stmt.location.column() == 0 {
self.add_check(Check::new(
@@ -1058,17 +1067,11 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::EM101)
| self.settings.enabled.contains(&CheckCode::EM102)
| self.settings.enabled.contains(&CheckCode::EM103)
|| self.settings.enabled.contains(&CheckCode::EM102)
|| self.settings.enabled.contains(&CheckCode::EM103)
{
if let Some(exc) = exc {
self.add_checks(
flake8_errmsg::checks::check_string_in_exception(
exc,
self.settings.flake8_errmsg.max_string_length,
)
.into_iter(),
);
flake8_errmsg::plugins::string_in_exception(self, exc);
}
}
}
@@ -1148,7 +1151,7 @@ where
if self.settings.enabled.contains(&CheckCode::B014)
|| self.settings.enabled.contains(&CheckCode::B025)
{
flake8_bugbear::plugins::duplicate_exceptions(self, stmt, handlers);
flake8_bugbear::plugins::duplicate_exceptions(self, handlers);
}
if self.settings.enabled.contains(&CheckCode::B013) {
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
@@ -1544,6 +1547,12 @@ where
pyupgrade::plugins::remove_six_compat(self, expr);
}
if self.settings.enabled.contains(&CheckCode::UP017)
&& self.settings.target_version >= PythonVersion::Py311
{
pyupgrade::plugins::datetime_utc_alias(self, expr);
}
if self.settings.enabled.contains(&CheckCode::YTT202) {
flake8_2020::plugins::name_or_attribute(self, expr);
}
@@ -1635,6 +1644,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP016) {
pyupgrade::plugins::remove_six_compat(self, expr);
}
if self.settings.enabled.contains(&CheckCode::UP018) {
pyupgrade::plugins::native_literals(self, expr, func, args, keywords);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::UP008) {

View File

@@ -55,7 +55,11 @@ pub fn check_lines(
}
if enforce_no_newline_at_end_of_file {
if let Some(check) = no_newline_at_end_of_file(contents) {
if let Some(check) = no_newline_at_end_of_file(
contents,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::W292),
) {
checks.push(check);
}
}

View File

@@ -1,11 +1,13 @@
//! `NoQA` enforcement and validation.
use std::str::FromStr;
use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
use crate::checks::{Check, CheckCode, CheckKind, UnusedCodes, CODE_REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::{flags, Settings};
@@ -98,18 +100,29 @@ pub fn check_noqa(
}
}
Directive::Codes(spaces, start, end, codes) => {
let mut invalid_codes = vec![];
let mut disabled_codes = vec![];
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
let mut valid_codes = vec![];
let mut self_ignore = false;
for code in codes {
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if code == CheckCode::RUF100.as_ref() {
self_ignore = true;
break;
}
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code);
} else {
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code);
if let Ok(check_code) = CheckCode::from_str(code) {
if settings.enabled.contains(&check_code) {
unmatched_codes.push(code);
} else {
disabled_codes.push(code);
}
} else {
invalid_codes.push(code);
unknown_codes.push(code);
}
}
}
@@ -118,14 +131,25 @@ pub fn check_noqa(
continue;
}
if !invalid_codes.is_empty() {
if !(disabled_codes.is_empty()
&& unknown_codes.is_empty()
&& unmatched_codes.is_empty())
{
let mut check = Check::new(
CheckKind::UnusedNOQA(Some(
invalid_codes
CheckKind::UnusedNOQA(Some(UnusedCodes {
disabled: disabled_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
)),
unknown: unknown_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
unmatched: unmatched_codes
.iter()
.map(|code| (*code).to_string())
.collect(),
})),
Range {
location: Location::new(row + 1, start),
end_location: Location::new(row + 1, end),

View File

@@ -89,7 +89,11 @@ pub fn check_tokens(
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {
checks.extend(pycodestyle::checks::invalid_escape_sequence(
locator, start, end,
locator,
start,
end,
matches!(autofix, flags::Autofix::Enabled)
&& settings.fixable.contains(&CheckCode::W605),
));
}
}

View File

@@ -1,5 +1,4 @@
use std::fmt;
use std::str::FromStr;
use itertools::Itertools;
use once_cell::sync::Lazy;
@@ -33,6 +32,7 @@ use crate::pyupgrade::types::Primitive;
)]
pub enum CheckCode {
// pycodestyle errors
E401,
E402,
E501,
E711,
@@ -224,6 +224,8 @@ pub enum CheckCode {
UP014,
UP015,
UP016,
UP017,
UP018,
// pydocstyle
D100,
D101,
@@ -633,6 +635,13 @@ impl fmt::Display for Branch {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnusedCodes {
pub unknown: Vec<String>,
pub disabled: Vec<String>,
pub unmatched: Vec<String>,
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
@@ -644,6 +653,7 @@ pub enum CheckKind {
IOError(String),
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultipleImportsOnOneLine,
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
@@ -825,6 +835,8 @@ pub enum CheckKind {
ConvertNamedTupleFunctionalToClass(String),
RedundantOpenModes,
RemoveSixCompat,
DatetimeTimezoneUTC,
NativeLiterals,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -937,7 +949,7 @@ pub enum CheckKind {
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
UnusedNOQA(Option<Vec<String>>),
UnusedNOQA(Option<UnusedCodes>),
// flake8-datetimez
CallDatetimeWithoutTzinfo,
CallDatetimeToday,
@@ -978,6 +990,7 @@ impl CheckCode {
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle errors
CheckCode::E401 => CheckKind::MultipleImportsOnOneLine,
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
@@ -1197,6 +1210,8 @@ impl CheckCode {
CheckCode::UP014 => CheckKind::ConvertNamedTupleFunctionalToClass("...".to_string()),
CheckCode::UP015 => CheckKind::RedundantOpenModes,
CheckCode::UP016 => CheckKind::RemoveSixCompat,
CheckCode::UP017 => CheckKind::DatetimeTimezoneUTC,
CheckCode::UP018 => CheckKind::NativeLiterals,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -1462,6 +1477,7 @@ impl CheckCode {
CheckCode::DTZ007 => CheckCategory::Flake8Datetimez,
CheckCode::DTZ011 => CheckCategory::Flake8Datetimez,
CheckCode::DTZ012 => CheckCategory::Flake8Datetimez,
CheckCode::E401 => CheckCategory::Pycodestyle,
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
@@ -1613,6 +1629,8 @@ impl CheckCode {
CheckCode::UP014 => CheckCategory::Pyupgrade,
CheckCode::UP015 => CheckCategory::Pyupgrade,
CheckCode::UP016 => CheckCategory::Pyupgrade,
CheckCode::UP017 => CheckCategory::Pyupgrade,
CheckCode::UP018 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1634,9 +1652,9 @@ impl CheckKind {
pub fn code(&self) -> &'static CheckCode {
match self {
// pycodestyle errors
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
CheckKind::AmbiguousClassName(..) => &CheckCode::E742,
CheckKind::AmbiguousFunctionName(..) => &CheckCode::E743,
CheckKind::AmbiguousVariableName(..) => &CheckCode::E741,
CheckKind::AssertTuple => &CheckCode::F631,
CheckKind::BreakOutsideLoop => &CheckCode::F701,
CheckKind::ContinueOutsideLoop => &CheckCode::F702,
@@ -1645,56 +1663,57 @@ impl CheckKind {
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
CheckKind::DuplicateArgumentName => &CheckCode::F831,
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722,
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
CheckKind::IOError(_) => &CheckCode::E902,
CheckKind::ForwardAnnotationSyntaxError(..) => &CheckCode::F722,
CheckKind::FutureFeatureNotDefined(..) => &CheckCode::F407,
CheckKind::IOError(..) => &CheckCode::E902,
CheckKind::IfTuple => &CheckCode::F634,
CheckKind::ImportShadowedByLoopVar(..) => &CheckCode::F402,
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
CheckKind::ImportStarNotPermitted(..) => &CheckCode::F406,
CheckKind::ImportStarUsage(..) => &CheckCode::F405,
CheckKind::ImportStarUsed(_) => &CheckCode::F403,
CheckKind::ImportStarUsed(..) => &CheckCode::F403,
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
CheckKind::IsLiteral => &CheckCode::F632,
CheckKind::LateFutureImport => &CheckCode::F404,
CheckKind::LineTooLong(..) => &CheckCode::E501,
CheckKind::MultipleImportsOnOneLine => &CheckCode::E401,
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::MultiValueRepeatedKeyVariable(..) => &CheckCode::F602,
CheckKind::NoneComparison(..) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::PercentFormatExpectedMapping => &CheckCode::F502,
CheckKind::PercentFormatExpectedSequence => &CheckCode::F503,
CheckKind::PercentFormatExtraNamedArguments(_) => &CheckCode::F504,
CheckKind::PercentFormatInvalidFormat(_) => &CheckCode::F501,
CheckKind::PercentFormatMissingArgument(_) => &CheckCode::F505,
CheckKind::PercentFormatExtraNamedArguments(..) => &CheckCode::F504,
CheckKind::PercentFormatInvalidFormat(..) => &CheckCode::F501,
CheckKind::PercentFormatMissingArgument(..) => &CheckCode::F505,
CheckKind::PercentFormatMixedPositionalAndNamed => &CheckCode::F506,
CheckKind::PercentFormatPositionalCountMismatch(..) => &CheckCode::F507,
CheckKind::PercentFormatStarRequiresSequence => &CheckCode::F508,
CheckKind::PercentFormatUnsupportedFormatCharacter(_) => &CheckCode::F509,
CheckKind::PercentFormatUnsupportedFormatCharacter(..) => &CheckCode::F509,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::StringDotFormatExtraNamedArguments(_) => &CheckCode::F522,
CheckKind::StringDotFormatExtraPositionalArguments(_) => &CheckCode::F523,
CheckKind::StringDotFormatInvalidFormat(_) => &CheckCode::F521,
CheckKind::StringDotFormatMissingArguments(_) => &CheckCode::F524,
CheckKind::StringDotFormatExtraNamedArguments(..) => &CheckCode::F522,
CheckKind::StringDotFormatExtraPositionalArguments(..) => &CheckCode::F523,
CheckKind::StringDotFormatInvalidFormat(..) => &CheckCode::F521,
CheckKind::StringDotFormatMissingArguments(..) => &CheckCode::F524,
CheckKind::StringDotFormatMixingAutomatic => &CheckCode::F525,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::SyntaxError(..) => &CheckCode::E999,
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
CheckKind::TwoStarredExpressions => &CheckCode::F622,
CheckKind::TypeComparison => &CheckCode::E721,
CheckKind::UndefinedExport(_) => &CheckCode::F822,
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::UndefinedExport(..) => &CheckCode::F822,
CheckKind::UndefinedLocal(..) => &CheckCode::F823,
CheckKind::RedefinedWhileUnused(..) => &CheckCode::F811,
CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UndefinedName(..) => &CheckCode::F821,
CheckKind::UnusedImport(..) => &CheckCode::F401,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UnusedAnnotation(_) => &CheckCode::F842,
CheckKind::YieldOutsideFunction(_) => &CheckCode::F704,
CheckKind::UnusedVariable(..) => &CheckCode::F841,
CheckKind::UnusedAnnotation(..) => &CheckCode::F842,
CheckKind::YieldOutsideFunction(..) => &CheckCode::F704,
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
CheckKind::InvalidEscapeSequence(..) => &CheckCode::W605,
// pylint
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
@@ -1703,96 +1722,96 @@ impl CheckKind {
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
CheckKind::UseSysExit(..) => &CheckCode::PLR1722,
CheckKind::NonlocalWithoutBinding(..) => &CheckCode::PLE0117,
CheckKind::UsedPriorGlobalDeclaration(..) => &CheckCode::PLE0118,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
CheckKind::BuiltinVariableShadowing(..) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(..) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(..) => &CheckCode::A003,
// flake8-bugbear
CheckKind::AbstractBaseClassWithoutAbstractMethod(_) => &CheckCode::B024,
CheckKind::AbstractBaseClassWithoutAbstractMethod(..) => &CheckCode::B024,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::CachedInstanceMethod => &CheckCode::B019,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
CheckKind::DuplicateHandlerException(..) => &CheckCode::B014,
CheckKind::DuplicateTryBlockException(..) => &CheckCode::B025,
CheckKind::EmptyMethodWithoutAbstractDecorator(..) => &CheckCode::B027,
CheckKind::FStringDocstring => &CheckCode::B021,
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
CheckKind::FunctionUsesLoopVariable(_) => &CheckCode::B023,
CheckKind::FunctionCallArgumentDefault(..) => &CheckCode::B008,
CheckKind::FunctionUsesLoopVariable(..) => &CheckCode::B023,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
CheckKind::JumpStatementInFinally(..) => &CheckCode::B012,
CheckKind::LoopVariableOverridesIterator(..) => &CheckCode::B020,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
CheckKind::ZipWithoutExplicitStrict => &CheckCode::B905,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::RedundantTupleInExceptionHandler(..) => &CheckCode::B013,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::UnusedLoopControlVariable(..) => &CheckCode::B007,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::UselessExpression => &CheckCode::B018,
// flake8-blind-except
CheckKind::BlindExcept(_) => &CheckCode::BLE001,
CheckKind::BlindExcept(..) => &CheckCode::BLE001,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
CheckKind::UnnecessaryGeneratorDict => &CheckCode::C402,
CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
CheckKind::UnnecessaryLiteralSet(..) => &CheckCode::C405,
CheckKind::UnnecessaryLiteralDict(..) => &CheckCode::C406,
CheckKind::UnnecessaryCollectionCall(..) => &CheckCode::C408,
CheckKind::UnnecessaryLiteralWithinTupleCall(..) => &CheckCode::C409,
CheckKind::UnnecessaryLiteralWithinListCall(..) => &CheckCode::C410,
CheckKind::UnnecessaryListCall => &CheckCode::C411,
CheckKind::UnnecessaryCallAroundSorted(_) => &CheckCode::C413,
CheckKind::UnnecessaryCallAroundSorted(..) => &CheckCode::C413,
CheckKind::UnnecessaryDoubleCastOrProcess(..) => &CheckCode::C414,
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
CheckKind::UnnecessarySubscriptReversal(..) => &CheckCode::C415,
CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
CheckKind::UnnecessaryMap(..) => &CheckCode::C417,
// flake8-debugger
CheckKind::Debugger(_) => &CheckCode::T100,
CheckKind::Debugger(..) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::TID252,
CheckKind::BannedRelativeImport(..) => &CheckCode::TID252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
CheckKind::ImplicitReturn => &CheckCode::RET503,
CheckKind::UnnecessaryAssign => &CheckCode::RET504,
CheckKind::SuperfluousElseReturn(_) => &CheckCode::RET505,
CheckKind::SuperfluousElseRaise(_) => &CheckCode::RET506,
CheckKind::SuperfluousElseContinue(_) => &CheckCode::RET507,
CheckKind::SuperfluousElseBreak(_) => &CheckCode::RET508,
CheckKind::SuperfluousElseReturn(..) => &CheckCode::RET505,
CheckKind::SuperfluousElseRaise(..) => &CheckCode::RET506,
CheckKind::SuperfluousElseContinue(..) => &CheckCode::RET507,
CheckKind::SuperfluousElseBreak(..) => &CheckCode::RET508,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// flake8-quotes
CheckKind::BadQuotesInlineString(_) => &CheckCode::Q000,
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
CheckKind::BadQuotesInlineString(..) => &CheckCode::Q000,
CheckKind::BadQuotesMultilineString(..) => &CheckCode::Q001,
CheckKind::BadQuotesDocstring(..) => &CheckCode::Q002,
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
// flake8-annotations
CheckKind::MissingTypeFunctionArgument(_) => &CheckCode::ANN001,
CheckKind::MissingTypeArgs(_) => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs(_) => &CheckCode::ANN003,
CheckKind::MissingTypeSelf(_) => &CheckCode::ANN101,
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeSpecialMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
CheckKind::MissingTypeFunctionArgument(..) => &CheckCode::ANN001,
CheckKind::MissingTypeArgs(..) => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs(..) => &CheckCode::ANN003,
CheckKind::MissingTypeSelf(..) => &CheckCode::ANN101,
CheckKind::MissingTypeCls(..) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(..) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(..) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeSpecialMethod(..) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(..) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(..) => &CheckCode::ANN206,
CheckKind::DynamicallyTypedExpression(..) => &CheckCode::ANN401,
// flake8-2020
CheckKind::SysVersionSlice3Referenced => &CheckCode::YTT101,
CheckKind::SysVersion2Referenced => &CheckCode::YTT102,
@@ -1807,28 +1826,30 @@ impl CheckKind {
// flake8-simplify
CheckKind::KeyInDict(..) => &CheckCode::SIM118,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::UP003,
CheckKind::TypeOfPrimitive(..) => &CheckCode::UP003,
CheckKind::UselessMetaclassType => &CheckCode::UP001,
CheckKind::DeprecatedUnittestAlias(..) => &CheckCode::UP005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::UP006,
CheckKind::UsePEP585Annotation(..) => &CheckCode::UP006,
CheckKind::UsePEP604Annotation => &CheckCode::UP007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::UP004,
CheckKind::UselessObjectInheritance(..) => &CheckCode::UP004,
CheckKind::SuperCallWithParameters => &CheckCode::UP008,
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::UP009,
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::UP010,
CheckKind::UnnecessaryFutureImport(..) => &CheckCode::UP010,
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::UP011,
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::UP012,
CheckKind::ConvertTypedDictFunctionalToClass(_) => &CheckCode::UP013,
CheckKind::ConvertNamedTupleFunctionalToClass(_) => &CheckCode::UP014,
CheckKind::ConvertTypedDictFunctionalToClass(..) => &CheckCode::UP013,
CheckKind::ConvertNamedTupleFunctionalToClass(..) => &CheckCode::UP014,
CheckKind::RedundantOpenModes => &CheckCode::UP015,
CheckKind::RemoveSixCompat => &CheckCode::UP016,
CheckKind::DatetimeTimezoneUTC => &CheckCode::UP017,
CheckKind::NativeLiterals => &CheckCode::UP018,
// pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
CheckKind::BlankLineBeforeSection(_) => &CheckCode::D411,
CheckKind::CapitalizeSectionName(_) => &CheckCode::D405,
CheckKind::DashedUnderlineAfterSection(_) => &CheckCode::D407,
CheckKind::DocumentAllArguments(_) => &CheckCode::D417,
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
CheckKind::BlankLineBeforeSection(..) => &CheckCode::D411,
CheckKind::CapitalizeSectionName(..) => &CheckCode::D405,
CheckKind::DashedUnderlineAfterSection(..) => &CheckCode::D407,
CheckKind::DocumentAllArguments(..) => &CheckCode::D417,
CheckKind::EndsInPeriod => &CheckCode::D400,
CheckKind::EndsInPunctuation => &CheckCode::D415,
CheckKind::FirstLineCapitalized => &CheckCode::D403,
@@ -1838,21 +1859,21 @@ impl CheckKind {
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
CheckKind::NewLineAfterLastParagraph => &CheckCode::D209,
CheckKind::NewLineAfterSectionName(_) => &CheckCode::D406,
CheckKind::NoBlankLineAfterFunction(_) => &CheckCode::D202,
CheckKind::NewLineAfterSectionName(..) => &CheckCode::D406,
CheckKind::NoBlankLineAfterFunction(..) => &CheckCode::D202,
CheckKind::BlankLineAfterSummary => &CheckCode::D205,
CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211,
CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201,
CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412,
CheckKind::NoBlankLineBeforeClass(..) => &CheckCode::D211,
CheckKind::NoBlankLineBeforeFunction(..) => &CheckCode::D201,
CheckKind::NoBlankLinesBetweenHeaderAndContent(..) => &CheckCode::D412,
CheckKind::NoOverIndentation => &CheckCode::D208,
CheckKind::NoSignature => &CheckCode::D402,
CheckKind::NoSurroundingWhitespace => &CheckCode::D210,
CheckKind::NoThisPrefix => &CheckCode::D404,
CheckKind::NoUnderIndentation => &CheckCode::D207,
CheckKind::NonEmpty => &CheckCode::D419,
CheckKind::NonEmptySection(_) => &CheckCode::D414,
CheckKind::OneBlankLineAfterClass(_) => &CheckCode::D204,
CheckKind::OneBlankLineBeforeClass(_) => &CheckCode::D203,
CheckKind::NonEmptySection(..) => &CheckCode::D414,
CheckKind::OneBlankLineAfterClass(..) => &CheckCode::D204,
CheckKind::OneBlankLineBeforeClass(..) => &CheckCode::D203,
CheckKind::PublicClass => &CheckCode::D101,
CheckKind::PublicFunction => &CheckCode::D103,
CheckKind::PublicInit => &CheckCode::D107,
@@ -1860,18 +1881,18 @@ impl CheckKind {
CheckKind::PublicModule => &CheckCode::D100,
CheckKind::PublicNestedClass => &CheckCode::D106,
CheckKind::PublicPackage => &CheckCode::D104,
CheckKind::SectionNameEndsInColon(_) => &CheckCode::D416,
CheckKind::SectionNotOverIndented(_) => &CheckCode::D214,
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
CheckKind::SectionNameEndsInColon(..) => &CheckCode::D416,
CheckKind::SectionNotOverIndented(..) => &CheckCode::D214,
CheckKind::SectionUnderlineAfterName(..) => &CheckCode::D408,
CheckKind::SectionUnderlineMatchesSectionLength(..) => &CheckCode::D409,
CheckKind::SectionUnderlineNotOverIndented(..) => &CheckCode::D215,
CheckKind::SkipDocstring => &CheckCode::D418,
CheckKind::UsesRPrefixForBackslashedContent => &CheckCode::D301,
CheckKind::UsesTripleQuotes => &CheckCode::D300,
// pep8-naming
CheckKind::InvalidClassName(_) => &CheckCode::N801,
CheckKind::InvalidFunctionName(_) => &CheckCode::N802,
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
CheckKind::InvalidClassName(..) => &CheckCode::N801,
CheckKind::InvalidFunctionName(..) => &CheckCode::N802,
CheckKind::InvalidArgumentName(..) => &CheckCode::N803,
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
CheckKind::NonLowercaseVariableInFunction(..) => &CheckCode::N806,
@@ -1944,7 +1965,7 @@ impl CheckKind {
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
CheckKind::UnusedNOQA(_) => &CheckCode::RUF100,
CheckKind::UnusedNOQA(..) => &CheckCode::RUF100,
}
}
@@ -2016,6 +2037,7 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => {
"Module level import not at top of file".to_string()
}
CheckKind::MultipleImportsOnOneLine => "Multiple imports on one line".to_string(),
CheckKind::MultiValueRepeatedKeyLiteral => {
"Dictionary key literal repeated".to_string()
}
@@ -2543,6 +2565,8 @@ impl CheckKind {
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
CheckKind::RedundantOpenModes => "Unnecessary open mode parameters".to_string(),
CheckKind::RemoveSixCompat => "Unnecessary `six` compatibility usage".to_string(),
CheckKind::DatetimeTimezoneUTC => "Use `datetime.UTC` alias".to_string(),
CheckKind::NativeLiterals => "Unnecessary call to `str` and `bytes`".to_string(),
CheckKind::ConvertTypedDictFunctionalToClass(name) => {
format!("Convert `{name}` from `TypedDict` functional to class syntax")
}
@@ -2587,13 +2611,13 @@ impl CheckKind {
CheckKind::NoBlankLineAfterFunction(num_lines) => {
format!("No blank lines allowed after function docstring (found {num_lines})")
}
CheckKind::NoBlankLineBeforeClass(_) => {
CheckKind::NoBlankLineBeforeClass(..) => {
"No blank lines allowed before class docstring".to_string()
}
CheckKind::OneBlankLineBeforeClass(_) => {
CheckKind::OneBlankLineBeforeClass(..) => {
"1 blank line required before class docstring".to_string()
}
CheckKind::OneBlankLineAfterClass(_) => {
CheckKind::OneBlankLineAfterClass(..) => {
"1 blank line required after class docstring".to_string()
}
CheckKind::PublicModule => "Missing docstring in public module".to_string(),
@@ -2839,19 +2863,44 @@ impl CheckKind {
)
}
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
None => "Unused blanket `noqa` directive".to_string(),
Some(codes) => {
let codes = codes
.iter()
.map(|code| {
if CheckCode::from_str(code).is_ok() {
code.to_string()
} else {
format!("{code} (not implemented)")
}
})
.join(", ");
format!("Unused `noqa` directive for: {codes}")
let mut codes_by_reason = vec![];
if !codes.unmatched.is_empty() {
codes_by_reason.push(format!(
"unused: {}",
codes
.unmatched
.iter()
.map(|code| format!("`{code}`"))
.join(", ")
));
}
if !codes.disabled.is_empty() {
codes_by_reason.push(format!(
"non-enabled: {}",
codes
.disabled
.iter()
.map(|code| format!("`{code}`"))
.join(", ")
));
}
if !codes.unknown.is_empty() {
codes_by_reason.push(format!(
"unknown: {}",
codes
.unknown
.iter()
.map(|code| format!("`{code}`"))
.join(", ")
));
}
if codes_by_reason.is_empty() {
"Unused `noqa` directive".to_string()
} else {
format!("Unused `noqa` directive ({})", codes_by_reason.join("; "))
}
}
},
// flake8-datetimez
@@ -2944,6 +2993,7 @@ impl CheckKind {
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
| CheckKind::DatetimeTimezoneUTC
| CheckKind::DeprecatedUnittestAlias(..)
| CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda
@@ -2953,16 +3003,19 @@ impl CheckKind {
| CheckKind::GetAttrWithConstant
| CheckKind::ImplicitReturn
| CheckKind::ImplicitReturnValue
| CheckKind::InvalidEscapeSequence(..)
| CheckKind::IsLiteral
| CheckKind::KeyInDict(..)
| CheckKind::MisplacedComparisonConstant(..)
| CheckKind::MissingReturnTypeSpecialMethod(..)
| CheckKind::NativeLiterals
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
| CheckKind::NoBlankLineBeforeClass(..)
| CheckKind::NoBlankLineBeforeFunction(..)
| CheckKind::NoBlankLinesBetweenHeaderAndContent(..)
| CheckKind::NoNewLineAtEndOfFile
| CheckKind::NoOverIndentation
| CheckKind::NoSurroundingWhitespace
| CheckKind::NoUnderIndentation
@@ -3043,6 +3096,81 @@ impl Check {
}
}
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCodePrefix>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", CheckCodePrefix::UP001),
("U003", CheckCodePrefix::UP003),
("U004", CheckCodePrefix::UP004),
("U005", CheckCodePrefix::UP005),
("U006", CheckCodePrefix::UP006),
("U007", CheckCodePrefix::UP007),
("U008", CheckCodePrefix::UP008),
("U009", CheckCodePrefix::UP009),
("U010", CheckCodePrefix::UP010),
("U011", CheckCodePrefix::UP011),
("U012", CheckCodePrefix::UP012),
("U013", CheckCodePrefix::UP013),
("U014", CheckCodePrefix::UP014),
("U015", CheckCodePrefix::UP015),
("U016", CheckCodePrefix::UP016),
("U017", CheckCodePrefix::UP017),
// TODO(charlie): Remove by 2023-02-01.
("I252", CheckCodePrefix::TID252),
("M001", CheckCodePrefix::RUF100),
// TODO(charlie): Remove by 2023-02-01.
("PDV002", CheckCodePrefix::PD002),
("PDV003", CheckCodePrefix::PD003),
("PDV004", CheckCodePrefix::PD004),
("PDV007", CheckCodePrefix::PD007),
("PDV008", CheckCodePrefix::PD008),
("PDV009", CheckCodePrefix::PD009),
("PDV010", CheckCodePrefix::PD010),
("PDV011", CheckCodePrefix::PD011),
("PDV012", CheckCodePrefix::PD012),
("PDV013", CheckCodePrefix::PD013),
("PDV015", CheckCodePrefix::PD015),
("PDV901", CheckCodePrefix::PD901),
// TODO(charlie): Remove by 2023-02-01.
("R501", CheckCodePrefix::RET501),
("R502", CheckCodePrefix::RET502),
("R503", CheckCodePrefix::RET503),
("R504", CheckCodePrefix::RET504),
("R505", CheckCodePrefix::RET505),
("R506", CheckCodePrefix::RET506),
("R507", CheckCodePrefix::RET507),
("R508", CheckCodePrefix::RET508),
("IC001", CheckCodePrefix::ICN001),
("IC002", CheckCodePrefix::ICN001),
("IC003", CheckCodePrefix::ICN001),
("IC004", CheckCodePrefix::ICN001),
// TODO(charlie): Remove by 2023-01-01.
("U", CheckCodePrefix::UP),
("U0", CheckCodePrefix::UP0),
("U00", CheckCodePrefix::UP00),
("U01", CheckCodePrefix::UP01),
// TODO(charlie): Remove by 2023-02-01.
("I2", CheckCodePrefix::TID2),
("I25", CheckCodePrefix::TID25),
("M", CheckCodePrefix::RUF100),
("M0", CheckCodePrefix::RUF100),
// TODO(charlie): Remove by 2023-02-01.
("PDV", CheckCodePrefix::PD),
("PDV0", CheckCodePrefix::PD0),
("PDV01", CheckCodePrefix::PD01),
("PDV9", CheckCodePrefix::PD9),
("PDV90", CheckCodePrefix::PD90),
// TODO(charlie): Remove by 2023-02-01.
("R", CheckCodePrefix::RET),
("R5", CheckCodePrefix::RET5),
("R50", CheckCodePrefix::RET50),
// TODO(charlie): Remove by 2023-02-01.
("IC", CheckCodePrefix::ICN),
("IC0", CheckCodePrefix::ICN0),
])
});
/// A hash map from deprecated to latest `CheckCode`.
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
FxHashMap::from_iter([
@@ -3062,6 +3190,7 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(
("U014", CheckCode::UP014),
("U015", CheckCode::UP015),
("U016", CheckCode::UP016),
("U017", CheckCode::UP017),
// TODO(charlie): Remove by 2023-02-01.
("I252", CheckCode::TID252),
("M001", CheckCode::RUF100),
@@ -3078,28 +3207,20 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(
("PDV013", CheckCode::PD013),
("PDV015", CheckCode::PD015),
("PDV901", CheckCode::PD901),
])
});
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U", "UP"),
("U0", "UP0"),
("U00", "UP00"),
("U01", "UP01"),
// TODO(charlie): Remove by 2023-02-01.
("I2", "TID2"),
("I25", "TID25"),
("M", "RUF100"),
("M0", "RUF100"),
("R501", CheckCode::RET501),
("R502", CheckCode::RET502),
("R503", CheckCode::RET503),
("R504", CheckCode::RET504),
("R505", CheckCode::RET505),
("R506", CheckCode::RET506),
("R507", CheckCode::RET507),
("R508", CheckCode::RET508),
// TODO(charlie): Remove by 2023-02-01.
("PDV", "PD"),
("PDV0", "PD0"),
("PDV01", "PD01"),
("PDV9", "PD9"),
("PDV90", "PD90"),
("IC001", CheckCode::ICN001),
("IC002", CheckCode::ICN001),
("IC003", CheckCode::ICN001),
("IC004", CheckCode::ICN001),
])
});

View File

@@ -1,6 +1,7 @@
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
use colored::Colorize;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumString};
@@ -8,7 +9,17 @@ use crate::checks::CheckCode;
use crate::one_time_warning;
#[derive(
EnumString, AsRefStr, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
EnumString,
AsRefStr,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Serialize,
Deserialize,
JsonSchema,
)]
pub enum CheckCodePrefix {
A,
@@ -179,6 +190,7 @@ pub enum CheckCodePrefix {
E,
E4,
E40,
E401,
E402,
E5,
E50,
@@ -291,6 +303,12 @@ pub enum CheckCodePrefix {
I2,
I25,
I252,
IC,
IC0,
IC001,
IC002,
IC003,
IC004,
ICN,
ICN0,
ICN00,
@@ -410,6 +428,17 @@ pub enum CheckCodePrefix {
Q001,
Q002,
Q003,
R,
R5,
R50,
R501,
R502,
R503,
R504,
R505,
R506,
R507,
R508,
RET,
RET5,
RET50,
@@ -474,6 +503,7 @@ pub enum CheckCodePrefix {
U014,
U015,
U016,
U017,
UP,
UP0,
UP00,
@@ -493,6 +523,8 @@ pub enum CheckCodePrefix {
UP014,
UP015,
UP016,
UP017,
UP018,
W,
W2,
W29,
@@ -1046,6 +1078,7 @@ impl CheckCodePrefix {
CheckCodePrefix::DTZ011 => vec![CheckCode::DTZ011],
CheckCodePrefix::DTZ012 => vec![CheckCode::DTZ012],
CheckCodePrefix::E => vec![
CheckCode::E401,
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -1061,8 +1094,9 @@ impl CheckCodePrefix {
CheckCode::E902,
CheckCode::E999,
],
CheckCodePrefix::E4 => vec![CheckCode::E402],
CheckCodePrefix::E40 => vec![CheckCode::E402],
CheckCodePrefix::E4 => vec![CheckCode::E401, CheckCode::E402],
CheckCodePrefix::E40 => vec![CheckCode::E401, CheckCode::E402],
CheckCodePrefix::E401 => vec![CheckCode::E401],
CheckCodePrefix::E402 => vec![CheckCode::E402],
CheckCodePrefix::E5 => vec![CheckCode::E501],
CheckCodePrefix::E50 => vec![CheckCode::E501],
@@ -1343,6 +1377,60 @@ impl CheckCodePrefix {
);
vec![CheckCode::TID252]
}
CheckCodePrefix::IC => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC` has been remapped to `ICN`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::IC0 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC0` has been remapped to `ICN0`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::IC001 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC001` has been remapped to `ICN001`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::IC002 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC002` has been remapped to `ICN001`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::IC003 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC003` has been remapped to `ICN001`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::IC004 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`IC004` has been remapped to `ICN001`".bold()
);
vec![CheckCode::ICN001]
}
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
@@ -1764,6 +1852,132 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::R => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R` has been remapped to `RET`".bold()
);
vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
]
}
CheckCodePrefix::R5 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R5` has been remapped to `RET5`".bold()
);
vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
]
}
CheckCodePrefix::R50 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R50` has been remapped to `RET50`".bold()
);
vec![
CheckCode::RET501,
CheckCode::RET502,
CheckCode::RET503,
CheckCode::RET504,
CheckCode::RET505,
CheckCode::RET506,
CheckCode::RET507,
CheckCode::RET508,
]
}
CheckCodePrefix::R501 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R501` has been remapped to `RET501`".bold()
);
vec![CheckCode::RET501]
}
CheckCodePrefix::R502 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R502` has been remapped to `RET502`".bold()
);
vec![CheckCode::RET502]
}
CheckCodePrefix::R503 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R503` has been remapped to `RET503`".bold()
);
vec![CheckCode::RET503]
}
CheckCodePrefix::R504 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R504` has been remapped to `RET504`".bold()
);
vec![CheckCode::RET504]
}
CheckCodePrefix::R505 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R505` has been remapped to `RET505`".bold()
);
vec![CheckCode::RET505]
}
CheckCodePrefix::R506 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R506` has been remapped to `RET506`".bold()
);
vec![CheckCode::RET506]
}
CheckCodePrefix::R507 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R507` has been remapped to `RET507`".bold()
);
vec![CheckCode::RET507]
}
CheckCodePrefix::R508 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`R508` has been remapped to `RET508`".bold()
);
vec![CheckCode::RET508]
}
CheckCodePrefix::RET => vec![
CheckCode::RET501,
CheckCode::RET502,
@@ -1885,6 +2099,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U0 => {
@@ -1910,6 +2126,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U00 => {
@@ -2017,6 +2235,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
]
}
CheckCodePrefix::U010 => {
@@ -2082,6 +2302,15 @@ impl CheckCodePrefix {
);
vec![CheckCode::UP016]
}
CheckCodePrefix::U017 => {
one_time_warning!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U017` has been remapped to `UP017`".bold()
);
vec![CheckCode::UP017]
}
CheckCodePrefix::UP => vec![
CheckCode::UP001,
CheckCode::UP003,
@@ -2098,6 +2327,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
@@ -2115,6 +2346,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
@@ -2142,6 +2375,8 @@ impl CheckCodePrefix {
CheckCode::UP014,
CheckCode::UP015,
CheckCode::UP016,
CheckCode::UP017,
CheckCode::UP018,
],
CheckCodePrefix::UP010 => vec![CheckCode::UP010],
CheckCodePrefix::UP011 => vec![CheckCode::UP011],
@@ -2150,6 +2385,8 @@ impl CheckCodePrefix {
CheckCodePrefix::UP014 => vec![CheckCode::UP014],
CheckCodePrefix::UP015 => vec![CheckCode::UP015],
CheckCodePrefix::UP016 => vec![CheckCode::UP016],
CheckCodePrefix::UP017 => vec![CheckCode::UP017],
CheckCodePrefix::UP018 => vec![CheckCode::UP018],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -2371,6 +2608,7 @@ impl CheckCodePrefix {
CheckCodePrefix::E => SuffixLength::Zero,
CheckCodePrefix::E4 => SuffixLength::One,
CheckCodePrefix::E40 => SuffixLength::Two,
CheckCodePrefix::E401 => SuffixLength::Three,
CheckCodePrefix::E402 => SuffixLength::Three,
CheckCodePrefix::E5 => SuffixLength::One,
CheckCodePrefix::E50 => SuffixLength::Two,
@@ -2483,6 +2721,12 @@ impl CheckCodePrefix {
CheckCodePrefix::I2 => SuffixLength::One,
CheckCodePrefix::I25 => SuffixLength::Two,
CheckCodePrefix::I252 => SuffixLength::Three,
CheckCodePrefix::IC => SuffixLength::Zero,
CheckCodePrefix::IC0 => SuffixLength::One,
CheckCodePrefix::IC001 => SuffixLength::Three,
CheckCodePrefix::IC002 => SuffixLength::Three,
CheckCodePrefix::IC003 => SuffixLength::Three,
CheckCodePrefix::IC004 => SuffixLength::Three,
CheckCodePrefix::ICN => SuffixLength::Zero,
CheckCodePrefix::ICN0 => SuffixLength::One,
CheckCodePrefix::ICN00 => SuffixLength::Two,
@@ -2602,6 +2846,17 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => SuffixLength::Three,
CheckCodePrefix::Q002 => SuffixLength::Three,
CheckCodePrefix::Q003 => SuffixLength::Three,
CheckCodePrefix::R => SuffixLength::Zero,
CheckCodePrefix::R5 => SuffixLength::One,
CheckCodePrefix::R50 => SuffixLength::Two,
CheckCodePrefix::R501 => SuffixLength::Three,
CheckCodePrefix::R502 => SuffixLength::Three,
CheckCodePrefix::R503 => SuffixLength::Three,
CheckCodePrefix::R504 => SuffixLength::Three,
CheckCodePrefix::R505 => SuffixLength::Three,
CheckCodePrefix::R506 => SuffixLength::Three,
CheckCodePrefix::R507 => SuffixLength::Three,
CheckCodePrefix::R508 => SuffixLength::Three,
CheckCodePrefix::RET => SuffixLength::Zero,
CheckCodePrefix::RET5 => SuffixLength::One,
CheckCodePrefix::RET50 => SuffixLength::Two,
@@ -2666,6 +2921,7 @@ impl CheckCodePrefix {
CheckCodePrefix::U014 => SuffixLength::Three,
CheckCodePrefix::U015 => SuffixLength::Three,
CheckCodePrefix::U016 => SuffixLength::Three,
CheckCodePrefix::U017 => SuffixLength::Three,
CheckCodePrefix::UP => SuffixLength::Zero,
CheckCodePrefix::UP0 => SuffixLength::One,
CheckCodePrefix::UP00 => SuffixLength::Two,
@@ -2685,6 +2941,8 @@ impl CheckCodePrefix {
CheckCodePrefix::UP014 => SuffixLength::Three,
CheckCodePrefix::UP015 => SuffixLength::Three,
CheckCodePrefix::UP016 => SuffixLength::Three,
CheckCodePrefix::UP017 => SuffixLength::Three,
CheckCodePrefix::UP018 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,

View File

@@ -133,6 +133,9 @@ pub struct Cli {
/// Generate shell completion
#[arg(long, hide = true, value_name = "SHELL")]
pub generate_shell_completion: Option<clap_complete_command::Shell>,
/// Path to the cache directory.
#[arg(long)]
pub cache_dir: Option<PathBuf>,
}
impl Cli {
@@ -180,6 +183,7 @@ impl Cli {
fix: resolve_bool_arg(self.fix, self.no_fix),
format: self.format,
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
cache_dir: self.cache_dir,
},
)
}
@@ -238,6 +242,7 @@ pub struct Overrides {
pub fix: Option<bool>,
pub format: Option<SerializationFormat>,
pub force_exclude: Option<bool>,
pub cache_dir: Option<PathBuf>,
}
/// Map the CLI settings to a `LogLevel`.

View File

@@ -20,7 +20,7 @@ use crate::message::Message;
use crate::resolver::{FileDiscovery, PyprojectDiscovery};
use crate::settings::flags;
use crate::settings::types::SerializationFormat;
use crate::{packages, resolver};
use crate::{cache, packages, resolver};
/// Run the linter over a collection of files.
pub fn run(
@@ -47,6 +47,30 @@ pub fn run(
.collect::<Vec<_>>(),
);
// Initialize the cache.
if matches!(cache, flags::Cache::Enabled) {
match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => {
if let Err(e) = cache::init(&settings.cache_dir) {
error!(
"Failed to initialize cache at {}: {e:?}",
settings.cache_dir.to_string_lossy()
);
}
}
PyprojectDiscovery::Hierarchical(default) => {
for settings in std::iter::once(default).chain(resolver.iter()) {
if let Err(e) = cache::init(&settings.cache_dir) {
error!(
"Failed to initialize cache at {}: {e:?}",
settings.cache_dir.to_string_lossy()
);
}
}
}
}
};
let start = Instant::now();
let mut diagnostics: Diagnostics = par_iter(&paths)
.map(|entry| {
@@ -114,16 +138,26 @@ fn read_from_stdin() -> Result<String> {
/// Run the linter over a single file, read from `stdin`.
pub fn run_stdin(
strategy: &PyprojectDiscovery,
filename: &Path,
filename: Option<&Path>,
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
let stdin = read_from_stdin()?;
let settings = match strategy {
if let Some(filename) = filename {
if !resolver::python_file_at_path(filename, pyproject_strategy, file_strategy, overrides)? {
return Ok(Diagnostics::default());
}
}
let settings = match pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings,
PyprojectDiscovery::Hierarchical(settings) => settings,
};
let mut diagnostics = lint_stdin(filename, &stdin, settings, autofix)?;
let package_root = filename
.and_then(Path::parent)
.and_then(packages::detect_package_root);
let stdin = read_from_stdin()?;
let mut diagnostics = lint_stdin(filename, package_root, &stdin, settings, autofix)?;
diagnostics.messages.sort_unstable();
Ok(diagnostics)
}

View File

@@ -1,51 +1,53 @@
//! Settings for the `flake-annotations` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8AnnotationsOptions"
)]
pub struct Options {
#[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"
)]
/// Whether to allow the omission of a return type hint for `__init__` if at
/// least one argument is annotated.
pub mypy_init_return: Option<bool>,
#[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"
)]
/// Whether to suppress `ANN000`-level errors for arguments matching the
/// "dummy" variable regex (like `_`).
pub suppress_dummy_args: Option<bool>,
#[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"
)]
/// 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).
pub suppress_none_returning: Option<bool>,
#[option(
doc = "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` \
arguments.",
default = "false",
value_type = "bool",
example = "allow-star-arg-any = true"
)]
/// Whether to suppress `ANN401` for dynamically typed `*args` and
/// `**kwargs` arguments.
pub allow_star_arg_any: Option<bool>,
}

View File

@@ -5,16 +5,35 @@ use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
const FUNC_NAME_ALLOWLIST: &[&str] = &[
"assertEqual",
"assertEquals",
"assertNotEqual",
"assertNotEquals",
"failIfEqual",
"failUnlessEqual",
"fromkeys",
"get",
"getattr",
"index",
"pop",
"setattr",
"setdefault",
];
/// Returns `true` if an argument is allowed to use a boolean trap. To return
/// `true`, the function name must be explicitly allowed, and the argument must
/// be either the first or second argument in the call.
fn allow_boolean_trap(func: &Expr) -> bool {
let ExprKind::Attribute { attr, .. } = &func.node else {
return false;
};
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
if let ExprKind::Attribute { attr, .. } = &func.node {
return FUNC_NAME_ALLOWLIST.contains(&attr.as_ref());
}
if let ExprKind::Name { id, .. } = &func.node {
return FUNC_NAME_ALLOWLIST.contains(&id.as_ref());
}
false
}
fn is_boolean_arg(arg: &Expr) -> bool {
@@ -79,8 +98,8 @@ pub fn check_boolean_positional_value_in_function_call(
args: &[Expr],
func: &Expr,
) {
for (index, arg) in args.iter().enumerate() {
if index < 2 && allow_boolean_trap(func) {
for arg in args {
if allow_boolean_trap(func) {
continue;
}
add_if_boolean(

View File

@@ -1,8 +1,6 @@
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustpython_ast::{
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
};
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location};
use crate::ast::helpers;
use crate::ast::types::Range;
@@ -26,17 +24,17 @@ fn duplicate_handler_exceptions<'a>(
checker: &mut Checker,
expr: &'a Expr,
elts: &'a [Expr],
) -> FxHashSet<Vec<&'a str>> {
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
) -> FxHashMap<Vec<&'a str>, &'a Expr> {
let mut seen: FxHashMap<Vec<&str>, &Expr> = FxHashMap::default();
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut unique_elts: Vec<&Expr> = Vec::default();
for type_ in elts {
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
if seen.contains(&call_path) {
if seen.contains_key(&call_path) {
duplicates.insert(call_path);
} else {
seen.insert(call_path);
seen.entry(call_path).or_insert(type_);
unique_elts.push(type_);
}
}
@@ -77,9 +75,9 @@ fn duplicate_handler_exceptions<'a>(
seen
}
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
pub fn duplicate_exceptions(checker: &mut Checker, handlers: &[Excepthandler]) {
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut duplicates: FxHashMap<Vec<&str>, Vec<&Expr>> = FxHashMap::default();
for handler in handlers {
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;
@@ -89,16 +87,16 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
if seen.contains(&call_path) {
duplicates.insert(call_path);
duplicates.entry(call_path).or_default().push(type_);
} else {
seen.insert(call_path);
}
}
}
ExprKind::Tuple { elts, .. } => {
for name in duplicate_handler_exceptions(checker, type_, elts) {
for (name, expr) in duplicate_handler_exceptions(checker, type_, elts) {
if seen.contains(&name) {
duplicates.insert(name);
duplicates.entry(name).or_default().push(expr);
} else {
seen.insert(name);
}
@@ -109,11 +107,13 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
}
if checker.settings.enabled.contains(&CheckCode::B025) {
for duplicate in duplicates.into_iter().sorted() {
checker.add_check(Check::new(
CheckKind::DuplicateTryBlockException(duplicate.join(".")),
Range::from_located(stmt),
));
for (name, exprs) in duplicates {
for expr in exprs {
checker.add_check(Check::new(
CheckKind::DuplicateTryBlockException(name.join(".")),
Range::from_located(expr),
));
}
}
}
}

View File

@@ -1,16 +1,19 @@
//! Settings for the `flake8-bugbear` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8BugbearOptions"
)]
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#"
@@ -18,6 +21,8 @@ pub struct Options {
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
"#
)]
/// Additional callable functions to consider "immutable" when evaluating,
/// e.g., `no-mutable-default-argument` checks (`B006`).
pub extend_immutable_calls: Option<Vec<String>>,
}

View File

@@ -5,37 +5,37 @@ expression: checks
- kind:
DuplicateTryBlockException: ValueError
location:
row: 15
column: 0
row: 19
column: 7
end_location:
row: 20
column: 9
row: 19
column: 17
fix: ~
- kind:
DuplicateTryBlockException: pickle.PickleError
location:
row: 22
column: 0
row: 28
column: 7
end_location:
row: 29
column: 9
fix: ~
- kind:
DuplicateTryBlockException: TypeError
location:
row: 31
column: 0
end_location:
row: 38
column: 9
row: 28
column: 25
fix: ~
- kind:
DuplicateTryBlockException: ValueError
location:
row: 31
column: 0
row: 35
column: 7
end_location:
row: 38
column: 9
row: 35
column: 17
fix: ~
- kind:
DuplicateTryBlockException: TypeError
location:
row: 37
column: 17
end_location:
row: 37
column: 26
fix: ~

View File

@@ -19,20 +19,14 @@ pub fn call_datetime_without_tzinfo(
return;
}
// no args / no args unqualified
if args.len() < 8 && keywords.is_empty() {
// No positional arg: keyword is missing or constant None.
if args.len() < 8 && !has_non_none_keyword(keywords, "tzinfo") {
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
return;
}
// none args
if args.len() == 8 && is_const_none(&args[7]) {
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
return;
}
// no kwargs / none kwargs
if !has_non_none_keyword(keywords, "tzinfo") {
// Positional arg: is constant None.
if args.len() >= 8 && is_const_none(&args[7]) {
checker.add_check(Check::new(CheckKind::CallDatetimeWithoutTzinfo, location));
}
}

View File

@@ -20,26 +20,26 @@ expression: checks
fix: ~
- kind: CallDatetimeWithoutTzinfo
location:
row: 10
row: 13
column: 0
end_location:
row: 10
row: 13
column: 37
fix: ~
- kind: CallDatetimeWithoutTzinfo
location:
row: 13
row: 16
column: 0
end_location:
row: 13
row: 16
column: 42
fix: ~
- kind: CallDatetimeWithoutTzinfo
location:
row: 18
row: 21
column: 0
end_location:
row: 18
row: 21
column: 29
fix: ~

View File

@@ -1,46 +0,0 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
pub fn check_string_in_exception(exc: &Expr, max_string_length: usize) -> Vec<Check> {
let mut checks = vec![];
if let ExprKind::Call { args, .. } = &exc.node {
if let Some(first) = args.first() {
match &first.node {
// Check for string literals
ExprKind::Constant {
value: Constant::Str(string),
..
} => {
if string.len() > max_string_length {
checks.push(Check::new(
CheckKind::RawStringInException,
Range::from_located(first),
));
}
}
// Check for f-strings
ExprKind::JoinedStr { .. } => checks.push(Check::new(
CheckKind::FStringInException,
Range::from_located(first),
)),
// Check for .format() calls
ExprKind::Call { func, .. } => {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
checks.push(Check::new(
CheckKind::DotFormatInException,
Range::from_located(first),
));
}
}
}
_ => {}
}
}
}
checks
}

View File

@@ -1,4 +1,4 @@
pub mod checks;
pub mod plugins;
pub mod settings;
#[cfg(test)]

View File

@@ -0,0 +1,52 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
/// EM101, EM102, EM103
pub fn string_in_exception(checker: &mut Checker, exc: &Expr) {
if let ExprKind::Call { args, .. } = &exc.node {
if let Some(first) = args.first() {
match &first.node {
// Check for string literals
ExprKind::Constant {
value: Constant::Str(string),
..
} => {
if checker.settings.enabled.contains(&CheckCode::EM101) {
if string.len() > checker.settings.flake8_errmsg.max_string_length {
checker.add_check(Check::new(
CheckKind::RawStringInException,
Range::from_located(first),
));
}
}
}
// Check for f-strings
ExprKind::JoinedStr { .. } => {
if checker.settings.enabled.contains(&CheckCode::EM102) {
checker.add_check(Check::new(
CheckKind::FStringInException,
Range::from_located(first),
));
}
}
// Check for .format() calls
ExprKind::Call { func, .. } => {
if checker.settings.enabled.contains(&CheckCode::EM103) {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
checker.add_check(Check::new(
CheckKind::DotFormatInException,
Range::from_located(first),
));
}
}
}
}
_ => {}
}
}
}
}

View File

@@ -1,19 +1,24 @@
//! Settings for the `flake8-errmsg` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8ErrMsgOptions"
)]
pub struct Options {
#[option(
doc = r#"
Maximum string length for string literals in exception messages.
"#,
default = "0",
value_type = "usize",
example = "max-string-length = 20"
)]
/// Maximum string length for string literals in exception messages.
pub max_string_length: Option<usize>,
}

View File

@@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher};
use itertools::Itertools;
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
@@ -15,12 +16,16 @@ const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
("seaborn", "sns"),
];
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8ImportConventionsOptions"
)]
pub struct Options {
#[option(
doc = "The conventional aliases for imports. These aliases can be extended by the \
`extend_aliases` option.",
default = r#"{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}"#,
value_type = "FxHashMap<String, String>",
example = r#"
@@ -32,10 +37,10 @@ pub struct Options {
seaborn = "sns"
"#
)]
/// The conventional aliases for imports. These aliases can be extended by
/// the `extend_aliases` option.
pub aliases: Option<FxHashMap<String, String>>,
#[option(
doc = "A mapping of modules to their conventional import aliases. These aliases will be \
added to the `aliases` mapping.",
default = r#"{}"#,
value_type = "FxHashMap<String, String>",
example = r#"
@@ -43,6 +48,8 @@ pub struct Options {
"dask.dataframe" = "dd"
"#
)]
/// A mapping of modules to their conventional import aliases. These aliases
/// will be added to the `aliases` mapping.
pub extend_aliases: Option<FxHashMap<String, String>>,
}

View File

@@ -1,57 +1,56 @@
//! Settings for the `flake8-quotes` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Quote {
Single,
Double,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8QuotesOptions"
)]
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"
"#
)]
/// Quote style to prefer for inline strings (either "single" (`'`) or
/// "double" (`"`)).
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"
"#
)]
/// Quote style to prefer for multiline strings (either "single" (`'`) or
/// "double" (`"`)).
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"
"#
)]
/// Quote style to prefer for docstrings (either "single" (`'`) or "double"
/// (`"`)).
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#"
@@ -59,6 +58,9 @@ pub struct Options {
avoid-escape = false
"#
)]
/// 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.
pub avoid_escape: Option<bool>,
}

View File

@@ -203,6 +203,10 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
return;
}
if stack.non_locals.contains(id.as_str()) {
return;
}
checker.add_check(Check::new(
CheckKind::UnnecessaryAssign,
Range::from_located(expr),

View File

@@ -1,4 +1,4 @@
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::visitor;
@@ -10,6 +10,7 @@ pub struct Stack<'a> {
pub ifs: Vec<&'a Stmt>,
pub elifs: Vec<&'a Stmt>,
pub refs: FxHashMap<&'a str, Vec<Location>>,
pub non_locals: FxHashSet<&'a str>,
pub assigns: FxHashMap<&'a str, Vec<Location>>,
pub loops: Vec<(Location, Location)>,
pub tries: Vec<(Location, Location)>,
@@ -48,6 +49,11 @@ impl<'a> ReturnVisitor<'a> {
impl<'a> Visitor<'a> for ReturnVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
self.stack
.non_locals
.extend(names.iter().map(std::string::String::as_str));
}
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// Don't recurse.
}

View File

@@ -1,23 +1,26 @@
//! Settings for the `flake8-tidy-imports` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Strictness {
Parents,
All,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8TidyImportsOptions"
)]
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#"
@@ -25,6 +28,8 @@ pub struct Options {
ban-relative-imports = "all"
"#
)]
/// Whether to ban all relative imports (`"all"`), or only those imports
/// that extend into the parent module and beyond (`"parents"`).
pub ban_relative_imports: Option<Strictness>,
}

View File

@@ -1,19 +1,24 @@
//! Settings for the `flake8-unused-arguments` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8UnusedArgumentsOptions"
)]
pub struct Options {
#[option(
doc = r#"
Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
"#,
default = "false",
value_type = "bool",
example = "ignore-variadic-names = true"
)]
/// Whether to allow unused variadic arguments, like `*args` and `**kwargs`.
pub ignore_variadic_names: Option<bool>,
}

View File

@@ -3,40 +3,19 @@
use std::collections::BTreeSet;
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "IsortOptions"
)]
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
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
@@ -44,42 +23,62 @@ pub struct Options {
combine-as-imports = true
"#
)]
/// 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
/// )
/// ```
///
/// Note that this setting is only effective when combined with
/// `combine-as-imports = true`. When `combine-as-imports` isn't
/// enabled, every aliased `import from` will be given its own line, in
/// which case, wrapping is not necessary.
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#"false"#,
value_type = "bool",
example = r#"
combine-as-imports = true
"#
)]
/// 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.
pub combine_as_imports: Option<bool>,
#[option(
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
known-first-party = ["src"]
"#
)]
/// A list of modules to consider first-party, regardless of whether they
/// can be identified as such via introspection of the local filesystem.
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"]
"#
)]
/// A list of modules to consider third-party, regardless of whether they
/// can be identified as such via introspection of the local filesystem.
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"]
"#
)]
/// A list of modules to consider standard-library, in addition to those
/// known to Ruff in advance.
pub extra_standard_library: Option<Vec<String>>,
}

View File

@@ -260,7 +260,8 @@ pub fn autoformat_path(path: &Path, _settings: &Settings) -> Result<()> {
/// Generate a list of `Check` violations from source code content derived from
/// stdin.
pub fn lint_stdin(
path: &Path,
path: Option<&Path>,
package: Option<&Path>,
stdin: &str,
settings: &Settings,
autofix: fixer::Mode,
@@ -269,7 +270,13 @@ pub fn lint_stdin(
let contents = stdin.to_string();
// Lint the file.
let (contents, fixed, messages) = lint(contents, path, None, settings, autofix)?;
let (contents, fixed, messages) = lint(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
autofix,
)?;
// Write the fixed contents to stdout.
if matches!(autofix, fixer::Mode::Apply) {

View File

@@ -18,6 +18,7 @@ use std::sync::mpsc::channel;
use ::ruff::autofix::fixer;
use ::ruff::cli::{extract_log_level, Cli, Overrides};
use ::ruff::commands;
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::printer::Printer;
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
@@ -26,7 +27,6 @@ use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
use ::ruff::{cache, commands};
use anyhow::Result;
use clap::{CommandFactory, Parser};
use colored::Colorize;
@@ -123,6 +123,7 @@ fn inner_main() -> Result<ExitCode> {
} else {
fixer::Mode::None
};
let cache = !cli.no_cache;
if let Some(code) = cli.explain {
commands::explain(&code, &format)?;
@@ -137,13 +138,6 @@ fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::SUCCESS);
}
// Initialize the cache.
let mut cache_enabled: bool = !cli.no_cache;
if cache_enabled && cache::init().is_err() {
eprintln!("Unable to initialize cache; disabling...");
cache_enabled = false;
}
let printer = Printer::new(&format, &log_level);
if cli.watch {
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
@@ -168,7 +162,7 @@ fn inner_main() -> Result<ExitCode> {
&pyproject_strategy,
&file_strategy,
&overrides,
cache_enabled.into(),
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
@@ -198,7 +192,7 @@ fn inner_main() -> Result<ExitCode> {
&pyproject_strategy,
&file_strategy,
&overrides,
cache_enabled.into(),
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
@@ -224,15 +218,20 @@ fn inner_main() -> Result<ExitCode> {
// Generate lint violations.
let diagnostics = if is_stdin {
let path = cli.stdin_filename.unwrap_or_else(|| PathBuf::from("-"));
commands::run_stdin(&pyproject_strategy, &path, autofix)?
commands::run_stdin(
cli.stdin_filename.as_deref(),
&pyproject_strategy,
&file_strategy,
&overrides,
autofix,
)?
} else {
commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache_enabled.into(),
cache.into(),
autofix,
)?
};

View File

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

View File

@@ -1,6 +1,7 @@
//! Settings for the `pep8-naming` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
const IGNORE_NAMES: [&str; 12] = [
@@ -22,26 +23,25 @@ const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Pep8NamingOptions"
)]
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"]
"#
)]
/// A list of names to ignore when considering `pep8-naming` violations.
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#"
@@ -49,13 +49,12 @@ pub struct Options {
classmethod-decorators = ["classmethod", "pydantic.validator"]
"#
)]
/// 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.
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#"
@@ -63,6 +62,10 @@ pub struct Options {
staticmethod-decorators = ["staticmethod", "stcmthd"]
"#
)]
/// 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.
pub staticmethod_decorators: Option<Vec<String>>,
}

View File

@@ -44,7 +44,7 @@ impl<'a> Printer<'a> {
}
}
fn pre_text(&self, diagnostics: &Diagnostics) {
fn post_text(&self, diagnostics: &Diagnostics, autofix: fixer::Mode) {
if self.log_level >= &LogLevel::Default {
let fixed = diagnostics.fixed;
let remaining = diagnostics.messages.len();
@@ -54,13 +54,16 @@ impl<'a> Printer<'a> {
} else if remaining > 0 {
println!("Found {remaining} error(s).");
}
}
}
fn post_text(&self, num_fixable: usize, autofix: fixer::Mode) {
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 && !matches!(autofix, fixer::Mode::Apply) {
println!("{num_fixable} potentially fixable with the --fix option.");
if !matches!(autofix, fixer::Mode::Apply) {
let num_fixable = diagnostics
.messages
.iter()
.filter(|message| message.kind.fixable())
.count();
if num_fixable > 0 {
println!("{num_fixable} potentially fixable with the --fix option.");
}
}
}
}
@@ -70,12 +73,6 @@ impl<'a> Printer<'a> {
return Ok(());
}
let num_fixable = diagnostics
.messages
.iter()
.filter(|message| message.kind.fixable())
.count();
match self.format {
SerializationFormat::Json => {
println!(
@@ -141,18 +138,13 @@ impl<'a> Printer<'a> {
println!("{}", report.to_string().unwrap());
}
SerializationFormat::Text => {
self.pre_text(diagnostics);
for message in &diagnostics.messages {
print_message(message);
}
self.post_text(num_fixable, autofix);
self.post_text(diagnostics, autofix);
}
SerializationFormat::Grouped => {
self.pre_text(diagnostics);
println!();
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
@@ -190,11 +182,9 @@ impl<'a> Printer<'a> {
println!();
}
self.post_text(num_fixable, autofix);
self.post_text(diagnostics, autofix);
}
SerializationFormat::Github => {
self.pre_text(diagnostics);
// Generate error workflow command in GitHub Actions format
// https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
diagnostics.messages.iter().for_each(|message| {

View File

@@ -1,10 +1,11 @@
use itertools::izip;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Located, Location, Stmt, StmtKind};
use rustpython_ast::{Constant, Located, Location, Stmt, StmtKind};
use rustpython_parser::ast::{Cmpop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
@@ -54,7 +55,14 @@ pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) ->
if id == "type" {
if let Some(arg) = args.first() {
// Allow comparison for types which are not obvious.
if !matches!(arg.node, ExprKind::Name { .. }) {
if !matches!(
arg.node,
ExprKind::Name { .. }
| ExprKind::Constant {
value: Constant::None,
kind: None
}
) {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
@@ -134,18 +142,24 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
}
/// W292
pub fn no_newline_at_end_of_file(contents: &str) -> Option<Check> {
pub fn no_newline_at_end_of_file(contents: &str, autofix: bool) -> Option<Check> {
if !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
// want to raise W292 anyway).
if let Some(line) = contents.lines().last() {
return Some(Check::new(
// Both locations are at the end of the file (and thus the same).
let location = Location::new(contents.lines().count(), line.len());
let mut check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
location: Location::new(contents.lines().count(), line.len() + 1),
end_location: Location::new(contents.lines().count(), line.len() + 1),
location,
end_location: location,
},
));
);
if autofix {
check.amend(Fix::insertion("\n".to_string(), location));
}
return Some(check);
}
}
None
@@ -174,6 +188,7 @@ pub fn invalid_escape_sequence(
locator: &SourceCodeLocator,
start: Location,
end: Location,
autofix: bool,
) -> Vec<Check> {
let mut checks = vec![];
@@ -221,13 +236,17 @@ pub fn invalid_escape_sequence(
};
let location = Location::new(start.row() + row_offset, col);
let end_location = Location::new(location.row(), location.column() + 2);
checks.push(Check::new(
let mut check = Check::new(
CheckKind::InvalidEscapeSequence(next_char),
Range {
location,
end_location,
},
));
);
if autofix {
check.amend(Fix::insertion(r"\".to_string(), location));
}
checks.push(check);
}
}
}

View File

@@ -13,24 +13,28 @@ mod tests {
use crate::linter::test_path;
use crate::settings;
#[test_case(CheckCode::E402, Path::new("E402.py"); "E402")]
#[test_case(CheckCode::E501, Path::new("E501.py"); "E501")]
#[test_case(CheckCode::E711, Path::new("E711.py"); "E711")]
#[test_case(CheckCode::E712, Path::new("E712.py"); "E712")]
#[test_case(CheckCode::E713, Path::new("E713.py"); "E713")]
#[test_case(CheckCode::E714, Path::new("E714.py"); "E714")]
#[test_case(CheckCode::E721, Path::new("E721.py"); "E721")]
#[test_case(CheckCode::E722, Path::new("E722.py"); "E722")]
#[test_case(CheckCode::E731, Path::new("E731.py"); "E731")]
#[test_case(CheckCode::E741, Path::new("E741.py"); "E741")]
#[test_case(CheckCode::E742, Path::new("E742.py"); "E742")]
#[test_case(CheckCode::E743, Path::new("E743.py"); "E743")]
#[test_case(CheckCode::E999, Path::new("E999.py"); "E999")]
#[test_case(CheckCode::W292, Path::new("W292_0.py"); "W292_0")]
#[test_case(CheckCode::W292, Path::new("W292_1.py"); "W292_1")]
#[test_case(CheckCode::W292, Path::new("W292_2.py"); "W292_2")]
#[test_case(CheckCode::W605, Path::new("W605_0.py"); "W605_0")]
#[test_case(CheckCode::W605, Path::new("W605_1.py"); "W605_1")]
#[test_case(CheckCode::E401, Path::new("E40.py"))]
#[test_case(CheckCode::E402, Path::new("E40.py"))]
#[test_case(CheckCode::E402, Path::new("E402.py"))]
#[test_case(CheckCode::E501, Path::new("E501.py"))]
#[test_case(CheckCode::E711, Path::new("E711.py"))]
#[test_case(CheckCode::E712, Path::new("E712.py"))]
#[test_case(CheckCode::E713, Path::new("E713.py"))]
#[test_case(CheckCode::E714, Path::new("E714.py"))]
#[test_case(CheckCode::E721, Path::new("E721.py"))]
#[test_case(CheckCode::E722, Path::new("E722.py"))]
#[test_case(CheckCode::E731, Path::new("E731.py"))]
#[test_case(CheckCode::E741, Path::new("E741.py"))]
#[test_case(CheckCode::E742, Path::new("E742.py"))]
#[test_case(CheckCode::E743, Path::new("E743.py"))]
#[test_case(CheckCode::E999, Path::new("E999.py"))]
#[test_case(CheckCode::W292, Path::new("W292_0.py"))]
#[test_case(CheckCode::W292, Path::new("W292_1.py"))]
#[test_case(CheckCode::W292, Path::new("W292_2.py"))]
#[test_case(CheckCode::W292, Path::new("W292_3.py"))]
#[test_case(CheckCode::W292, Path::new("W292_4.py"))]
#[test_case(CheckCode::W605, Path::new("W605_0.py"))]
#[test_case(CheckCode::W605, Path::new("W605_1.py"))]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View File

@@ -0,0 +1,13 @@
---
source: src/pycodestyle/mod.rs
expression: checks
---
- kind: MultipleImportsOnOneLine
location:
row: 2
column: 0
end_location:
row: 2
column: 14
fix: ~

View File

@@ -0,0 +1,29 @@
---
source: src/pycodestyle/mod.rs
expression: checks
---
- kind: ModuleImportNotAtTopOfFile
location:
row: 55
column: 0
end_location:
row: 55
column: 10
fix: ~
- kind: ModuleImportNotAtTopOfFile
location:
row: 57
column: 0
end_location:
row: 57
column: 10
fix: ~
- kind: ModuleImportNotAtTopOfFile
location:
row: 61
column: 0
end_location:
row: 61
column: 10
fix: ~

View File

@@ -42,14 +42,6 @@ expression: checks
row: 18
column: 31
fix: ~
- kind: TypeComparison
location:
row: 18
column: 35
end_location:
row: 18
column: 58
fix: ~
- kind: TypeComparison
location:
row: 20

View File

@@ -5,9 +5,16 @@ expression: checks
- kind: NoNewLineAtEndOfFile
location:
row: 2
column: 9
column: 8
end_location:
row: 2
column: 9
fix: ~
column: 8
fix:
content: "\n"
location:
row: 2
column: 8
end_location:
row: 2
column: 8

View File

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

View File

@@ -0,0 +1,20 @@
---
source: src/pycodestyle/mod.rs
expression: checks
---
- kind: NoNewLineAtEndOfFile
location:
row: 1
column: 1
end_location:
row: 1
column: 1
fix:
content: "\n"
location:
row: 1
column: 1
end_location:
row: 1
column: 1

View File

@@ -10,7 +10,14 @@ expression: checks
end_location:
row: 2
column: 11
fix: ~
fix:
content: "\\"
location:
row: 2
column: 9
end_location:
row: 2
column: 9
- kind:
InvalidEscapeSequence: "."
location:
@@ -19,7 +26,14 @@ expression: checks
end_location:
row: 6
column: 2
fix: ~
fix:
content: "\\"
location:
row: 6
column: 0
end_location:
row: 6
column: 0
- kind:
InvalidEscapeSequence: _
location:
@@ -28,7 +42,14 @@ expression: checks
end_location:
row: 11
column: 7
fix: ~
fix:
content: "\\"
location:
row: 11
column: 5
end_location:
row: 11
column: 5
- kind:
InvalidEscapeSequence: _
location:
@@ -37,5 +58,12 @@ expression: checks
end_location:
row: 18
column: 7
fix: ~
fix:
content: "\\"
location:
row: 18
column: 5
end_location:
row: 18
column: 5

View File

@@ -10,7 +10,14 @@ expression: checks
end_location:
row: 2
column: 11
fix: ~
fix:
content: "\\"
location:
row: 2
column: 9
end_location:
row: 2
column: 9
- kind:
InvalidEscapeSequence: "."
location:
@@ -19,7 +26,14 @@ expression: checks
end_location:
row: 6
column: 2
fix: ~
fix:
content: "\\"
location:
row: 6
column: 0
end_location:
row: 6
column: 0
- kind:
InvalidEscapeSequence: _
location:
@@ -28,7 +42,14 @@ expression: checks
end_location:
row: 11
column: 7
fix: ~
fix:
content: "\\"
location:
row: 11
column: 5
end_location:
row: 11
column: 5
- kind:
InvalidEscapeSequence: _
location:
@@ -37,5 +58,12 @@ expression: checks
end_location:
row: 18
column: 7
fix: ~
fix:
content: "\\"
location:
row: 18
column: 5
end_location:
row: 18
column: 5

View File

@@ -6,7 +6,7 @@ expression: checks
PercentFormatInvalidFormat: incomplete format
location:
row: 1
column: 9
column: 0
end_location:
row: 1
column: 25

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 6
column: 10
column: 0
end_location:
row: 6
column: 19
@@ -13,7 +13,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 7
column: 10
column: 0
end_location:
row: 7
column: 20
@@ -21,7 +21,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 8
column: 10
column: 0
end_location:
row: 8
column: 19
@@ -29,7 +29,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 9
column: 10
column: 0
end_location:
row: 9
column: 22
@@ -37,7 +37,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 11
column: 10
column: 0
end_location:
row: 11
column: 37
@@ -45,7 +45,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 12
column: 10
column: 0
end_location:
row: 12
column: 37
@@ -53,7 +53,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 13
column: 10
column: 0
end_location:
row: 13
column: 37

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatExpectedMapping
location:
row: 9
column: 10
column: 0
end_location:
row: 9
column: 21

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatExpectedSequence
location:
row: 17
column: 8
column: 0
end_location:
row: 17
column: 24
@@ -13,7 +13,7 @@ expression: checks
- kind: PercentFormatExpectedSequence
location:
row: 18
column: 8
column: 0
end_location:
row: 18
column: 28
@@ -21,7 +21,7 @@ expression: checks
- kind: PercentFormatExpectedSequence
location:
row: 23
column: 8
column: 0
end_location:
row: 23
column: 42

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatExpectedSequence
location:
row: 10
column: 8
column: 0
end_location:
row: 10
column: 20

View File

@@ -7,7 +7,7 @@ expression: checks
- b
location:
row: 3
column: 14
column: 0
end_location:
row: 3
column: 34
@@ -24,7 +24,7 @@ expression: checks
- b
location:
row: 8
column: 8
column: 0
end_location:
row: 8
column: 29
@@ -41,7 +41,7 @@ expression: checks
- b
location:
row: 9
column: 8
column: 0
end_location:
row: 9
column: 29

View File

@@ -7,7 +7,7 @@ expression: checks
- baz
location:
row: 8
column: 10
column: 0
end_location:
row: 8
column: 32

View File

@@ -7,7 +7,7 @@ expression: checks
- bar
location:
row: 7
column: 10
column: 0
end_location:
row: 7
column: 14

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatMixedPositionalAndNamed
location:
row: 2
column: 13
column: 0
end_location:
row: 2
column: 29
@@ -13,7 +13,7 @@ expression: checks
- kind: PercentFormatMixedPositionalAndNamed
location:
row: 3
column: 13
column: 0
end_location:
row: 3
column: 29
@@ -21,7 +21,7 @@ expression: checks
- kind: PercentFormatMixedPositionalAndNamed
location:
row: 11
column: 11
column: 0
end_location:
row: 11
column: 27

View File

@@ -8,7 +8,7 @@ expression: checks
- 1
location:
row: 5
column: 8
column: 0
end_location:
row: 5
column: 14
@@ -19,7 +19,7 @@ expression: checks
- 3
location:
row: 6
column: 8
column: 0
end_location:
row: 6
column: 19

View File

@@ -5,7 +5,7 @@ expression: checks
- kind: PercentFormatStarRequiresSequence
location:
row: 11
column: 11
column: 0
end_location:
row: 11
column: 27

View File

@@ -6,7 +6,7 @@ expression: checks
PercentFormatUnsupportedFormatCharacter: j
location:
row: 4
column: 5
column: 0
end_location:
row: 4
column: 11

View File

@@ -37,6 +37,7 @@ mod tests {
#[test_case(CheckCode::UP014, Path::new("UP014.py"); "UP014")]
#[test_case(CheckCode::UP015, Path::new("UP015.py"); "UP015")]
#[test_case(CheckCode::UP016, Path::new("UP016.py"); "UP016")]
#[test_case(CheckCode::UP018, Path::new("UP018.py"); "UP018")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(
@@ -105,4 +106,18 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn datetime_utc_alias_py311() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/pyupgrade/UP017.py"),
&settings::Settings {
target_version: PythonVersion::Py311,
..settings::Settings::for_rule(CheckCode::UP017)
},
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -0,0 +1,25 @@
use rustpython_ast::Expr;
use crate::ast::helpers::{collect_call_paths, compose_call_path, dealias_call_path};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
/// UP017
pub fn datetime_utc_alias(checker: &mut Checker, expr: &Expr) {
let dealiased_call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases);
if dealiased_call_path == ["datetime", "timezone", "utc"] {
let mut check = Check::new(CheckKind::DatetimeTimezoneUTC, Range::from_located(expr));
if checker.patch(&CheckCode::UP017) {
check.amend(Fix::replacement(
compose_call_path(expr)
.unwrap()
.replace("timezone.utc", "UTC"),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}

View File

@@ -1,6 +1,8 @@
pub use convert_named_tuple_functional_to_class::convert_named_tuple_functional_to_class;
pub use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
pub use datetime_utc_alias::datetime_utc_alias;
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use native_literals::native_literals;
pub use redundant_open_modes::redundant_open_modes;
pub use remove_six_compat::remove_six_compat;
pub use super_call_with_parameters::super_call_with_parameters;
@@ -15,7 +17,9 @@ pub use useless_object_inheritance::useless_object_inheritance;
mod convert_named_tuple_functional_to_class;
mod convert_typed_dict_functional_to_class;
mod datetime_utc_alias;
mod deprecated_unittest_alias;
mod native_literals;
mod redundant_open_modes;
mod remove_six_compat;
mod super_call_with_parameters;

View File

@@ -0,0 +1,73 @@
use rustpython_ast::{Constant, Expr, ExprKind, Keyword};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
/// UP018
pub fn native_literals(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
let ExprKind::Name { id, .. } = &func.node else { return; };
if (id == "str" || id == "bytes")
&& keywords.is_empty()
&& args.len() <= 1
&& checker.is_builtin(id)
{
let Some(arg) = args.get(0) else {
let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr));
if checker.patch(&CheckCode::UP018) {
check.amend(Fix::replacement(
format!("{}\"\"", if id == "bytes" { "b" } else { "" }),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
return;
};
if !matches!(
&arg.node,
ExprKind::Constant {
value: Constant::Str(_) | Constant::Bytes(_),
..
}
) {
return;
}
// rust-python merges adjacent string/bytes literals into one node, but we can't
// safely remove the outer call in this situation. We're following pyupgrade
// here and skip.
let arg_code = checker
.locator
.slice_source_code_range(&Range::from_located(arg));
if lexer::make_tokenizer(&arg_code)
.flatten()
.filter(|(_, tok, _)| matches!(tok, Tok::String { .. } | Tok::Bytes { .. }))
.count()
> 1
{
return;
}
let mut check = Check::new(CheckKind::NativeLiterals, Range::from_located(expr));
if checker.patch(&CheckCode::UP018) {
check.amend(Fix::replacement(
arg_code.to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}

View File

@@ -1,15 +1,19 @@
//! Settings for the `pyupgrade` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "PyUpgradeOptions"
)]
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#"
@@ -17,6 +21,11 @@ pub struct Options {
keep-runtime-typing = true
"#
)]
/// 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.
pub keep_runtime_typing: Option<bool>,
}

View File

@@ -0,0 +1,95 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: NativeLiterals
location:
row: 18
column: 0
end_location:
row: 18
column: 5
fix:
content: "\"\""
location:
row: 18
column: 0
end_location:
row: 18
column: 5
- kind: NativeLiterals
location:
row: 19
column: 0
end_location:
row: 19
column: 10
fix:
content: "\"foo\""
location:
row: 19
column: 0
end_location:
row: 19
column: 10
- kind: NativeLiterals
location:
row: 20
column: 0
end_location:
row: 21
column: 7
fix:
content: "\"\"\"\nfoo\"\"\""
location:
row: 20
column: 0
end_location:
row: 21
column: 7
- kind: NativeLiterals
location:
row: 22
column: 0
end_location:
row: 22
column: 7
fix:
content: "b\"\""
location:
row: 22
column: 0
end_location:
row: 22
column: 7
- kind: NativeLiterals
location:
row: 23
column: 0
end_location:
row: 23
column: 13
fix:
content: "b\"foo\""
location:
row: 23
column: 0
end_location:
row: 23
column: 13
- kind: NativeLiterals
location:
row: 24
column: 0
end_location:
row: 25
column: 7
fix:
content: "b\"\"\"\nfoo\"\"\""
location:
row: 24
column: 0
end_location:
row: 25
column: 7

View File

@@ -0,0 +1,35 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: DatetimeTimezoneUTC
location:
row: 10
column: 6
end_location:
row: 10
column: 27
fix:
content: datetime.UTC
location:
row: 10
column: 6
end_location:
row: 10
column: 27
- kind: DatetimeTimezoneUTC
location:
row: 11
column: 6
end_location:
row: 11
column: 21
fix:
content: dt.UTC
location:
row: 11
column: 6
end_location:
row: 11
column: 21

View File

@@ -86,6 +86,11 @@ impl Resolver {
.unwrap_or(default),
}
}
/// Return an iterator over the resolved `Settings` in this `Resolver`.
pub fn iter(&self) -> impl Iterator<Item = &Settings> {
self.settings.values()
}
}
/// Recursively resolve a `Configuration` from a `pyproject.toml` file at the
@@ -164,8 +169,8 @@ pub fn resolve_settings(
/// Return `true` if the given file should be ignored based on the exclusion
/// criteria.
fn is_excluded(file_path: &str, file_basename: &str, exclude: &globset::GlobSet) -> bool {
exclude.is_match(file_path) || exclude.is_match(file_basename)
fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::GlobSet) -> bool {
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
}
/// Return `true` if the `Path` appears to be that of a Python file.
@@ -190,7 +195,7 @@ pub fn python_files_in_path(
overrides: &Overrides,
) -> Result<(Vec<Result<DirEntry, ignore::Error>>, Resolver)> {
// Normalize every path (e.g., convert from relative to absolute).
let paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
let mut paths: Vec<PathBuf> = paths.iter().map(fs::normalize_path).collect();
// Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default();
@@ -207,6 +212,14 @@ pub fn python_files_in_path(
}
}
// Check if the paths themselves are excluded.
if file_strategy.force_exclude {
paths.retain(|path| !is_file_excluded(path, &resolver, pyproject_strategy));
if paths.is_empty() {
return Ok((vec![], resolver));
}
}
// Create the `WalkBuilder`.
let mut builder = WalkBuilder::new(
paths
@@ -264,19 +277,23 @@ pub fn python_files_in_path(
// Respect our own exclusion behavior.
if let Ok(entry) = &result {
if file_strategy.force_exclude || entry.depth() > 0 {
if entry.depth() > 0 {
let path = entry.path();
let resolver = resolver.read().unwrap();
let settings = resolver.resolve(path, pyproject_strategy);
match fs::extract_path_names(path) {
Ok((file_path, file_basename)) => {
if !settings.exclude.is_empty()
&& is_excluded(file_path, file_basename, &settings.exclude)
&& match_exclusion(file_path, file_basename, &settings.exclude)
{
debug!("Ignored path via `exclude`: {:?}", path);
return WalkState::Skip;
} else if !settings.extend_exclude.is_empty()
&& is_excluded(file_path, file_basename, &settings.extend_exclude)
&& match_exclusion(
file_path,
file_basename,
&settings.extend_exclude,
)
{
debug!("Ignored path via `extend-exclude`: {:?}", path);
return WalkState::Skip;
@@ -303,6 +320,72 @@ pub fn python_files_in_path(
Ok((files.into_inner().unwrap(), resolver.into_inner().unwrap()))
}
/// Return `true` if the Python file at `Path` is _not_ excluded.
pub fn python_file_at_path(
path: &Path,
pyproject_strategy: &PyprojectDiscovery,
file_strategy: &FileDiscovery,
overrides: &Overrides,
) -> Result<bool> {
if !file_strategy.force_exclude {
return Ok(true);
}
// Normalize the path (e.g., convert from relative to absolute).
let path = fs::normalize_path(path);
// Search for `pyproject.toml` files in all parent directories.
let mut resolver = Resolver::default();
for ancestor in path.ancestors() {
let pyproject = ancestor.join("pyproject.toml");
if pyproject.is_file() {
if has_ruff_section(&pyproject)? {
let (root, settings) =
resolve_scoped_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
resolver.add(root, settings);
}
}
}
// Check exclusions.
Ok(!is_file_excluded(&path, &resolver, pyproject_strategy))
}
/// Return `true` if the given top-level `Path` should be excluded.
fn is_file_excluded(
path: &Path,
resolver: &Resolver,
pyproject_strategy: &PyprojectDiscovery,
) -> bool {
// TODO(charlie): Respect gitignore.
for path in path.ancestors() {
if path.file_name().is_none() {
break;
}
let settings = resolver.resolve(path, pyproject_strategy);
match fs::extract_path_names(path) {
Ok((file_path, file_basename)) => {
if !settings.exclude.is_empty()
&& match_exclusion(file_path, file_basename, &settings.exclude)
{
debug!("Ignored path via `exclude`: {:?}", path);
return true;
} else if !settings.extend_exclude.is_empty()
&& match_exclusion(file_path, file_basename, &settings.extend_exclude)
{
debug!("Ignored path via `extend-exclude`: {:?}", path);
return true;
}
}
Err(err) => {
debug!("Ignored path due to error in parsing: {:?}: {}", path, err);
return true;
}
}
}
false
}
#[cfg(test)]
mod tests {
use std::path::Path;
@@ -312,7 +395,7 @@ mod tests {
use path_absolutize::Absolutize;
use crate::fs;
use crate::resolver::{is_excluded, is_python_path};
use crate::resolver::{is_python_path, match_exclusion};
use crate::settings::types::FilePattern;
#[test]
@@ -349,7 +432,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -364,7 +447,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -381,7 +464,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -396,7 +479,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -413,7 +496,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -430,7 +513,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(is_excluded(
assert!(match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)
@@ -447,7 +530,7 @@ mod tests {
.to_path_buf(),
);
let (file_path, file_basename) = fs::extract_path_names(&path)?;
assert!(!is_excluded(
assert!(!match_exclusion(
file_path,
file_basename,
&make_exclusion(exclude,)

Some files were not shown because too many files have changed in this diff Show More