Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
605416922d | ||
|
|
7b81f36e54 | ||
|
|
ff63da9f52 | ||
|
|
d645a19e0a | ||
|
|
30ae0d3723 | ||
|
|
3fb9e76012 | ||
|
|
0f283ae98c | ||
|
|
269926cec4 | ||
|
|
28018442f6 | ||
|
|
abc9810e2b | ||
|
|
a20482961b | ||
|
|
d97c07818e | ||
|
|
7e92485f43 | ||
|
|
930c3be69d | ||
|
|
24d0a980c5 | ||
|
|
f5f0ed280a | ||
|
|
ca58c72fc9 | ||
|
|
c40f14620a | ||
|
|
04300ce258 | ||
|
|
ead5f948d3 | ||
|
|
e93e9fae82 | ||
|
|
f5ddec0fb3 | ||
|
|
3de2a57416 | ||
|
|
b29b4084ff | ||
|
|
c61ca4a953 | ||
|
|
58d5ac08a8 | ||
|
|
cc63a4be6a | ||
|
|
549a5d44bc | ||
|
|
d65ce6308b | ||
|
|
b988a268e4 | ||
|
|
1c3265ef98 | ||
|
|
8001a1639c | ||
|
|
7d9c1d7a5a | ||
|
|
c5cebb106e | ||
|
|
8c61e8a1ef | ||
|
|
4f338273a5 | ||
|
|
648191652d | ||
|
|
90558609c3 | ||
|
|
991d3c1ef6 | ||
|
|
f472fbc6d4 | ||
|
|
09b65a6449 | ||
|
|
9d2eced941 | ||
|
|
be0f6acb40 | ||
|
|
0c624af036 | ||
|
|
4ca328f964 | ||
|
|
07b5bf7030 | ||
|
|
f40ae943a7 | ||
|
|
8d46d3bfa6 | ||
|
|
4fb0c6e3ad | ||
|
|
ebfdefd110 | ||
|
|
11f06055a0 | ||
|
|
6a6a792562 |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.230
|
||||
rev: v0.0.233
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ If you're looking for a place to start, we recommend implementing a new lint rul
|
||||
pattern-match against the examples in the existing codebase. Many lint rules are inspired by
|
||||
existing Python plugins, which can be used as a reference implementation.
|
||||
|
||||
As a concrete example: consider taking on one of the rules in [`flake8-simplify`](https://github.com/charliermarsh/ruff/issues/998),
|
||||
and looking to the originating [Python source](https://github.com/MartinThoma/flake8-simplify) for
|
||||
guidance.
|
||||
As a concrete example: consider taking on one of the rules from the [`tryceratops`](https://github.com/charliermarsh/ruff/issues/2056)
|
||||
plugin, and looking to the originating [Python source](https://github.com/guilatrova/tryceratops)
|
||||
for guidance. [`flake8-simplify`](https://github.com/charliermarsh/ruff/issues/998) has a few rules
|
||||
left too.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
||||
63
Cargo.lock
generated
63
Cargo.lock
generated
@@ -719,7 +719,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -733,7 +733,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml_edit",
|
||||
"toml 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -984,6 +984,15 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -1633,7 +1642,7 @@ checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1828,7 +1837,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
@@ -1847,6 +1856,7 @@ dependencies = [
|
||||
"ignore",
|
||||
"imperative",
|
||||
"insta",
|
||||
"is_executable",
|
||||
"itertools",
|
||||
"js-sys",
|
||||
"libcst",
|
||||
@@ -1875,14 +1885,14 @@ dependencies = [
|
||||
"textwrap",
|
||||
"thiserror",
|
||||
"titlecase",
|
||||
"toml_edit",
|
||||
"toml 0.6.0",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1919,7 +1929,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.32",
|
||||
@@ -1940,7 +1950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
@@ -2200,6 +2210,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.0.0"
|
||||
@@ -2480,32 +2499,44 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.5.0"
|
||||
name = "toml"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808b51e57d0ef8f71115d8f3a01e7d3750d01c79cac4b3eda910f4389fdf92fd"
|
||||
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.17.1"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34cc558345efd7e88b9eda9626df2138b80bb46a7606f695e751c892bc7dac6"
|
||||
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"nom8",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ default-members = [".", "ruff_cli"]
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
@@ -46,7 +46,7 @@ num-traits = "0.2.15"
|
||||
once_cell = { version = "1.16.0" }
|
||||
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff_macros = { version = "0.0.230", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.233", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "4f38cb68e4a97aeea9eb19673803a0bd5f655383" }
|
||||
@@ -61,7 +61,7 @@ strum_macros = { version = "0.24.3" }
|
||||
textwrap = { version = "0.16.0" }
|
||||
thiserror = { version = "1.0" }
|
||||
titlecase = { version = "2.2.1" }
|
||||
toml_edit = { version = "0.17.1", features = ["easy"] }
|
||||
toml = { version = "0.6.0" }
|
||||
|
||||
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
|
||||
# For (future) wasm-pack support
|
||||
@@ -73,6 +73,9 @@ serde-wasm-bindgen = { version = "0.4" }
|
||||
js-sys = { version = "0.3.60" }
|
||||
wasm-bindgen = { version = "0.2.83" }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
is_executable = "1.0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
test-case = { version = "2.2.2" }
|
||||
|
||||
107
README.md
107
README.md
@@ -51,18 +51,22 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||
- [Apache Airflow](https://github.com/apache/airflow)
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
- [Dagster](https://github.com/dagster-io/dagster)
|
||||
- [Pydantic](https://github.com/pydantic/pydantic)
|
||||
- [Dagster](https://github.com/dagster-io/dagster)
|
||||
- [Dagger](https://github.com/dagger/dagger)
|
||||
- [Sphinx](https://github.com/sphinx-doc/sphinx)
|
||||
- [Hatch](https://github.com/pypa/hatch)
|
||||
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
|
||||
- [Synapse (Matrix)](https://github.com/matrix-org/synapse)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [Great Expectations](https://github.com/great-expectations/great_expectations)
|
||||
- [Polars](https://github.com/pola-rs/polars)
|
||||
- [Ibis](https://github.com/ibis-project/ibis)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- [Synapse (Matrix)](https://github.com/matrix-org/synapse)
|
||||
- [SnowCLI (Snowflake)](https://github.com/Snowflake-Labs/snowcli)
|
||||
- [Saleor](https://github.com/saleor/saleor)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [Home Assistant](https://github.com/home-assistant/core)
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- [cibuildwheel (PyPA)](https://github.com/pypa/cibuildwheel)
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -141,6 +145,7 @@ developer of [Zulip](https://github.com/zulip/zulip):
|
||||
1. [flake8-executable (EXE)](#flake8-executable-exe)
|
||||
1. [flake8-type-checking (TYP)](#flake8-type-checking-typ)
|
||||
1. [tryceratops (TRY)](#tryceratops-try)
|
||||
1. [flake8-use-pathlib (PTH)](#flake8-use-pathlib-pth)
|
||||
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
|
||||
1. [Editor Integrations](#editor-integrations)
|
||||
1. [FAQ](#faq)
|
||||
@@ -178,6 +183,12 @@ For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.o
|
||||
pacman -S ruff
|
||||
```
|
||||
|
||||
For **Alpine** users, Ruff is also available as [`ruff`](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/ruff) on the testing repositories:
|
||||
|
||||
```shell
|
||||
apk add ruff
|
||||
```
|
||||
|
||||
[](https://repology.org/project/ruff-python-linter/versions)
|
||||
|
||||
### Usage
|
||||
@@ -201,7 +212,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.0.230'
|
||||
rev: 'v0.0.233'
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -666,8 +677,8 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/) on PyPI.
|
||||
| D209 | new-line-after-last-paragraph | Multi-line docstring closing quotes should be on a separate line | 🛠 |
|
||||
| D210 | no-surrounding-whitespace | No whitespaces allowed surrounding docstring text | 🛠 |
|
||||
| D211 | no-blank-line-before-class | No blank lines allowed before class docstring | 🛠 |
|
||||
| D212 | multi-line-summary-first-line | Multi-line docstring summary should start at the first line | |
|
||||
| D213 | multi-line-summary-second-line | Multi-line docstring summary should start at the second line | |
|
||||
| D212 | multi-line-summary-first-line | Multi-line docstring summary should start at the first line | 🛠 |
|
||||
| D213 | multi-line-summary-second-line | Multi-line docstring summary should start at the second line | 🛠 |
|
||||
| D214 | section-not-over-indented | Section is over-indented ("{name}") | 🛠 |
|
||||
| D215 | section-underline-not-over-indented | Section underline is over-indented ("{name}") | 🛠 |
|
||||
| D300 | uses-triple-quotes | Use """triple double quotes""" | |
|
||||
@@ -810,6 +821,7 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/) on PyPI.
|
||||
| S506 | unsafe-yaml-load | Probable use of unsafe loader `{name}` with `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`. | |
|
||||
| S508 | snmp-insecure-version | The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. | |
|
||||
| S509 | snmp-weak-cryptography | You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. | |
|
||||
| S612 | logging-config-insecure-listen | Use of insecure `logging.config.listen` detected | |
|
||||
| S701 | jinja2-autoescape-false | Using jinja2 templates with `autoescape=False` is dangerous and can lead to XSS. Ensure `autoescape=True` or use the `select_autoescape` function. | |
|
||||
|
||||
### flake8-blind-except (BLE)
|
||||
@@ -1150,7 +1162,9 @@ For more, see [flake8-pie](https://pypi.org/project/flake8-pie/) on PyPI.
|
||||
| PIE790 | no-unnecessary-pass | Unnecessary `pass` statement | 🛠 |
|
||||
| PIE794 | dupe-class-field-definitions | Class field `{name}` is defined multiple times | 🛠 |
|
||||
| PIE796 | prefer-unique-enums | Enum contains duplicate value: `{value}` | |
|
||||
| PIE807 | prefer-list-builtin | Prefer `list()` over useless lambda | 🛠 |
|
||||
| PIE800 | no-unnecessary-spread | Unnecessary spread `**` | |
|
||||
| PIE804 | no-unnecessary-dict-kwargs | Unnecessary `dict` kwargs | |
|
||||
| PIE807 | prefer-list-builtin | Prefer `list` over useless lambda | 🛠 |
|
||||
|
||||
### flake8-commas (COM)
|
||||
|
||||
@@ -1176,6 +1190,8 @@ For more, see [flake8-executable](https://pypi.org/project/flake8-executable/) o
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| EXE001 | shebang-not-executable | Shebang is present but file is not executable | |
|
||||
| EXE002 | shebang-missing-executable-file | The file is executable but no shebang is present | |
|
||||
| EXE003 | shebang-python | Shebang should contain "python" | |
|
||||
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
|
||||
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
|
||||
@@ -1195,7 +1211,42 @@ For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| TRY004 | prefer-type-error | Prefer `TypeError` exception for invalid type | 🛠 |
|
||||
| TRY200 | reraise-no-cause | Use `raise from` to specify exception cause | |
|
||||
| TRY201 | verbose-raise | Use `raise` without specifying exception name | |
|
||||
| TRY300 | try-consider-else | Consider `else` block | |
|
||||
| TRY301 | raise-within-try | Abstract `raise` to an inner function | |
|
||||
|
||||
### flake8-use-pathlib (PTH)
|
||||
|
||||
For more, see [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| PTH100 | pathlib-abspath | `os.path.abspath` should be replaced by `.resolve()` | |
|
||||
| PTH101 | pathlib-chmod | `os.chmod` should be replaced by `.chmod()` | |
|
||||
| PTH102 | pathlib-mkdir | `os.mkdir` should be replaced by `.mkdir()` | |
|
||||
| PTH103 | pathlib-makedirs | `os.makedirs` should be replaced by `.mkdir(parents=True)` | |
|
||||
| PTH104 | pathlib-rename | `os.rename` should be replaced by `.rename()` | |
|
||||
| PTH105 | pathlib-replace | `os.replace`should be replaced by `.replace()` | |
|
||||
| PTH106 | pathlib-rmdir | `os.rmdir` should be replaced by `.rmdir()` | |
|
||||
| PTH107 | pathlib-remove | `os.remove` should be replaced by `.unlink()` | |
|
||||
| PTH108 | pathlib-unlink | `os.unlink` should be replaced by `.unlink()` | |
|
||||
| PTH109 | pathlib-getcwd | `os.getcwd()` should be replaced by `Path.cwd()` | |
|
||||
| PTH110 | pathlib-exists | `os.path.exists` should be replaced by `.exists()` | |
|
||||
| PTH111 | pathlib-expanduser | `os.path.expanduser` should be replaced by `.expanduser()` | |
|
||||
| PTH112 | pathlib-is-dir | `os.path.isdir` should be replaced by `.is_dir()` | |
|
||||
| PTH113 | pathlib-is-file | `os.path.isfile` should be replaced by `.is_file()` | |
|
||||
| PTH114 | pathlib-is-link | `os.path.islink` should be replaced by `.is_symlink()` | |
|
||||
| PTH115 | pathlib-readlink | `os.readlink(` should be replaced by `.readlink()` | |
|
||||
| PTH116 | pathlib-stat | `os.stat` should be replaced by `.stat()` or `.owner()` or `.group()` | |
|
||||
| PTH117 | pathlib-is-abs | `os.path.isabs` should be replaced by `.is_absolute()` | |
|
||||
| PTH118 | pathlib-join | `os.path.join` should be replaced by foo_path / "bar" | |
|
||||
| PTH119 | pathlib-basename | `os.path.basename` should be replaced by `.name` | |
|
||||
| PTH120 | pathlib-dirname | `os.path.dirname` should be replaced by `.parent` | |
|
||||
| PTH121 | pathlib-samefile | `os.path.samefile` should be replaced by `.samefile()` | |
|
||||
| PTH122 | pathlib-splitext | `os.path.splitext` should be replaced by `.suffix` | |
|
||||
| PTH123 | pathlib-open | `open("foo")` should be replaced by`Path("foo").open()` | |
|
||||
| PTH124 | pathlib-py-path | `py.path` is in maintenance mode, use `pathlib` instead | |
|
||||
|
||||
### Ruff-specific rules (RUF)
|
||||
|
||||
@@ -1602,12 +1653,15 @@ project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
|
||||
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
|
||||
|
||||
Ruff's import sorting is intended to be nearly equivalent to `isort` when used `profile = "black"`.
|
||||
(There are some minor differences in how Ruff and isort break ties between similar imports.)
|
||||
There are a few known, minor differences in how Ruff and isort break ties between similar imports,
|
||||
and in how Ruff and isort treat inline comments in some cases (see: [#1381](https://github.com/charliermarsh/ruff/issues/1381),
|
||||
[#2104](https://github.com/charliermarsh/ruff/issues/2104)).
|
||||
|
||||
Like `isort`, Ruff's import sorting is compatible with Black.
|
||||
|
||||
Ruff is less configurable than `isort`, but supports the `known-first-party`, `known-third-party`,
|
||||
`extra-standard-library`, and `src` settings, like so:
|
||||
Ruff does not yet support all of `isort`'s configuration options, though it does support many of
|
||||
them. You can find the supported settings in the [API reference](#isort). For example, you can set
|
||||
`known-first-party` like so:
|
||||
|
||||
```toml
|
||||
[tool.ruff]
|
||||
@@ -2187,10 +2241,9 @@ 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`).
|
||||
imports will still be flagged, but with a dedicated message suggesting
|
||||
that the import is either added to the module's `__all__` symbol, or
|
||||
re-exported with a redundant alias (e.g., `import os as os`).
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
@@ -2643,6 +2696,28 @@ max-string-length = 20
|
||||
|
||||
---
|
||||
|
||||
### `flake8-implicit-str-concat`
|
||||
|
||||
#### [`allow-multiline`](#allow-multiline)
|
||||
|
||||
Whether to allow implicit string concatenations for multiline strings.
|
||||
By default, implicit concatenations of multiline strings are
|
||||
allowed (but continuation lines, delimited with a backslash, are
|
||||
prohibited).
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml
|
||||
[tool.ruff.flake8-implicit-str-concat]
|
||||
allow-multiline = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `flake8-import-conventions`
|
||||
|
||||
#### [`aliases`](#aliases)
|
||||
|
||||
84
build.rs
84
build.rs
@@ -1,84 +0,0 @@
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
|
||||
generate_linter_name_and_url(&out_dir);
|
||||
}
|
||||
|
||||
const RULES_SUBMODULE_DOC_PREFIX: &str = "//! Rules from ";
|
||||
|
||||
/// The `src/rules/*/mod.rs` files are expected to have a first line such as the
|
||||
/// following:
|
||||
///
|
||||
/// //! Rules from [Pyflakes](https://pypi.org/project/pyflakes/).
|
||||
///
|
||||
/// This function extracts the link label and url from these comments and
|
||||
/// generates the `name` and `url` functions for the `Linter` enum
|
||||
/// accordingly, so that they can be used by `ruff_dev::generate_rules_table`.
|
||||
fn generate_linter_name_and_url(out_dir: &Path) {
|
||||
println!("cargo:rerun-if-changed=src/rules/");
|
||||
|
||||
let mut name_match_arms: String = r#"Linter::Ruff => "Ruff-specific rules","#.into();
|
||||
let mut url_match_arms: String = r#"Linter::Ruff => None,"#.into();
|
||||
|
||||
for file in fs::read_dir("src/rules/")
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.filter(|f| f.file_type().unwrap().is_dir() && f.file_name() != "ruff")
|
||||
{
|
||||
let mod_rs_path = file.path().join("mod.rs");
|
||||
let mod_rs_path = mod_rs_path.to_str().unwrap();
|
||||
let first_line = BufReader::new(fs::File::open(mod_rs_path).unwrap())
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let Some(comment) = first_line.strip_prefix(RULES_SUBMODULE_DOC_PREFIX) else {
|
||||
panic!("expected first line in {mod_rs_path} to start with `{RULES_SUBMODULE_DOC_PREFIX}`")
|
||||
};
|
||||
let md_link = comment.trim_end_matches('.');
|
||||
|
||||
let (name, url) = md_link
|
||||
.strip_prefix('[')
|
||||
.unwrap()
|
||||
.strip_suffix(')')
|
||||
.unwrap()
|
||||
.split_once("](")
|
||||
.unwrap();
|
||||
|
||||
let dirname = file.file_name();
|
||||
let dirname = dirname.to_str().unwrap();
|
||||
|
||||
let variant_name = dirname
|
||||
.split('_')
|
||||
.map(|part| match part {
|
||||
"errmsg" => "ErrMsg".to_string(),
|
||||
"mccabe" => "McCabe".to_string(),
|
||||
"pep8" => "PEP8".to_string(),
|
||||
_ => format!("{}{}", part[..1].to_uppercase(), &part[1..]),
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
name_match_arms.push_str(&format!(r#"Linter::{variant_name} => "{name}","#));
|
||||
url_match_arms.push_str(&format!(r#"Linter::{variant_name} => Some("{url}"),"#));
|
||||
}
|
||||
|
||||
write!(
|
||||
BufWriter::new(fs::File::create(out_dir.join("linter.rs")).unwrap()),
|
||||
"
|
||||
impl Linter {{
|
||||
pub fn name(&self) -> &'static str {{
|
||||
match self {{ {name_match_arms} }}
|
||||
}}
|
||||
|
||||
pub fn url(&self) -> Option<&'static str> {{
|
||||
match self {{ {url_match_arms} }}
|
||||
}}
|
||||
}}
|
||||
"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -16,7 +16,7 @@ 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_edit = { version = "0.17.1", features = ["easy"] }
|
||||
toml = { version = "0.6.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ fn main() -> Result<()> {
|
||||
|
||||
// Create Ruff's pyproject.toml section.
|
||||
let pyproject = flake8_to_ruff::convert(&config, &external_config, cli.plugin)?;
|
||||
println!("{}", toml_edit::easy::to_string_pretty(&pyproject)?);
|
||||
println!("{}", toml::to_string_pretty(&pyproject)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
22
licenses/LICENSE_RoPP_flake8-use-pathlib
Normal file
22
licenses/LICENSE_RoPP_flake8-use-pathlib
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rodolphe Pelloux-Prayer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [
|
||||
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },
|
||||
|
||||
8
resources/test/fixtures/flake8_bandit/S612.py
vendored
Normal file
8
resources/test/fixtures/flake8_bandit/S612.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import logging.config
|
||||
|
||||
t = logging.config.listen(9999)
|
||||
|
||||
def verify_func():
|
||||
pass
|
||||
|
||||
l = logging.config.listen(9999, verify=verify_func)
|
||||
4
resources/test/fixtures/flake8_executable/EXE001_1.py
vendored
Normal file
4
resources/test/fixtures/flake8_executable/EXE001_1.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE001_2.py
vendored
Normal file
2
resources/test/fixtures/flake8_executable/EXE001_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
4
resources/test/fixtures/flake8_executable/EXE001_3.py
vendored
Executable file
4
resources/test/fixtures/flake8_executable/EXE001_3.py
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE002_1.py
vendored
Executable file
2
resources/test/fixtures/flake8_executable/EXE002_1.py
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
2
resources/test/fixtures/flake8_executable/EXE002_2.py
vendored
Normal file
2
resources/test/fixtures/flake8_executable/EXE002_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
4
resources/test/fixtures/flake8_executable/EXE002_3.py
vendored
Executable file
4
resources/test/fixtures/flake8_executable/EXE002_3.py
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('I should be executable.')
|
||||
0
resources/test/fixtures/flake8_executable/EXE003.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE003.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE004_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_1.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_2.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_2.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_3.py
vendored
Normal file → Executable file
0
resources/test/fixtures/flake8_executable/EXE005_3.py
vendored
Normal file → Executable file
17
resources/test/fixtures/flake8_pie/PIE800.py
vendored
Normal file
17
resources/test/fixtures/flake8_pie/PIE800.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{"foo": 1, **{"bar": 1}} # PIE800
|
||||
|
||||
foo({**foo, **{"bar": True}}) # PIE800
|
||||
|
||||
{**foo, **{"bar": 10}} # PIE800
|
||||
|
||||
{**foo, **buzz, **{bar: 10}} # PIE800
|
||||
|
||||
{**foo, "bar": True } # OK
|
||||
|
||||
{"foo": 1, "buzz": {"bar": 1}} # OK
|
||||
|
||||
{**foo, "bar": True } # OK
|
||||
|
||||
Table.objects.filter(inst=inst, **{f"foo__{bar}__exists": True}) # OK
|
||||
|
||||
buzz = {**foo, "bar": { 1: 2 }} # OK
|
||||
19
resources/test/fixtures/flake8_pie/PIE804.py
vendored
Normal file
19
resources/test/fixtures/flake8_pie/PIE804.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
foo(**{"bar": True}) # PIE804
|
||||
|
||||
foo(**{"r2d2": True}) # PIE804
|
||||
|
||||
Foo.objects.create(**{"bar": True}) # PIE804
|
||||
|
||||
Foo.objects.create(**{"_id": some_id}) # PIE804
|
||||
|
||||
Foo.objects.create(**{**bar}) # PIE804
|
||||
|
||||
|
||||
foo(**{**data, "foo": "buzz"})
|
||||
foo(**buzz)
|
||||
foo(**{"bar-foo": True})
|
||||
foo(**{"bar foo": True})
|
||||
foo(**{"1foo": True})
|
||||
foo(**{buzz: True})
|
||||
foo(**{"": True})
|
||||
foo(**{f"buzz__{bar}": True})
|
||||
@@ -114,3 +114,12 @@ def bar3(x, y, z):
|
||||
else:
|
||||
return z
|
||||
return None
|
||||
|
||||
|
||||
def prompts(self, foo):
|
||||
if not foo:
|
||||
return []
|
||||
|
||||
for x in foo:
|
||||
yield x
|
||||
yield x + 1
|
||||
|
||||
@@ -92,3 +92,40 @@ if node.module:
|
||||
"multiprocessing."
|
||||
):
|
||||
print("Bad module!")
|
||||
|
||||
|
||||
# OK
|
||||
if a:
|
||||
if b:
|
||||
print("foo")
|
||||
else:
|
||||
print("bar")
|
||||
|
||||
# OK
|
||||
if a:
|
||||
if b:
|
||||
if c:
|
||||
print("foo")
|
||||
else:
|
||||
print("bar")
|
||||
else:
|
||||
print("bar")
|
||||
|
||||
# OK
|
||||
if a:
|
||||
# SIM 102
|
||||
if b:
|
||||
if c:
|
||||
print("foo")
|
||||
else:
|
||||
print("bar")
|
||||
|
||||
|
||||
# OK
|
||||
if a:
|
||||
if b:
|
||||
if c:
|
||||
print("foo")
|
||||
print("baz")
|
||||
else:
|
||||
print("bar")
|
||||
|
||||
31
resources/test/fixtures/flake8_use_pathlib/full_name.py
vendored
Normal file
31
resources/test/fixtures/flake8_use_pathlib/full_name.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import os.path
|
||||
|
||||
p = "/foo"
|
||||
|
||||
a = os.path.abspath(p)
|
||||
aa = os.chmod(p)
|
||||
aaa = os.mkdir(p)
|
||||
os.makedirs(p)
|
||||
os.rename(p)
|
||||
os.replace(p)
|
||||
os.rmdir(p)
|
||||
os.remove(p)
|
||||
os.unlink(p)
|
||||
os.getcwd(p)
|
||||
b = os.path.exists(p)
|
||||
bb = os.path.expanduser(p)
|
||||
bbb = os.path.isdir(p)
|
||||
bbbb = os.path.isfile(p)
|
||||
bbbbb = os.path.islink(p)
|
||||
os.readlink(p)
|
||||
os.stat(p)
|
||||
os.path.isabs(p)
|
||||
os.path.join(p)
|
||||
os.path.basename(p)
|
||||
os.path.dirname(p)
|
||||
os.path.samefile(p)
|
||||
os.path.splitext(p)
|
||||
with open(p) as fp:
|
||||
fp.read()
|
||||
open(p).close()
|
||||
28
resources/test/fixtures/flake8_use_pathlib/import_as.py
vendored
Normal file
28
resources/test/fixtures/flake8_use_pathlib/import_as.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import os as foo
|
||||
import os.path as foo_p
|
||||
|
||||
p = "/foo"
|
||||
|
||||
a = foo_p.abspath(p)
|
||||
aa = foo.chmod(p)
|
||||
aaa = foo.mkdir(p)
|
||||
foo.makedirs(p)
|
||||
foo.rename(p)
|
||||
foo.replace(p)
|
||||
foo.rmdir(p)
|
||||
foo.remove(p)
|
||||
foo.unlink(p)
|
||||
foo.getcwd(p)
|
||||
b = foo_p.exists(p)
|
||||
bb = foo_p.expanduser(p)
|
||||
bbb = foo_p.isdir(p)
|
||||
bbbb = foo_p.isfile(p)
|
||||
bbbbb = foo_p.islink(p)
|
||||
foo.readlink(p)
|
||||
foo.stat(p)
|
||||
foo_p.isabs(p)
|
||||
foo_p.join(p)
|
||||
foo_p.basename(p)
|
||||
foo_p.dirname(p)
|
||||
foo_p.samefile(p)
|
||||
foo_p.splitext(p)
|
||||
33
resources/test/fixtures/flake8_use_pathlib/import_from.py
vendored
Normal file
33
resources/test/fixtures/flake8_use_pathlib/import_from.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
from os import chmod, mkdir, makedirs, rename, replace, rmdir
|
||||
from os import remove, unlink, getcwd, readlink, stat
|
||||
from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
|
||||
p = "/foo"
|
||||
|
||||
a = abspath(p)
|
||||
aa = chmod(p)
|
||||
aaa = mkdir(p)
|
||||
makedirs(p)
|
||||
rename(p)
|
||||
replace(p)
|
||||
rmdir(p)
|
||||
remove(p)
|
||||
unlink(p)
|
||||
getcwd(p)
|
||||
b = exists(p)
|
||||
bb = expanduser(p)
|
||||
bbb = isdir(p)
|
||||
bbbb = isfile(p)
|
||||
bbbbb = islink(p)
|
||||
readlink(p)
|
||||
stat(p)
|
||||
isabs(p)
|
||||
join(p)
|
||||
basename(p)
|
||||
dirname(p)
|
||||
samefile(p)
|
||||
splitext(p)
|
||||
with open(p) as fp:
|
||||
fp.read()
|
||||
open(p).close()
|
||||
35
resources/test/fixtures/flake8_use_pathlib/import_from_as.py
vendored
Normal file
35
resources/test/fixtures/flake8_use_pathlib/import_from_as.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
from os import chmod as xchmod, mkdir as xmkdir
|
||||
from os import makedirs as xmakedirs, rename as xrename, replace as xreplace
|
||||
from os import rmdir as xrmdir, remove as xremove, unlink as xunlink
|
||||
from os import getcwd as xgetcwd, readlink as xreadlink, stat as xstat
|
||||
from os.path import abspath as xabspath, exists as xexists
|
||||
from os.path import expanduser as xexpanduser, isdir as xisdir
|
||||
from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
|
||||
p = "/foo"
|
||||
|
||||
a = xabspath(p)
|
||||
aa = xchmod(p)
|
||||
aaa = xmkdir(p)
|
||||
xmakedirs(p)
|
||||
xrename(p)
|
||||
xreplace(p)
|
||||
xrmdir(p)
|
||||
xremove(p)
|
||||
xunlink(p)
|
||||
xgetcwd(p)
|
||||
b = xexists(p)
|
||||
bb = xexpanduser(p)
|
||||
bbb = xisdir(p)
|
||||
bbbb = xisfile(p)
|
||||
bbbbb = xislink(p)
|
||||
xreadlink(p)
|
||||
xstat(p)
|
||||
xisabs(p)
|
||||
xjoin(p)
|
||||
xbasename(p)
|
||||
xdirname(p)
|
||||
xsamefile(p)
|
||||
xsplitext(p)
|
||||
3
resources/test/fixtures/flake8_use_pathlib/py_path_1.py
vendored
Normal file
3
resources/test/fixtures/flake8_use_pathlib/py_path_1.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import py
|
||||
|
||||
p = py.path.local("../foo")
|
||||
3
resources/test/fixtures/flake8_use_pathlib/py_path_2.py
vendored
Normal file
3
resources/test/fixtures/flake8_use_pathlib/py_path_2.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from py.path import local as path
|
||||
|
||||
p = path("/foo")
|
||||
60
resources/test/fixtures/pydocstyle/D401.py
vendored
60
resources/test/fixtures/pydocstyle/D401.py
vendored
@@ -1,3 +1,7 @@
|
||||
"""This module docstring does not need to be written in imperative mood."""
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
# Bad examples
|
||||
|
||||
def bad_liouiwnlkjl():
|
||||
@@ -18,7 +22,11 @@ def bad_sdgfsdg23245777():
|
||||
|
||||
def bad_run_something():
|
||||
"""Runs something"""
|
||||
pass
|
||||
|
||||
def bad_nested():
|
||||
"""Runs other things, nested"""
|
||||
|
||||
bad_nested()
|
||||
|
||||
|
||||
def multi_line():
|
||||
@@ -32,6 +40,11 @@ def multi_line():
|
||||
def good_run_something():
|
||||
"""Run away."""
|
||||
|
||||
def good_nested():
|
||||
"""Run to the hills."""
|
||||
|
||||
good_nested()
|
||||
|
||||
|
||||
def good_construct():
|
||||
"""Construct a beautiful house."""
|
||||
@@ -41,3 +54,48 @@ def good_multi_line():
|
||||
"""Write a logical line that
|
||||
extends to two physical lines.
|
||||
"""
|
||||
|
||||
|
||||
good_top_level_var = False
|
||||
"""This top level assignment attribute docstring does not need to be written in imperative mood."""
|
||||
|
||||
|
||||
# Classes and their members
|
||||
|
||||
class Thingy:
|
||||
"""This class docstring does not need to be written in imperative mood."""
|
||||
|
||||
_beep = "boop"
|
||||
"""This class attribute docstring does not need to be written in imperative mood."""
|
||||
|
||||
def bad_method(self):
|
||||
"""This method docstring should be written in imperative mood."""
|
||||
|
||||
@property
|
||||
def good_property(self):
|
||||
"""This property method docstring does not need to be written in imperative mood."""
|
||||
return self._beep
|
||||
|
||||
@cached_property
|
||||
def good_cached_property(self):
|
||||
"""This property method docstring does not need to be written in imperative mood."""
|
||||
return 42 * 42
|
||||
|
||||
class NestedThingy:
|
||||
"""This nested class docstring does not need to be written in imperative mood."""
|
||||
|
||||
|
||||
# Test functions
|
||||
|
||||
def test_something():
|
||||
"""This test function does not need to be written in imperative mood.
|
||||
|
||||
pydocstyle's rationale:
|
||||
We exclude tests from the imperative mood check, because to phrase
|
||||
their docstring in the imperative mood, they would have to start with
|
||||
a highly redundant "Test that ..."
|
||||
"""
|
||||
|
||||
|
||||
def runTest():
|
||||
"""This test function does not need to be written in imperative mood, either."""
|
||||
|
||||
2
resources/test/fixtures/pyupgrade/UP032.py
vendored
2
resources/test/fixtures/pyupgrade/UP032.py
vendored
@@ -44,6 +44,8 @@ print("foo {} ".format(x))
|
||||
|
||||
"{}".format(a)
|
||||
|
||||
'({}={{0!e}})'.format(a)
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
|
||||
27
resources/test/fixtures/tryceratops/TRY200.py
vendored
Normal file
27
resources/test/fixtures/tryceratops/TRY200.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MainFunctionFailed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def process():
|
||||
raise MyException
|
||||
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except MyException:
|
||||
raise MainFunctionFailed()
|
||||
|
||||
if True:
|
||||
raise MainFunctionFailed()
|
||||
|
||||
|
||||
def good():
|
||||
try:
|
||||
process()
|
||||
except MyException as ex:
|
||||
raise MainFunctionFailed() from ex
|
||||
54
resources/test/fixtures/tryceratops/TRY201.py
vendored
Normal file
54
resources/test/fixtures/tryceratops/TRY201.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Violation:
|
||||
|
||||
Raising an exception using its assigned name is verbose and unrequired
|
||||
"""
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def bad():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
logger.exception("process failed")
|
||||
raise e
|
||||
|
||||
|
||||
def good():
|
||||
try:
|
||||
process()
|
||||
except MyException:
|
||||
logger.exception("process failed")
|
||||
raise
|
||||
|
||||
def still_good():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
print(e)
|
||||
raise
|
||||
|
||||
|
||||
def bad_that_needs_recursion():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
logger.exception("process failed")
|
||||
if True:
|
||||
raise e
|
||||
|
||||
|
||||
def bad_that_needs_recursion_2():
|
||||
try:
|
||||
process()
|
||||
except MyException as e:
|
||||
logger.exception("process failed")
|
||||
if True:
|
||||
def foo():
|
||||
raise e
|
||||
29
resources/test/fixtures/tryceratops/TRY301.py
vendored
Normal file
29
resources/test/fixtures/tryceratops/TRY301.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
class MyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def bad():
|
||||
try:
|
||||
a = process()
|
||||
if not a:
|
||||
raise MyException(a)
|
||||
|
||||
raise MyException(a)
|
||||
|
||||
try:
|
||||
b = process()
|
||||
if not b:
|
||||
raise MyException(b)
|
||||
except Exception:
|
||||
logger.exception("something failed")
|
||||
except Exception:
|
||||
logger.exception("something failed")
|
||||
|
||||
|
||||
def good():
|
||||
try:
|
||||
a = process() # This throws the exception now
|
||||
except MyException:
|
||||
logger.exception("a failed")
|
||||
except Exception:
|
||||
logger.exception("something failed")
|
||||
131
ruff.schema.json
131
ruff.schema.json
@@ -175,6 +175,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"flake8-implicit-str-concat": {
|
||||
"description": "Options for the `flake8-implicit-str-concat` plugin.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Flake8ImplicitStrConcatOptions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"flake8-import-conventions": {
|
||||
"description": "Options for the `flake8-import-conventions` plugin.",
|
||||
"anyOf": [
|
||||
@@ -259,7 +270,7 @@
|
||||
}
|
||||
},
|
||||
"ignore-init-module-imports": {
|
||||
"description": "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`).",
|
||||
"description": "Avoid automatically removing unused imports in `__init__.py` files. Such imports will still be flagged, but with a dedicated message suggesting that the import is either added to the module's `__all__` symbol, or re-exported with a redundant alias (e.g., `import os as os`).",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
@@ -626,6 +637,19 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Flake8ImplicitStrConcatOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow-multiline": {
|
||||
"description": "Whether to allow implicit string concatenations for multiline strings. By default, implicit concatenations of multiline strings are allowed (but continuation lines, delimited with a backslash, are prohibited).",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Flake8ImportConventionsOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1164,13 +1188,13 @@
|
||||
"RuleSelector": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ALL",
|
||||
"A",
|
||||
"A0",
|
||||
"A00",
|
||||
"A001",
|
||||
"A002",
|
||||
"A003",
|
||||
"ALL",
|
||||
"ANN",
|
||||
"ANN0",
|
||||
"ANN00",
|
||||
@@ -1381,6 +1405,8 @@
|
||||
"EXE",
|
||||
"EXE0",
|
||||
"EXE00",
|
||||
"EXE001",
|
||||
"EXE002",
|
||||
"EXE003",
|
||||
"EXE004",
|
||||
"EXE005",
|
||||
@@ -1458,15 +1484,6 @@
|
||||
"I00",
|
||||
"I001",
|
||||
"I002",
|
||||
"I2",
|
||||
"I25",
|
||||
"I252",
|
||||
"IC",
|
||||
"IC0",
|
||||
"IC001",
|
||||
"IC002",
|
||||
"IC003",
|
||||
"IC004",
|
||||
"ICN",
|
||||
"ICN0",
|
||||
"ICN00",
|
||||
@@ -1481,9 +1498,6 @@
|
||||
"ISC001",
|
||||
"ISC002",
|
||||
"ISC003",
|
||||
"M",
|
||||
"M0",
|
||||
"M001",
|
||||
"N",
|
||||
"N8",
|
||||
"N80",
|
||||
@@ -1521,23 +1535,6 @@
|
||||
"PD9",
|
||||
"PD90",
|
||||
"PD901",
|
||||
"PDV",
|
||||
"PDV0",
|
||||
"PDV002",
|
||||
"PDV003",
|
||||
"PDV004",
|
||||
"PDV007",
|
||||
"PDV008",
|
||||
"PDV009",
|
||||
"PDV01",
|
||||
"PDV010",
|
||||
"PDV011",
|
||||
"PDV012",
|
||||
"PDV013",
|
||||
"PDV015",
|
||||
"PDV9",
|
||||
"PDV90",
|
||||
"PDV901",
|
||||
"PGH",
|
||||
"PGH0",
|
||||
"PGH00",
|
||||
@@ -1553,6 +1550,8 @@
|
||||
"PIE796",
|
||||
"PIE8",
|
||||
"PIE80",
|
||||
"PIE800",
|
||||
"PIE804",
|
||||
"PIE807",
|
||||
"PL",
|
||||
"PLC",
|
||||
@@ -1633,6 +1632,36 @@
|
||||
"PT024",
|
||||
"PT025",
|
||||
"PT026",
|
||||
"PTH",
|
||||
"PTH1",
|
||||
"PTH10",
|
||||
"PTH100",
|
||||
"PTH101",
|
||||
"PTH102",
|
||||
"PTH103",
|
||||
"PTH104",
|
||||
"PTH105",
|
||||
"PTH106",
|
||||
"PTH107",
|
||||
"PTH108",
|
||||
"PTH109",
|
||||
"PTH11",
|
||||
"PTH110",
|
||||
"PTH111",
|
||||
"PTH112",
|
||||
"PTH113",
|
||||
"PTH114",
|
||||
"PTH115",
|
||||
"PTH116",
|
||||
"PTH117",
|
||||
"PTH118",
|
||||
"PTH119",
|
||||
"PTH12",
|
||||
"PTH120",
|
||||
"PTH121",
|
||||
"PTH122",
|
||||
"PTH123",
|
||||
"PTH124",
|
||||
"Q",
|
||||
"Q0",
|
||||
"Q00",
|
||||
@@ -1640,17 +1669,6 @@
|
||||
"Q001",
|
||||
"Q002",
|
||||
"Q003",
|
||||
"R",
|
||||
"R5",
|
||||
"R50",
|
||||
"R501",
|
||||
"R502",
|
||||
"R503",
|
||||
"R504",
|
||||
"R505",
|
||||
"R506",
|
||||
"R507",
|
||||
"R508",
|
||||
"RET",
|
||||
"RET5",
|
||||
"RET50",
|
||||
@@ -1695,6 +1713,9 @@
|
||||
"S506",
|
||||
"S508",
|
||||
"S509",
|
||||
"S6",
|
||||
"S61",
|
||||
"S612",
|
||||
"S7",
|
||||
"S70",
|
||||
"S701",
|
||||
@@ -1752,34 +1773,18 @@
|
||||
"TRY0",
|
||||
"TRY00",
|
||||
"TRY004",
|
||||
"TRY2",
|
||||
"TRY20",
|
||||
"TRY200",
|
||||
"TRY201",
|
||||
"TRY3",
|
||||
"TRY30",
|
||||
"TRY300",
|
||||
"TRY301",
|
||||
"TYP",
|
||||
"TYP0",
|
||||
"TYP00",
|
||||
"TYP005",
|
||||
"U",
|
||||
"U0",
|
||||
"U00",
|
||||
"U001",
|
||||
"U003",
|
||||
"U004",
|
||||
"U005",
|
||||
"U006",
|
||||
"U007",
|
||||
"U008",
|
||||
"U009",
|
||||
"U01",
|
||||
"U010",
|
||||
"U011",
|
||||
"U012",
|
||||
"U013",
|
||||
"U014",
|
||||
"U015",
|
||||
"U016",
|
||||
"U017",
|
||||
"U019",
|
||||
"UP",
|
||||
"UP0",
|
||||
"UP00",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -3,11 +3,12 @@ use std::path::PathBuf;
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use ruff::logging::LogLevel;
|
||||
use ruff::registry::{Rule, RuleSelector};
|
||||
use ruff::registry::Rule;
|
||||
use ruff::resolver::ConfigProcessor;
|
||||
use ruff::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use ruff::RuleSelector;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
|
||||
@@ -10,11 +10,8 @@ use std::{fs, process, str};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assert_cmd::Command;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
use ruff::logging::{set_up_logging, LogLevel};
|
||||
use ruff::registry::{Linter, RuleNamespace};
|
||||
use strum::IntoEnumIterator;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// Handles `blackd` process and allows submitting code to it for formatting.
|
||||
@@ -175,13 +172,6 @@ fn test_ruff_black_compatibility() -> Result<()> {
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
let codes = Linter::iter()
|
||||
// Exclude ruff codes, specifically RUF100, because it causes differences that are not a
|
||||
// problem. Ruff would add a `# noqa: W292` after the first run, black introduces a
|
||||
// newline, and ruff removes the `# noqa: W292` again.
|
||||
.filter(|linter| *linter != Linter::Ruff)
|
||||
.map(|linter| linter.prefixes().join(","))
|
||||
.join(",");
|
||||
let ruff_args = [
|
||||
"-",
|
||||
"--silent",
|
||||
@@ -189,8 +179,11 @@ fn test_ruff_black_compatibility() -> Result<()> {
|
||||
"--fix",
|
||||
"--line-length",
|
||||
"88",
|
||||
"--select",
|
||||
&codes,
|
||||
"--select ALL",
|
||||
// Exclude ruff codes, specifically RUF100, because it causes differences that are not a
|
||||
// problem. Ruff would add a `# noqa: W292` after the first run, black introduces a
|
||||
// newline, and ruff removes the `# noqa: W292` again.
|
||||
"--ignore RUF",
|
||||
];
|
||||
|
||||
for entry in paths {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.230"
|
||||
version = "0.0.233"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
|
||||
|
||||
let rulecodeprefix = super::rule_code_prefix::expand(
|
||||
&Ident::new("Rule", Span::call_site()),
|
||||
&Ident::new("RuleSelector", Span::call_site()),
|
||||
&Ident::new("RuleCodePrefix", Span::call_site()),
|
||||
mapping.entries.iter().map(|(code, ..)| code),
|
||||
|code| code_to_name[code],
|
||||
);
|
||||
|
||||
@@ -1,89 +1,9 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
const ALL: &str = "ALL";
|
||||
|
||||
/// A hash map from deprecated `RuleSelector` to latest
|
||||
/// `RuleSelector`.
|
||||
pub static PREFIX_REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
HashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U001", "UP001"),
|
||||
("U003", "UP003"),
|
||||
("U004", "UP004"),
|
||||
("U005", "UP005"),
|
||||
("U006", "UP006"),
|
||||
("U007", "UP007"),
|
||||
("U008", "UP008"),
|
||||
("U009", "UP009"),
|
||||
("U010", "UP010"),
|
||||
("U011", "UP011"),
|
||||
("U012", "UP012"),
|
||||
("U013", "UP013"),
|
||||
("U014", "UP014"),
|
||||
("U015", "UP015"),
|
||||
("U016", "UP016"),
|
||||
("U017", "UP017"),
|
||||
("U019", "UP019"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", "TID252"),
|
||||
("M001", "RUF100"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV002", "PD002"),
|
||||
("PDV003", "PD003"),
|
||||
("PDV004", "PD004"),
|
||||
("PDV007", "PD007"),
|
||||
("PDV008", "PD008"),
|
||||
("PDV009", "PD009"),
|
||||
("PDV010", "PD010"),
|
||||
("PDV011", "PD011"),
|
||||
("PDV012", "PD012"),
|
||||
("PDV013", "PD013"),
|
||||
("PDV015", "PD015"),
|
||||
("PDV901", "PD901"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R501", "RET501"),
|
||||
("R502", "RET502"),
|
||||
("R503", "RET503"),
|
||||
("R504", "RET504"),
|
||||
("R505", "RET505"),
|
||||
("R506", "RET506"),
|
||||
("R507", "RET507"),
|
||||
("R508", "RET508"),
|
||||
("IC001", "ICN001"),
|
||||
("IC002", "ICN001"),
|
||||
("IC003", "ICN001"),
|
||||
("IC004", "ICN001"),
|
||||
// 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"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV", "PD"),
|
||||
("PDV0", "PD0"),
|
||||
("PDV01", "PD01"),
|
||||
("PDV9", "PD9"),
|
||||
("PDV90", "PD90"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R", "RET"),
|
||||
("R5", "RET5"),
|
||||
("R50", "RET50"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("IC", "ICN"),
|
||||
("IC0", "ICN0"),
|
||||
])
|
||||
});
|
||||
|
||||
pub fn expand<'a>(
|
||||
rule_type: &Ident,
|
||||
prefix_ident: &Ident,
|
||||
@@ -116,20 +36,8 @@ pub fn expand<'a>(
|
||||
all_codes.insert(code_str);
|
||||
}
|
||||
|
||||
prefix_to_codes.insert(ALL.to_string(), all_codes);
|
||||
prefix_to_codes.insert("PL".to_string(), pl_codes);
|
||||
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
for (alias, rule_code) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
.get(*rule_code)
|
||||
.unwrap_or_else(|| panic!("Unknown RuleCode: {alias:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let prefix_variants = prefix_to_codes.keys().map(|prefix| {
|
||||
let prefix = Ident::new(prefix, Span::call_site());
|
||||
quote! {
|
||||
@@ -139,26 +47,9 @@ pub fn expand<'a>(
|
||||
|
||||
let prefix_impl = generate_impls(rule_type, prefix_ident, &prefix_to_codes, variant_name);
|
||||
|
||||
let prefix_redirects = PREFIX_REDIRECTS.iter().map(|(alias, rule_code)| {
|
||||
let code = Ident::new(rule_code, Span::call_site());
|
||||
quote! {
|
||||
(#alias, #prefix_ident::#code)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SuffixLength {
|
||||
None,
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
::strum_macros::EnumIter,
|
||||
::strum_macros::EnumString,
|
||||
::strum_macros::AsRefStr,
|
||||
Debug,
|
||||
@@ -167,22 +58,15 @@ pub fn expand<'a>(
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Clone,
|
||||
Hash,
|
||||
::serde::Serialize,
|
||||
::serde::Deserialize,
|
||||
::schemars::JsonSchema,
|
||||
)]
|
||||
pub enum #prefix_ident {
|
||||
#(#prefix_variants,)*
|
||||
}
|
||||
|
||||
#prefix_impl
|
||||
|
||||
/// A hash map from deprecated `RuleSelector` to latest `RuleSelector`.
|
||||
pub static PREFIX_REDIRECTS: ::once_cell::sync::Lazy<::rustc_hash::FxHashMap<&'static str, #prefix_ident>> = ::once_cell::sync::Lazy::new(|| {
|
||||
::rustc_hash::FxHashMap::from_iter([
|
||||
#(#prefix_redirects),*
|
||||
])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,64 +84,37 @@ fn generate_impls<'a>(
|
||||
}
|
||||
});
|
||||
let prefix = Ident::new(prefix_str, Span::call_site());
|
||||
if let Some(target) = PREFIX_REDIRECTS.get(prefix_str.as_str()) {
|
||||
quote! {
|
||||
#prefix_ident::#prefix => {
|
||||
crate::warn_user_once!(
|
||||
"`{}` has been remapped to `{}`", #prefix_str, #target
|
||||
);
|
||||
vec![#(#codes),*].into_iter()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#prefix_ident::#prefix => vec![#(#codes),*].into_iter(),
|
||||
}
|
||||
|
||||
quote! {
|
||||
#prefix_ident::#prefix => vec![#(#codes),*].into_iter(),
|
||||
}
|
||||
});
|
||||
|
||||
let specificity_match_arms = prefix_to_codes.keys().map(|prefix_str| {
|
||||
let prefix = Ident::new(prefix_str, Span::call_site());
|
||||
if prefix_str == ALL {
|
||||
quote! {
|
||||
#prefix_ident::#prefix => SuffixLength::None,
|
||||
}
|
||||
} else {
|
||||
let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count();
|
||||
if prefix_str != "PL" && prefix_str.starts_with("PL") {
|
||||
num_numeric += 1;
|
||||
}
|
||||
let suffix_len = match num_numeric {
|
||||
0 => quote! { SuffixLength::Zero },
|
||||
1 => quote! { SuffixLength::One },
|
||||
2 => quote! { SuffixLength::Two },
|
||||
3 => quote! { SuffixLength::Three },
|
||||
4 => quote! { SuffixLength::Four },
|
||||
5 => quote! { SuffixLength::Five },
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
};
|
||||
quote! {
|
||||
#prefix_ident::#prefix => #suffix_len,
|
||||
}
|
||||
let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count();
|
||||
if prefix_str != "PL" && prefix_str.starts_with("PL") {
|
||||
num_numeric += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let categories = prefix_to_codes.keys().map(|prefix_str| {
|
||||
if prefix_str.chars().all(char::is_alphabetic)
|
||||
&& !PREFIX_REDIRECTS.contains_key(&prefix_str.as_str())
|
||||
{
|
||||
let prefix = Ident::new(prefix_str, Span::call_site());
|
||||
quote! {
|
||||
#prefix_ident::#prefix,
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
let suffix_len = match num_numeric {
|
||||
0 => quote! { Specificity::Linter },
|
||||
1 => quote! { Specificity::Code1Char },
|
||||
2 => quote! { Specificity::Code2Chars },
|
||||
3 => quote! { Specificity::Code3Chars },
|
||||
4 => quote! { Specificity::Code4Chars },
|
||||
5 => quote! { Specificity::Code5Chars },
|
||||
_ => panic!("Invalid prefix: {prefix}"),
|
||||
};
|
||||
quote! {
|
||||
#prefix_ident::#prefix => #suffix_len,
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl #prefix_ident {
|
||||
pub fn specificity(&self) -> SuffixLength {
|
||||
pub(crate) fn specificity(&self) -> crate::rule_selector::Specificity {
|
||||
use crate::rule_selector::Specificity;
|
||||
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match self {
|
||||
#(#specificity_match_arms)*
|
||||
@@ -270,15 +127,11 @@ fn generate_impls<'a>(
|
||||
type IntoIter = ::std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
use colored::Colorize;
|
||||
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match self {
|
||||
#(#into_iter_match_arms)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const CATEGORIES: &[#prefix_ident] = &[#(#categories)*];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::quote;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Data, DataEnum, DeriveInput, Error, Lit, Meta, MetaNameValue};
|
||||
use syn::{Attribute, Data, DataEnum, DeriveInput, Error, Lit, Meta, MetaNameValue};
|
||||
|
||||
pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let DeriveInput { ident, data: Data::Enum(DataEnum {
|
||||
@@ -13,39 +15,62 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
||||
let mut parsed = Vec::new();
|
||||
|
||||
let mut prefix_match_arms = quote!();
|
||||
let mut name_match_arms = quote!(Self::Ruff => "Ruff-specific rules",);
|
||||
let mut url_match_arms = quote!(Self::Ruff => None,);
|
||||
|
||||
let mut all_prefixes = HashSet::new();
|
||||
|
||||
for variant in variants {
|
||||
let prefix_attrs: Vec<_> = variant
|
||||
let prefixes: Result<Vec<_>, _> = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|a| a.path.is_ident("prefix"))
|
||||
.map(|attr| {
|
||||
let Ok(Meta::NameValue(MetaNameValue{lit: Lit::Str(lit), ..})) = attr.parse_meta() else {
|
||||
return Err(Error::new(attr.span(), r#"expected attribute to be in the form of [#prefix = "..."]"#));
|
||||
};
|
||||
let str = lit.value();
|
||||
match str.chars().next() {
|
||||
None => return Err(Error::new(lit.span(), "expected prefix string to be non-empty")),
|
||||
Some(_) => {},
|
||||
}
|
||||
if !all_prefixes.insert(str.clone()) {
|
||||
return Err(Error::new(lit.span(), "prefix has already been defined before"));
|
||||
}
|
||||
Ok(str)
|
||||
})
|
||||
.collect();
|
||||
let prefixes = prefixes?;
|
||||
|
||||
if prefix_attrs.is_empty() {
|
||||
if prefixes.is_empty() {
|
||||
return Err(Error::new(
|
||||
variant.span(),
|
||||
r#"Missing [#prefix = "..."] attribute"#,
|
||||
r#"Missing #[prefix = "..."] attribute"#,
|
||||
));
|
||||
}
|
||||
|
||||
let mut prefix_literals = Vec::new();
|
||||
|
||||
for attr in prefix_attrs {
|
||||
let Ok(Meta::NameValue(MetaNameValue{lit: Lit::Str(lit), ..})) = attr.parse_meta() else {
|
||||
return Err(Error::new(attr.span(), r#"expected attribute to be in the form of [#prefix = "..."]"#))
|
||||
};
|
||||
parsed.push((lit.clone(), variant.ident.clone()));
|
||||
prefix_literals.push(lit);
|
||||
}
|
||||
let Some(doc_attr) = variant.attrs.iter().find(|a| a.path.is_ident("doc")) else {
|
||||
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
|
||||
};
|
||||
|
||||
let variant_ident = variant.ident;
|
||||
|
||||
if variant_ident != "Ruff" {
|
||||
let (name, url) = parse_doc_attr(doc_attr)?;
|
||||
name_match_arms.extend(quote! {Self::#variant_ident => #name,});
|
||||
url_match_arms.extend(quote! {Self::#variant_ident => Some(#url),});
|
||||
}
|
||||
|
||||
for lit in &prefixes {
|
||||
parsed.push((lit.clone(), variant_ident.clone()));
|
||||
}
|
||||
|
||||
prefix_match_arms.extend(quote! {
|
||||
Self::#variant_ident => &[#(#prefix_literals),*],
|
||||
Self::#variant_ident => &[#(#prefixes),*],
|
||||
});
|
||||
}
|
||||
|
||||
parsed.sort_by_key(|(prefix, _)| prefix.value().len());
|
||||
parsed.sort_by_key(|(prefix, _)| prefix.len());
|
||||
|
||||
let mut if_statements = quote!();
|
||||
let mut into_iter_match_arms = quote!();
|
||||
@@ -55,18 +80,18 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
||||
return Some((#ident::#field, rest));
|
||||
}});
|
||||
|
||||
let prefix_ident = Ident::new(&prefix.value(), Span::call_site());
|
||||
let prefix_ident = Ident::new(&prefix, Span::call_site());
|
||||
|
||||
if field != "Pycodestyle" {
|
||||
into_iter_match_arms.extend(quote! {
|
||||
#ident::#field => RuleSelector::#prefix_ident.into_iter(),
|
||||
#ident::#field => RuleCodePrefix::#prefix_ident.into_iter(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
into_iter_match_arms.extend(quote! {
|
||||
#ident::Pycodestyle => {
|
||||
let rules: Vec<_> = (&RuleSelector::E).into_iter().chain(&RuleSelector::W).collect();
|
||||
let rules: Vec<_> = (&RuleCodePrefix::E).into_iter().chain(&RuleCodePrefix::W).collect();
|
||||
rules.into_iter()
|
||||
}
|
||||
});
|
||||
@@ -82,6 +107,14 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
||||
fn prefixes(&self) -> &'static [&'static str] {
|
||||
match self { #prefix_match_arms }
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
match self { #name_match_arms }
|
||||
}
|
||||
|
||||
fn url(&self) -> Option<&'static str> {
|
||||
match self { #url_match_arms }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &#ident {
|
||||
@@ -98,3 +131,23 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses an attribute in the form of `#[doc = " [name](https://example.com/)"]`
|
||||
/// into a tuple of link label and URL.
|
||||
fn parse_doc_attr(doc_attr: &Attribute) -> syn::Result<(String, String)> {
|
||||
let Ok(Meta::NameValue(MetaNameValue{lit: Lit::Str(doc_lit), ..})) = doc_attr.parse_meta() else {
|
||||
return Err(Error::new(doc_attr.span(), r#"expected doc attribute to be in the form of #[doc = "..."]"#))
|
||||
};
|
||||
parse_markdown_link(doc_lit.value().trim())
|
||||
.map(|(name, url)| (name.to_string(), url.to_string()))
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
doc_lit.span(),
|
||||
r#"expected doc comment to be in the form of `/// [name](https://example.com/)`"#,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_markdown_link(link: &str) -> Option<(&str, &str)> {
|
||||
link.strip_prefix('[')?.strip_suffix(')')?.split_once("](")
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ mod tests {
|
||||
fp.write(f"{indent}// {plugin}")
|
||||
fp.write("\n")
|
||||
|
||||
elif line.strip() == '#[prefix = "RUF"]':
|
||||
elif line.strip() == '/// Ruff-specific rules':
|
||||
fp.write(f"/// [{plugin}]({url})\n")
|
||||
fp.write(f'{indent}#[prefix = "{prefix_code}"]\n')
|
||||
fp.write(f"{indent}{pascal_case(plugin)},")
|
||||
fp.write("\n")
|
||||
|
||||
@@ -116,7 +116,7 @@ pub fn {rule_name_snake}(checker: &mut Checker) {{}}
|
||||
|
||||
if line.strip() == f"// {linter}":
|
||||
indent = get_indent(line)
|
||||
fp.write(f"{indent}{code} => rules::{linter}::rules::{name},")
|
||||
fp.write(f"{indent}{code} => rules::{dir_name(linter)}::rules::{name},")
|
||||
fp.write("\n")
|
||||
has_written = True
|
||||
|
||||
|
||||
40
src/ast/hashable.rs
Normal file
40
src/ast/hashable.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::comparable::ComparableExpr;
|
||||
|
||||
/// Wrapper around `Expr` that implements `Hash` and `PartialEq`.
|
||||
pub struct HashableExpr<'a>(&'a Expr);
|
||||
|
||||
impl Hash for HashableExpr<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
let comparable = ComparableExpr::from(self.0);
|
||||
comparable.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Self> for HashableExpr<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let comparable = ComparableExpr::from(self.0);
|
||||
comparable == ComparableExpr::from(other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HashableExpr<'_> {}
|
||||
|
||||
impl<'a> From<&'a Expr> for HashableExpr<'a> {
|
||||
fn from(expr: &'a Expr) -> Self {
|
||||
Self(expr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> HashableExpr<'a> {
|
||||
pub(crate) fn from_expr(expr: &'a Expr) -> Self {
|
||||
Self(expr)
|
||||
}
|
||||
|
||||
pub(crate) fn as_expr(&self) -> &'a Expr {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod branch_detection;
|
||||
pub mod cast;
|
||||
pub mod comparable;
|
||||
pub mod function_type;
|
||||
pub mod hashable;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
|
||||
@@ -36,8 +36,8 @@ use crate::rules::{
|
||||
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
|
||||
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pie, flake8_print,
|
||||
flake8_pytest_style, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
|
||||
flake8_unused_arguments, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes,
|
||||
pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
flake8_unused_arguments, flake8_use_pathlib, mccabe, pandas_vet, pep8_naming, pycodestyle,
|
||||
pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -1386,6 +1386,7 @@ where
|
||||
stmt,
|
||||
test,
|
||||
body,
|
||||
orelse,
|
||||
self.current_stmt_parent().map(Into::into),
|
||||
);
|
||||
}
|
||||
@@ -1578,6 +1579,12 @@ where
|
||||
if self.settings.rules.enabled(&Rule::TryConsiderElse) {
|
||||
tryceratops::rules::try_consider_else(self, body, orelse);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::VerboseRaise) {
|
||||
tryceratops::rules::verbose_raise(self, handlers);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::RaiseWithinTry) {
|
||||
tryceratops::rules::raise_within_try(self, body);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.rules.enabled(&Rule::DoNotAssignLambda) {
|
||||
@@ -2203,6 +2210,11 @@ where
|
||||
flake8_bugbear::rules::zip_without_explicit_strict(self, expr, func, keywords);
|
||||
}
|
||||
|
||||
// flake8-pie
|
||||
if self.settings.rules.enabled(&Rule::NoUnnecessaryDictKwargs) {
|
||||
flake8_pie::rules::no_unnecessary_dict_kwargs(self, expr, keywords);
|
||||
}
|
||||
|
||||
// flake8-bandit
|
||||
if self.settings.rules.enabled(&Rule::ExecUsed) {
|
||||
if let Some(diagnostic) = flake8_bandit::rules::exec_used(expr, func) {
|
||||
@@ -2249,6 +2261,15 @@ where
|
||||
if self.settings.rules.enabled(&Rule::RequestWithoutTimeout) {
|
||||
flake8_bandit::rules::request_without_timeout(self, func, args, keywords);
|
||||
}
|
||||
if self
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::LoggingConfigInsecureListen)
|
||||
{
|
||||
flake8_bandit::rules::logging_config_insecure_listen(
|
||||
self, func, args, keywords,
|
||||
);
|
||||
}
|
||||
|
||||
// flake8-comprehensions
|
||||
if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorList) {
|
||||
@@ -2545,6 +2566,35 @@ where
|
||||
{
|
||||
flake8_simplify::rules::open_file_with_context_handler(self, func);
|
||||
}
|
||||
|
||||
// flake8-use-pathlib
|
||||
if self.settings.rules.enabled(&Rule::PathlibAbspath)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibChmod)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibMkdir)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibMakedirs)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibRename)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibReplace)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibRmdir)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibRemove)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibUnlink)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibGetcwd)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibExists)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibExpanduser)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibIsDir)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibIsFile)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibIsLink)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibReadlink)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibStat)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibIsAbs)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibJoin)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibBasename)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibSamefile)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibSplitext)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibOpen)
|
||||
|| self.settings.rules.enabled(&Rule::PathlibPyPath)
|
||||
{
|
||||
flake8_use_pathlib::helpers::replaceable_by_pathlib(self, func);
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, values } => {
|
||||
if self
|
||||
@@ -2558,6 +2608,10 @@ where
|
||||
{
|
||||
pyflakes::rules::repeated_keys(self, keys, values);
|
||||
}
|
||||
|
||||
if self.settings.rules.enabled(&Rule::NoUnnecessarySpread) {
|
||||
flake8_pie::rules::no_unnecessary_spread(self, keys, values);
|
||||
}
|
||||
}
|
||||
ExprKind::Yield { .. } => {
|
||||
if self.settings.rules.enabled(&Rule::YieldOutsideFunction) {
|
||||
@@ -3306,6 +3360,9 @@ where
|
||||
body,
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::ReraiseNoCause) {
|
||||
tryceratops::rules::reraise_no_cause(self, body);
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.rules.enabled(&Rule::AmbiguousVariableName) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
//! Lint rules based on checking raw physical lines.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::flake8_executable::helpers::extract_shebang;
|
||||
use crate::rules::flake8_executable::rules::{shebang_newline, shebang_python, shebang_whitespace};
|
||||
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
|
||||
use crate::rules::flake8_executable::rules::{
|
||||
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
|
||||
};
|
||||
use crate::rules::pycodestyle::rules::{
|
||||
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
|
||||
};
|
||||
@@ -11,6 +15,7 @@ use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
|
||||
use crate::settings::{flags, Settings};
|
||||
|
||||
pub fn check_lines(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
commented_lines: &[usize],
|
||||
doc_lines: &[usize],
|
||||
@@ -18,8 +23,11 @@ pub fn check_lines(
|
||||
autofix: flags::Autofix,
|
||||
) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
let mut has_any_shebang = false;
|
||||
|
||||
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
|
||||
let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable);
|
||||
let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile);
|
||||
let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace);
|
||||
let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline);
|
||||
let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython);
|
||||
@@ -68,8 +76,23 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_shebang_whitespace || enforce_shebang_newline || enforce_shebang_python {
|
||||
if enforce_shebang_missing
|
||||
|| enforce_shebang_not_executable
|
||||
|| enforce_shebang_whitespace
|
||||
|| enforce_shebang_newline
|
||||
|| enforce_shebang_python
|
||||
{
|
||||
let shebang = extract_shebang(line);
|
||||
if enforce_shebang_not_executable {
|
||||
if let Some(diagnostic) = shebang_not_executable(path, index, &shebang) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if enforce_shebang_missing {
|
||||
if !has_any_shebang && matches!(shebang, ShebangDirective::Match(_, _, _, _)) {
|
||||
has_any_shebang = true;
|
||||
}
|
||||
}
|
||||
if enforce_shebang_whitespace {
|
||||
if let Some(diagnostic) =
|
||||
shebang_whitespace(index, &shebang, fix_shebang_whitespace)
|
||||
@@ -124,12 +147,20 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
if enforce_shebang_missing && !has_any_shebang {
|
||||
if let Some(diagnostic) = shebang_missing(path) {
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::{flags, Settings};
|
||||
@@ -139,6 +170,7 @@ mod tests {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
check_lines(
|
||||
Path::new("foo.py"),
|
||||
line,
|
||||
&[],
|
||||
&[],
|
||||
|
||||
@@ -6,7 +6,8 @@ use rustpython_parser::ast::Location;
|
||||
use crate::ast::types::Range;
|
||||
use crate::fix::Fix;
|
||||
use crate::noqa::{is_file_exempt, Directive};
|
||||
use crate::registry::{Diagnostic, DiagnosticKind, Rule, CODE_REDIRECTS};
|
||||
use crate::registry::{Diagnostic, DiagnosticKind, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::settings::{flags, Settings};
|
||||
use crate::violations::UnusedCodes;
|
||||
use crate::{noqa, violations};
|
||||
@@ -123,7 +124,7 @@ pub fn check_noqa(
|
||||
let mut valid_codes = vec![];
|
||||
let mut self_ignore = false;
|
||||
for code in codes {
|
||||
let code = CODE_REDIRECTS.get(code).map_or(code, |r| r.code());
|
||||
let code = get_redirect_target(code).unwrap_or(code);
|
||||
if code == Rule::UnusedNOQA.code() {
|
||||
self_ignore = true;
|
||||
break;
|
||||
|
||||
@@ -124,9 +124,12 @@ pub fn check_tokens(
|
||||
// ISC001, ISC002
|
||||
if enforce_implicit_string_concatenation {
|
||||
diagnostics.extend(
|
||||
flake8_implicit_str_concat::rules::implicit(tokens)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
flake8_implicit_str_concat::rules::implicit(
|
||||
tokens,
|
||||
&settings.flake8_implicit_str_concat,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
|
||||
use super::external_config::ExternalConfig;
|
||||
use super::plugin::Plugin;
|
||||
use super::{parser, plugin};
|
||||
use crate::registry::RuleSelector;
|
||||
use crate::registry::RuleCodePrefix;
|
||||
use crate::rule_selector::{prefix_to_selector, RuleSelector};
|
||||
use crate::rules::flake8_pytest_style::types::{
|
||||
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
|
||||
};
|
||||
@@ -21,6 +21,12 @@ use crate::settings::options::Options;
|
||||
use crate::settings::pyproject::Pyproject;
|
||||
use crate::warn_user;
|
||||
|
||||
const DEFAULT_SELECTORS: &[RuleSelector] = &[
|
||||
prefix_to_selector(RuleCodePrefix::F),
|
||||
prefix_to_selector(RuleCodePrefix::E),
|
||||
prefix_to_selector(RuleCodePrefix::W),
|
||||
];
|
||||
|
||||
pub fn convert(
|
||||
config: &HashMap<String, HashMap<String, Option<String>>>,
|
||||
external_config: &ExternalConfig,
|
||||
@@ -76,7 +82,7 @@ pub fn convert(
|
||||
.as_ref()
|
||||
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
|
||||
})
|
||||
.unwrap_or_else(|| plugin::resolve_select(&plugins));
|
||||
.unwrap_or_else(|| resolve_select(&plugins));
|
||||
let mut ignore = flake8
|
||||
.get("ignore")
|
||||
.and_then(|value| {
|
||||
@@ -406,21 +412,47 @@ pub fn convert(
|
||||
Ok(Pyproject::new(options))
|
||||
}
|
||||
|
||||
/// Resolve the set of enabled `RuleSelector` values for the given
|
||||
/// plugins.
|
||||
fn resolve_select(plugins: &[Plugin]) -> BTreeSet<RuleSelector> {
|
||||
let mut select: BTreeSet<_> = DEFAULT_SELECTORS.iter().cloned().collect();
|
||||
select.extend(plugins.iter().map(Plugin::selector));
|
||||
select
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::super::plugin::Plugin;
|
||||
use super::convert;
|
||||
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
|
||||
use crate::flake8_to_ruff::ExternalConfig;
|
||||
use crate::registry::RuleSelector;
|
||||
use crate::registry::RuleCodePrefix;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::rules::pydocstyle::settings::Convention;
|
||||
use crate::rules::{flake8_quotes, pydocstyle};
|
||||
use crate::settings::options::Options;
|
||||
use crate::settings::pyproject::Pyproject;
|
||||
|
||||
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
|
||||
Options {
|
||||
ignore: Some(vec![]),
|
||||
select: Some(
|
||||
DEFAULT_SELECTORS
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(plugins)
|
||||
.sorted()
|
||||
.collect(),
|
||||
),
|
||||
..Options::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_converts_empty() -> Result<()> {
|
||||
let actual = convert(
|
||||
@@ -428,55 +460,7 @@ mod tests {
|
||||
&ExternalConfig::default(),
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
let expected = Pyproject::new(default_options([]));
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
@@ -493,53 +477,8 @@ mod tests {
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -557,53 +496,8 @@ mod tests {
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: Some(100),
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -620,55 +514,7 @@ mod tests {
|
||||
&ExternalConfig::default(),
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
});
|
||||
let expected = Pyproject::new(default_options([]));
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
Ok(())
|
||||
@@ -685,58 +531,13 @@ mod tests {
|
||||
Some(vec![]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
..default_options([])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -757,60 +558,10 @@ mod tests {
|
||||
Some(vec![Plugin::Flake8Docstrings]),
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::D,
|
||||
RuleSelector::E,
|
||||
RuleSelector::F,
|
||||
RuleSelector::W,
|
||||
]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: Some(pydocstyle::settings::Options {
|
||||
convention: Some(Convention::Numpy),
|
||||
}),
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
..default_options([RuleCodePrefix::D.into()])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -828,63 +579,13 @@ mod tests {
|
||||
None,
|
||||
)?;
|
||||
let expected = Pyproject::new(Options {
|
||||
allowed_confusables: None,
|
||||
builtins: None,
|
||||
cache_dir: None,
|
||||
dummy_variable_rgx: None,
|
||||
exclude: None,
|
||||
extend: None,
|
||||
extend_exclude: None,
|
||||
extend_ignore: None,
|
||||
extend_select: None,
|
||||
external: None,
|
||||
fix: None,
|
||||
fix_only: None,
|
||||
fixable: None,
|
||||
format: None,
|
||||
force_exclude: None,
|
||||
ignore: Some(vec![]),
|
||||
ignore_init_module_imports: None,
|
||||
line_length: None,
|
||||
namespace_packages: None,
|
||||
per_file_ignores: None,
|
||||
required_version: None,
|
||||
respect_gitignore: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::E,
|
||||
RuleSelector::F,
|
||||
RuleSelector::Q,
|
||||
RuleSelector::W,
|
||||
]),
|
||||
show_source: None,
|
||||
src: None,
|
||||
target_version: None,
|
||||
unfixable: None,
|
||||
typing_modules: None,
|
||||
task_tags: None,
|
||||
update_check: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bandit: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_builtins: None,
|
||||
flake8_errmsg: None,
|
||||
flake8_pytest_style: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
docstring_quotes: None,
|
||||
avoid_escape: None,
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
flake8_unused_arguments: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
pycodestyle: None,
|
||||
pydocstyle: None,
|
||||
pylint: None,
|
||||
pyupgrade: None,
|
||||
..default_options([RuleCodePrefix::Q.into()])
|
||||
});
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use colored::Colorize;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::registry::{RuleSelector, PREFIX_REDIRECTS};
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::warn_user;
|
||||
|
||||
@@ -21,9 +20,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<RuleSelector> {
|
||||
if code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code) {
|
||||
codes.push(code.clone());
|
||||
} else if let Ok(code) = RuleSelector::from_str(code) {
|
||||
if let Ok(code) = RuleSelector::from_str(code) {
|
||||
codes.push(code);
|
||||
} else {
|
||||
warn_user!("Unsupported prefix code: {code}");
|
||||
@@ -89,14 +86,7 @@ impl State {
|
||||
fn parse(&self) -> Vec<PatternPrefixPair> {
|
||||
let mut codes: Vec<PatternPrefixPair> = vec![];
|
||||
for code in &self.codes {
|
||||
if let Some(code) = PREFIX_REDIRECTS.get(code.as_str()) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
prefix: code.clone(),
|
||||
});
|
||||
}
|
||||
} else if let Ok(code) = RuleSelector::from_str(code) {
|
||||
if let Ok(code) = RuleSelector::from_str(code) {
|
||||
for filename in &self.filenames {
|
||||
codes.push(PatternPrefixPair {
|
||||
pattern: filename.clone(),
|
||||
@@ -206,7 +196,8 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use super::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
|
||||
use crate::registry::RuleSelector;
|
||||
use crate::registry::RuleCodePrefix;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
|
||||
#[test]
|
||||
@@ -220,19 +211,19 @@ mod tests {
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401");
|
||||
let expected = vec![RuleSelector::F401];
|
||||
let expected = vec![RuleCodePrefix::F401.into()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,");
|
||||
let expected = vec![RuleSelector::F401];
|
||||
let expected = vec![RuleCodePrefix::F401.into()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401,E501");
|
||||
let expected = vec![RuleSelector::F401, RuleSelector::E501];
|
||||
let expected = vec![RuleCodePrefix::F401.into(), RuleCodePrefix::E501.into()];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = parse_prefix_codes("F401, E501");
|
||||
let expected = vec![RuleSelector::F401, RuleSelector::E501];
|
||||
let expected = vec![RuleCodePrefix::F401.into(), RuleCodePrefix::E501.into()];
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
@@ -285,11 +276,11 @@ mod tests {
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "locust/test/*".to_string(),
|
||||
prefix: RuleSelector::F841,
|
||||
prefix: RuleCodePrefix::F841.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: RuleSelector::F841,
|
||||
prefix: RuleCodePrefix::F841.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
@@ -305,23 +296,23 @@ mod tests {
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "t/*".to_string(),
|
||||
prefix: RuleSelector::D,
|
||||
prefix: RuleCodePrefix::D.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "setup.py".to_string(),
|
||||
prefix: RuleSelector::D,
|
||||
prefix: RuleCodePrefix::D.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "examples/*".to_string(),
|
||||
prefix: RuleSelector::D,
|
||||
prefix: RuleCodePrefix::D.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "docs/*".to_string(),
|
||||
prefix: RuleSelector::D,
|
||||
prefix: RuleCodePrefix::D.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "extra/*".to_string(),
|
||||
prefix: RuleSelector::D,
|
||||
prefix: RuleCodePrefix::D.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
@@ -343,47 +334,47 @@ mod tests {
|
||||
let expected: Vec<PatternPrefixPair> = vec![
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/__init__.py".to_string(),
|
||||
prefix: RuleSelector::E402,
|
||||
prefix: RuleCodePrefix::E402.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
|
||||
prefix: RuleSelector::F401,
|
||||
prefix: RuleCodePrefix::F401.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/http/__init__.py".to_string(),
|
||||
prefix: RuleSelector::F401,
|
||||
prefix: RuleCodePrefix::F401.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: RuleSelector::E402,
|
||||
prefix: RuleCodePrefix::E402.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/linkextractors/__init__.py".to_string(),
|
||||
prefix: RuleSelector::F401,
|
||||
prefix: RuleCodePrefix::F401.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/selector/__init__.py".to_string(),
|
||||
prefix: RuleSelector::F401,
|
||||
prefix: RuleCodePrefix::F401.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: RuleSelector::E402,
|
||||
prefix: RuleCodePrefix::E402.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/spiders/__init__.py".to_string(),
|
||||
prefix: RuleSelector::F401,
|
||||
prefix: RuleCodePrefix::F401.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: RuleSelector::F403,
|
||||
prefix: RuleCodePrefix::F403.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "scrapy/utils/url.py".to_string(),
|
||||
prefix: RuleSelector::F405,
|
||||
prefix: RuleCodePrefix::F405.into(),
|
||||
},
|
||||
PatternPrefixPair {
|
||||
pattern: "tests/test_loader.py".to_string(),
|
||||
prefix: RuleSelector::E741,
|
||||
prefix: RuleCodePrefix::E741.into(),
|
||||
},
|
||||
];
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -4,32 +4,44 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use crate::registry::RuleSelector;
|
||||
use crate::registry::RuleCodePrefix;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake82020,
|
||||
Flake8Annotations,
|
||||
Flake8Bandit,
|
||||
Flake8BlindExcept,
|
||||
Flake8BooleanTrap,
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Commas,
|
||||
Flake8Comprehensions,
|
||||
Flake8Datetimez,
|
||||
Flake8Debugger,
|
||||
Flake8Docstrings,
|
||||
Flake8Eradicate,
|
||||
Flake8ErrMsg,
|
||||
Flake8Executable,
|
||||
Flake8ImplicitStrConcat,
|
||||
Flake8ImportConventions,
|
||||
Flake8NoPep420,
|
||||
Flake8Pie,
|
||||
Flake8Print,
|
||||
Flake8PytestStyle,
|
||||
Flake8Quotes,
|
||||
Flake8Return,
|
||||
Flake8Simplify,
|
||||
Flake8TidyImports,
|
||||
Flake8TypeChecking,
|
||||
Flake8UnusedArguments,
|
||||
Flake8UsePathlib,
|
||||
McCabe,
|
||||
PEP8Naming,
|
||||
PandasVet,
|
||||
Pyupgrade,
|
||||
Tryceratops,
|
||||
}
|
||||
|
||||
impl FromStr for Plugin {
|
||||
@@ -37,28 +49,39 @@ impl FromStr for Plugin {
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"flake8-2020" => Ok(Plugin::Flake82020),
|
||||
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
|
||||
"flake8-bandit" => Ok(Plugin::Flake8Bandit),
|
||||
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
|
||||
"flake8-boolean-trap" => Ok(Plugin::Flake8BooleanTrap),
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-commas" => Ok(Plugin::Flake8Commas),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
"flake8-datetimez" => Ok(Plugin::Flake8Datetimez),
|
||||
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
|
||||
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
|
||||
"flake8-eradicate" => Ok(Plugin::Flake8Eradicate),
|
||||
"flake8-errmsg" => Ok(Plugin::Flake8ErrMsg),
|
||||
"flake8-executable" => Ok(Plugin::Flake8Executable),
|
||||
"flake8-implicit-str-concat" => Ok(Plugin::Flake8ImplicitStrConcat),
|
||||
"flake8-import-conventions" => Ok(Plugin::Flake8ImportConventions),
|
||||
"flake8-no-pep420" => Ok(Plugin::Flake8NoPep420),
|
||||
"flake8-pie" => Ok(Plugin::Flake8Pie),
|
||||
"flake8-print" => Ok(Plugin::Flake8Print),
|
||||
"flake8-pytest-style" => Ok(Plugin::Flake8PytestStyle),
|
||||
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
|
||||
"flake8-return" => Ok(Plugin::Flake8Return),
|
||||
"flake8-simplify" => Ok(Plugin::Flake8Simplify),
|
||||
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
|
||||
"flake8-type-checking" => Ok(Plugin::Flake8TypeChecking),
|
||||
"flake8-unused-arguments" => Ok(Plugin::Flake8UnusedArguments),
|
||||
"flake8-use-pathlib" => Ok(Plugin::Flake8UsePathlib),
|
||||
"mccabe" => Ok(Plugin::McCabe),
|
||||
"pandas-vet" => Ok(Plugin::PandasVet),
|
||||
"pep8-naming" => Ok(Plugin::PEP8Naming),
|
||||
"pandas-vet" => Ok(Plugin::PandasVet),
|
||||
"pyupgrade" => Ok(Plugin::Pyupgrade),
|
||||
"tryceratops" => Ok(Plugin::Tryceratops),
|
||||
_ => Err(anyhow!("Unknown plugin: {string}")),
|
||||
}
|
||||
}
|
||||
@@ -70,60 +93,81 @@ impl fmt::Debug for Plugin {
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Plugin::Flake82020 => "flake8-2020",
|
||||
Plugin::Flake8Annotations => "flake8-annotations",
|
||||
Plugin::Flake8Bandit => "flake8-bandit",
|
||||
Plugin::Flake8BlindExcept => "flake8-blind-except",
|
||||
Plugin::Flake8BooleanTrap => "flake8-boolean-trap",
|
||||
Plugin::Flake8Bugbear => "flake8-bugbear",
|
||||
Plugin::Flake8Builtins => "flake8-builtins",
|
||||
Plugin::Flake8Commas => "flake8-commas",
|
||||
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::Flake8Executable => "flake8-executable",
|
||||
Plugin::Flake8ImplicitStrConcat => "flake8-implicit-str-concat",
|
||||
Plugin::Flake8ImportConventions => "flake8-import-conventions",
|
||||
Plugin::Flake8NoPep420 => "flake8-no-pep420",
|
||||
Plugin::Flake8Pie => "flake8-pie",
|
||||
Plugin::Flake8Print => "flake8-print",
|
||||
Plugin::Flake8PytestStyle => "flake8-pytest-style",
|
||||
Plugin::Flake8Quotes => "flake8-quotes",
|
||||
Plugin::Flake8Return => "flake8-return",
|
||||
Plugin::Flake8Simplify => "flake8-simplify",
|
||||
Plugin::Flake8TidyImports => "flake8-tidy-imports",
|
||||
Plugin::Flake8TypeChecking => "flake8-type-checking",
|
||||
Plugin::Flake8UnusedArguments => "flake8-unused-arguments",
|
||||
Plugin::Flake8UsePathlib => "flake8-use-pathlib",
|
||||
Plugin::McCabe => "mccabe",
|
||||
Plugin::PEP8Naming => "pep8-naming",
|
||||
Plugin::PandasVet => "pandas-vet",
|
||||
Plugin::Pyupgrade => "pyupgrade",
|
||||
Plugin::Tryceratops => "tryceratops",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(martin): Convert into `impl From<Plugin> for Linter`
|
||||
impl Plugin {
|
||||
pub fn selector(&self) -> RuleSelector {
|
||||
match self {
|
||||
Plugin::Flake8Annotations => RuleSelector::ANN,
|
||||
Plugin::Flake8Bandit => RuleSelector::S,
|
||||
// TODO(charlie): Handle rename of `B` to `BLE`.
|
||||
Plugin::Flake8BlindExcept => RuleSelector::BLE,
|
||||
Plugin::Flake8Bugbear => RuleSelector::B,
|
||||
Plugin::Flake8Builtins => RuleSelector::A,
|
||||
Plugin::Flake8Comprehensions => RuleSelector::C4,
|
||||
Plugin::Flake8Datetimez => RuleSelector::DTZ,
|
||||
Plugin::Flake8Debugger => RuleSelector::T1,
|
||||
Plugin::Flake8Docstrings => RuleSelector::D,
|
||||
// TODO(charlie): Handle rename of `E` to `ERA`.
|
||||
Plugin::Flake8Eradicate => RuleSelector::ERA,
|
||||
Plugin::Flake8ErrMsg => RuleSelector::EM,
|
||||
Plugin::Flake8ImplicitStrConcat => RuleSelector::ISC,
|
||||
Plugin::Flake8Print => RuleSelector::T2,
|
||||
Plugin::Flake8PytestStyle => RuleSelector::PT,
|
||||
Plugin::Flake8Quotes => RuleSelector::Q,
|
||||
Plugin::Flake8Return => RuleSelector::RET,
|
||||
Plugin::Flake8Simplify => RuleSelector::SIM,
|
||||
Plugin::Flake8TidyImports => RuleSelector::TID25,
|
||||
Plugin::McCabe => RuleSelector::C9,
|
||||
Plugin::PandasVet => RuleSelector::PD,
|
||||
Plugin::PEP8Naming => RuleSelector::N,
|
||||
Plugin::Pyupgrade => RuleSelector::UP,
|
||||
Plugin::Flake82020 => RuleCodePrefix::YTT.into(),
|
||||
Plugin::Flake8Annotations => RuleCodePrefix::ANN.into(),
|
||||
Plugin::Flake8Bandit => RuleCodePrefix::S.into(),
|
||||
Plugin::Flake8BlindExcept => RuleCodePrefix::BLE.into(),
|
||||
Plugin::Flake8BooleanTrap => RuleCodePrefix::FBT.into(),
|
||||
Plugin::Flake8Bugbear => RuleCodePrefix::B.into(),
|
||||
Plugin::Flake8Builtins => RuleCodePrefix::A.into(),
|
||||
Plugin::Flake8Commas => RuleCodePrefix::COM.into(),
|
||||
Plugin::Flake8Comprehensions => RuleCodePrefix::C4.into(),
|
||||
Plugin::Flake8Datetimez => RuleCodePrefix::DTZ.into(),
|
||||
Plugin::Flake8Debugger => RuleCodePrefix::T1.into(),
|
||||
Plugin::Flake8Docstrings => RuleCodePrefix::D.into(),
|
||||
Plugin::Flake8Eradicate => RuleCodePrefix::ERA.into(),
|
||||
Plugin::Flake8ErrMsg => RuleCodePrefix::EM.into(),
|
||||
Plugin::Flake8Executable => RuleCodePrefix::EXE.into(),
|
||||
Plugin::Flake8ImplicitStrConcat => RuleCodePrefix::ISC.into(),
|
||||
Plugin::Flake8ImportConventions => RuleCodePrefix::ICN.into(),
|
||||
Plugin::Flake8NoPep420 => RuleCodePrefix::INP.into(),
|
||||
Plugin::Flake8Pie => RuleCodePrefix::PIE.into(),
|
||||
Plugin::Flake8Print => RuleCodePrefix::T2.into(),
|
||||
Plugin::Flake8PytestStyle => RuleCodePrefix::PT.into(),
|
||||
Plugin::Flake8Quotes => RuleCodePrefix::Q.into(),
|
||||
Plugin::Flake8Return => RuleCodePrefix::RET.into(),
|
||||
Plugin::Flake8Simplify => RuleCodePrefix::SIM.into(),
|
||||
Plugin::Flake8TidyImports => RuleCodePrefix::TID.into(),
|
||||
Plugin::Flake8TypeChecking => RuleCodePrefix::TYP.into(),
|
||||
Plugin::Flake8UnusedArguments => RuleCodePrefix::ARG.into(),
|
||||
Plugin::Flake8UsePathlib => RuleCodePrefix::PTH.into(),
|
||||
Plugin::McCabe => RuleCodePrefix::C9.into(),
|
||||
Plugin::PEP8Naming => RuleCodePrefix::N.into(),
|
||||
Plugin::PandasVet => RuleCodePrefix::PD.into(),
|
||||
Plugin::Pyupgrade => RuleCodePrefix::UP.into(),
|
||||
Plugin::Tryceratops => RuleCodePrefix::TRY.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,26 +294,40 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
|
||||
/// For example, if the user ignores `ANN101`, we should infer that
|
||||
/// `flake8-annotations` is active.
|
||||
pub fn infer_plugins_from_codes(selectors: &BTreeSet<RuleSelector>) -> Vec<Plugin> {
|
||||
// Ignore cases in which we've knowingly changed rule prefixes.
|
||||
[
|
||||
Plugin::Flake82020,
|
||||
Plugin::Flake8Annotations,
|
||||
Plugin::Flake8Bandit,
|
||||
Plugin::Flake8BlindExcept,
|
||||
// Plugin::Flake8BlindExcept,
|
||||
Plugin::Flake8BooleanTrap,
|
||||
Plugin::Flake8Bugbear,
|
||||
Plugin::Flake8Builtins,
|
||||
// Plugin::Flake8Commas,
|
||||
Plugin::Flake8Comprehensions,
|
||||
Plugin::Flake8Datetimez,
|
||||
Plugin::Flake8Debugger,
|
||||
Plugin::Flake8Docstrings,
|
||||
Plugin::Flake8Eradicate,
|
||||
// Plugin::Flake8Eradicate,
|
||||
Plugin::Flake8ErrMsg,
|
||||
Plugin::Flake8Executable,
|
||||
Plugin::Flake8ImplicitStrConcat,
|
||||
// Plugin::Flake8ImportConventions,
|
||||
Plugin::Flake8NoPep420,
|
||||
Plugin::Flake8Pie,
|
||||
Plugin::Flake8Print,
|
||||
Plugin::Flake8PytestStyle,
|
||||
Plugin::Flake8Quotes,
|
||||
Plugin::Flake8Return,
|
||||
Plugin::Flake8Simplify,
|
||||
Plugin::Flake8TidyImports,
|
||||
Plugin::PandasVet,
|
||||
// Plugin::Flake8TidyImports,
|
||||
// Plugin::Flake8TypeChecking,
|
||||
Plugin::Flake8UnusedArguments,
|
||||
// Plugin::Flake8UsePathlib,
|
||||
Plugin::McCabe,
|
||||
Plugin::PEP8Naming,
|
||||
Plugin::PandasVet,
|
||||
Plugin::Tryceratops,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|plugin| {
|
||||
@@ -286,14 +344,6 @@ pub fn infer_plugins_from_codes(selectors: &BTreeSet<RuleSelector>) -> Vec<Plugi
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve the set of enabled `RuleSelector` values for the given
|
||||
/// plugins.
|
||||
pub fn resolve_select(plugins: &[Plugin]) -> BTreeSet<RuleSelector> {
|
||||
let mut select = BTreeSet::from([RuleSelector::F, RuleSelector::E, RuleSelector::W]);
|
||||
select.extend(plugins.iter().map(Plugin::selector));
|
||||
select
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -19,6 +19,6 @@ pub struct Pyproject {
|
||||
|
||||
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {
|
||||
let contents = std::fs::read_to_string(path)?;
|
||||
let pyproject = toml_edit::easy::from_str::<Pyproject>(&contents)?;
|
||||
let pyproject = toml::from_str::<Pyproject>(&contents)?;
|
||||
Ok(pyproject)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ mod noqa;
|
||||
mod python;
|
||||
pub mod registry;
|
||||
pub mod resolver;
|
||||
mod rule_redirects;
|
||||
mod rule_selector;
|
||||
mod rules;
|
||||
mod rustpython_helpers;
|
||||
pub mod settings;
|
||||
@@ -47,6 +49,7 @@ mod violations;
|
||||
mod visibility;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use violation::{AutofixKind, Availability as AutofixAvailability};
|
||||
pub use violations::IOError;
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::linter::check_path;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_errmsg,
|
||||
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_tidy_imports,
|
||||
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
|
||||
pyupgrade,
|
||||
flake8_implicit_str_concat, flake8_import_conventions, flake8_pytest_style, flake8_quotes,
|
||||
flake8_tidy_imports, flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle,
|
||||
pydocstyle, pylint, pyupgrade,
|
||||
};
|
||||
use crate::rustpython_helpers::tokenize;
|
||||
use crate::settings::configuration::Configuration;
|
||||
@@ -142,6 +142,9 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
|
||||
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
|
||||
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),
|
||||
flake8_tidy_imports: Some(flake8_tidy_imports::Settings::default().into()),
|
||||
flake8_implicit_str_concat: Some(
|
||||
flake8_implicit_str_concat::settings::Settings::default().into(),
|
||||
),
|
||||
flake8_import_conventions: Some(
|
||||
flake8_import_conventions::settings::Settings::default().into(),
|
||||
),
|
||||
|
||||
@@ -141,6 +141,7 @@ pub fn check_path(
|
||||
.any(|rule_code| matches!(rule_code.lint_source(), LintSource::Lines))
|
||||
{
|
||||
diagnostics.extend(check_lines(
|
||||
path,
|
||||
contents,
|
||||
indexer.commented_lines(),
|
||||
&doc_lines,
|
||||
|
||||
@@ -4,6 +4,7 @@ use fern;
|
||||
#[macro_export]
|
||||
macro_rules! warn_user_once {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
if !WARNED.swap(true, std::sync::atomic::Ordering::SeqCst) {
|
||||
let message = format!("{}", format_args!($($arg)*));
|
||||
@@ -20,6 +21,7 @@ macro_rules! warn_user_once {
|
||||
#[macro_export]
|
||||
macro_rules! warn_user {
|
||||
($($arg:tt)*) => {
|
||||
use colored::Colorize;
|
||||
let message = format!("{}", format_args!($($arg)*));
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
|
||||
13
src/noqa.rs
13
src/noqa.rs
@@ -8,7 +8,8 @@ use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::registry::{Diagnostic, Rule, CODE_REDIRECTS};
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rule_redirects::get_redirect_target;
|
||||
use crate::settings::hashable::HashableHashSet;
|
||||
use crate::source_code::LineEnding;
|
||||
|
||||
@@ -71,13 +72,9 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
/// thereof).
|
||||
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
|
||||
let needle: &str = needle.code();
|
||||
haystack.iter().any(|candidate| {
|
||||
if let Some(candidate) = CODE_REDIRECTS.get(candidate) {
|
||||
needle == candidate.code()
|
||||
} else {
|
||||
&needle == candidate
|
||||
}
|
||||
})
|
||||
haystack
|
||||
.iter()
|
||||
.any(|candidate| needle == get_redirect_target(candidate).unwrap_or(candidate))
|
||||
}
|
||||
|
||||
pub fn add_noqa(
|
||||
|
||||
168
src/registry.rs
168
src/registry.rs
@@ -1,14 +1,13 @@
|
||||
//! Registry of [`Rule`] to [`DiagnosticKind`] mappings.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use ruff_macros::RuleNamespace;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumIter};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::fix::Fix;
|
||||
use crate::rule_selector::{prefix_to_selector, RuleSelector};
|
||||
use crate::violation::Violation;
|
||||
use crate::{rules, violations};
|
||||
|
||||
@@ -338,6 +337,7 @@ ruff_macros::define_rule_mapping!(
|
||||
S506 => violations::UnsafeYAMLLoad,
|
||||
S508 => violations::SnmpInsecureVersion,
|
||||
S509 => violations::SnmpWeakCryptography,
|
||||
S612 => rules::flake8_bandit::rules::LoggingConfigInsecureListen,
|
||||
S701 => violations::Jinja2AutoescapeFalse,
|
||||
// flake8-boolean-trap
|
||||
FBT001 => violations::BooleanPositionalArgInFunctionDefinition,
|
||||
@@ -413,6 +413,8 @@ ruff_macros::define_rule_mapping!(
|
||||
PIE790 => violations::NoUnnecessaryPass,
|
||||
PIE794 => violations::DupeClassFieldDefinitions,
|
||||
PIE796 => violations::PreferUniqueEnums,
|
||||
PIE800 => violations::NoUnnecessarySpread,
|
||||
PIE804 => violations::NoUnnecessaryDictKwargs,
|
||||
PIE807 => violations::PreferListBuiltin,
|
||||
// flake8-commas
|
||||
COM812 => violations::TrailingCommaMissing,
|
||||
@@ -421,6 +423,8 @@ ruff_macros::define_rule_mapping!(
|
||||
// flake8-no-pep420
|
||||
INP001 => violations::ImplicitNamespacePackage,
|
||||
// flake8-executable
|
||||
EXE001 => rules::flake8_executable::rules::ShebangNotExecutable,
|
||||
EXE002 => rules::flake8_executable::rules::ShebangMissingExecutableFile,
|
||||
EXE003 => rules::flake8_executable::rules::ShebangPython,
|
||||
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
|
||||
EXE005 => rules::flake8_executable::rules::ShebangNewline,
|
||||
@@ -428,7 +432,36 @@ ruff_macros::define_rule_mapping!(
|
||||
TYP005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
|
||||
// tryceratops
|
||||
TRY004 => rules::tryceratops::rules::PreferTypeError,
|
||||
TRY200 => rules::tryceratops::rules::ReraiseNoCause,
|
||||
TRY201 => rules::tryceratops::rules::VerboseRaise,
|
||||
TRY300 => rules::tryceratops::rules::TryConsiderElse,
|
||||
TRY301 => rules::tryceratops::rules::RaiseWithinTry,
|
||||
// flake8-use-pathlib
|
||||
PTH100 => rules::flake8_use_pathlib::violations::PathlibAbspath,
|
||||
PTH101 => rules::flake8_use_pathlib::violations::PathlibChmod,
|
||||
PTH102 => rules::flake8_use_pathlib::violations::PathlibMkdir,
|
||||
PTH103 => rules::flake8_use_pathlib::violations::PathlibMakedirs,
|
||||
PTH104 => rules::flake8_use_pathlib::violations::PathlibRename,
|
||||
PTH105 => rules::flake8_use_pathlib::violations::PathlibReplace,
|
||||
PTH106 => rules::flake8_use_pathlib::violations::PathlibRmdir,
|
||||
PTH107 => rules::flake8_use_pathlib::violations::PathlibRemove,
|
||||
PTH108 => rules::flake8_use_pathlib::violations::PathlibUnlink,
|
||||
PTH109 => rules::flake8_use_pathlib::violations::PathlibGetcwd,
|
||||
PTH110 => rules::flake8_use_pathlib::violations::PathlibExists,
|
||||
PTH111 => rules::flake8_use_pathlib::violations::PathlibExpanduser,
|
||||
PTH112 => rules::flake8_use_pathlib::violations::PathlibIsDir,
|
||||
PTH113 => rules::flake8_use_pathlib::violations::PathlibIsFile,
|
||||
PTH114 => rules::flake8_use_pathlib::violations::PathlibIsLink,
|
||||
PTH115 => rules::flake8_use_pathlib::violations::PathlibReadlink,
|
||||
PTH116 => rules::flake8_use_pathlib::violations::PathlibStat,
|
||||
PTH117 => rules::flake8_use_pathlib::violations::PathlibIsAbs,
|
||||
PTH118 => rules::flake8_use_pathlib::violations::PathlibJoin,
|
||||
PTH119 => rules::flake8_use_pathlib::violations::PathlibBasename,
|
||||
PTH120 => rules::flake8_use_pathlib::violations::PathlibDirname,
|
||||
PTH121 => rules::flake8_use_pathlib::violations::PathlibSamefile,
|
||||
PTH122 => rules::flake8_use_pathlib::violations::PathlibSplitext,
|
||||
PTH123 => rules::flake8_use_pathlib::violations::PathlibOpen,
|
||||
PTH124 => rules::flake8_use_pathlib::violations::PathlibPyPath,
|
||||
// ruff
|
||||
RUF001 => violations::AmbiguousUnicodeCharacterString,
|
||||
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
|
||||
@@ -440,81 +473,122 @@ ruff_macros::define_rule_mapping!(
|
||||
|
||||
#[derive(EnumIter, Debug, PartialEq, Eq, RuleNamespace)]
|
||||
pub enum Linter {
|
||||
/// [Pyflakes](https://pypi.org/project/pyflakes/)
|
||||
#[prefix = "F"]
|
||||
Pyflakes,
|
||||
/// [pycodestyle](https://pypi.org/project/pycodestyle/)
|
||||
#[prefix = "E"]
|
||||
#[prefix = "W"]
|
||||
Pycodestyle,
|
||||
/// [mccabe](https://pypi.org/project/mccabe/)
|
||||
#[prefix = "C90"]
|
||||
McCabe,
|
||||
/// [isort](https://pypi.org/project/isort/)
|
||||
#[prefix = "I"]
|
||||
Isort,
|
||||
/// [pydocstyle](https://pypi.org/project/pydocstyle/)
|
||||
#[prefix = "D"]
|
||||
Pydocstyle,
|
||||
/// [pyupgrade](https://pypi.org/project/pyupgrade/)
|
||||
#[prefix = "UP"]
|
||||
Pyupgrade,
|
||||
/// [pep8-naming](https://pypi.org/project/pep8-naming/)
|
||||
#[prefix = "N"]
|
||||
PEP8Naming,
|
||||
/// [flake8-2020](https://pypi.org/project/flake8-2020/)
|
||||
#[prefix = "YTT"]
|
||||
Flake82020,
|
||||
/// [flake8-annotations](https://pypi.org/project/flake8-annotations/)
|
||||
#[prefix = "ANN"]
|
||||
Flake8Annotations,
|
||||
/// [flake8-bandit](https://pypi.org/project/flake8-bandit/)
|
||||
#[prefix = "S"]
|
||||
Flake8Bandit,
|
||||
/// [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
|
||||
#[prefix = "BLE"]
|
||||
Flake8BlindExcept,
|
||||
/// [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
|
||||
#[prefix = "FBT"]
|
||||
Flake8BooleanTrap,
|
||||
/// [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
|
||||
#[prefix = "B"]
|
||||
Flake8Bugbear,
|
||||
/// [flake8-builtins](https://pypi.org/project/flake8-builtins/)
|
||||
#[prefix = "A"]
|
||||
Flake8Builtins,
|
||||
/// [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
|
||||
#[prefix = "C4"]
|
||||
Flake8Comprehensions,
|
||||
/// [flake8-debugger](https://pypi.org/project/flake8-debugger/)
|
||||
#[prefix = "T10"]
|
||||
Flake8Debugger,
|
||||
/// [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
|
||||
#[prefix = "EM"]
|
||||
Flake8ErrMsg,
|
||||
/// [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
#[prefix = "ISC"]
|
||||
Flake8ImplicitStrConcat,
|
||||
/// [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
#[prefix = "ICN"]
|
||||
Flake8ImportConventions,
|
||||
/// [flake8-print](https://pypi.org/project/flake8-print/)
|
||||
#[prefix = "T20"]
|
||||
Flake8Print,
|
||||
/// [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
|
||||
#[prefix = "PT"]
|
||||
Flake8PytestStyle,
|
||||
/// [flake8-quotes](https://pypi.org/project/flake8-quotes/)
|
||||
#[prefix = "Q"]
|
||||
Flake8Quotes,
|
||||
/// [flake8-return](https://pypi.org/project/flake8-return/)
|
||||
#[prefix = "RET"]
|
||||
Flake8Return,
|
||||
/// [flake8-simplify](https://pypi.org/project/flake8-simplify/)
|
||||
#[prefix = "SIM"]
|
||||
Flake8Simplify,
|
||||
/// [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
|
||||
#[prefix = "TID"]
|
||||
Flake8TidyImports,
|
||||
/// [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/)
|
||||
#[prefix = "ARG"]
|
||||
Flake8UnusedArguments,
|
||||
/// [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
|
||||
#[prefix = "DTZ"]
|
||||
Flake8Datetimez,
|
||||
/// [eradicate](https://pypi.org/project/eradicate/)
|
||||
#[prefix = "ERA"]
|
||||
Eradicate,
|
||||
/// [pandas-vet](https://pypi.org/project/pandas-vet/)
|
||||
#[prefix = "PD"]
|
||||
PandasVet,
|
||||
/// [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks)
|
||||
#[prefix = "PGH"]
|
||||
PygrepHooks,
|
||||
/// [Pylint](https://pypi.org/project/pylint/)
|
||||
#[prefix = "PL"]
|
||||
Pylint,
|
||||
/// [flake8-pie](https://pypi.org/project/flake8-pie/)
|
||||
#[prefix = "PIE"]
|
||||
Flake8Pie,
|
||||
/// [flake8-commas](https://pypi.org/project/flake8-commas/)
|
||||
#[prefix = "COM"]
|
||||
Flake8Commas,
|
||||
/// [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/)
|
||||
#[prefix = "INP"]
|
||||
Flake8NoPep420,
|
||||
/// [flake8-executable](https://pypi.org/project/flake8-executable/)
|
||||
#[prefix = "EXE"]
|
||||
Flake8Executable,
|
||||
/// [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
|
||||
#[prefix = "TYP"]
|
||||
Flake8TypeChecking,
|
||||
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
|
||||
#[prefix = "TRY"]
|
||||
Tryceratops,
|
||||
/// [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
|
||||
#[prefix = "PTH"]
|
||||
Flake8UsePathlib,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
}
|
||||
@@ -523,26 +597,34 @@ pub trait RuleNamespace: Sized {
|
||||
fn parse_code(code: &str) -> Option<(Self, &str)>;
|
||||
|
||||
fn prefixes(&self) -> &'static [&'static str];
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/linter.rs"));
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn url(&self) -> Option<&'static str>;
|
||||
}
|
||||
|
||||
/// The prefix, name and selector for an upstream linter category.
|
||||
pub struct LinterCategory(pub &'static str, pub &'static str, pub RuleSelector);
|
||||
|
||||
// TODO(martin): Move these constant definitions back to Linter::categories impl
|
||||
// once RuleSelector is an enum with a Linter variant
|
||||
const PYCODESTYLE_CATEGORIES: &[LinterCategory] = &[
|
||||
LinterCategory("E", "Error", prefix_to_selector(RuleCodePrefix::E)),
|
||||
LinterCategory("W", "Warning", prefix_to_selector(RuleCodePrefix::W)),
|
||||
];
|
||||
|
||||
const PYLINT_CATEGORIES: &[LinterCategory] = &[
|
||||
LinterCategory("PLC", "Convention", prefix_to_selector(RuleCodePrefix::PLC)),
|
||||
LinterCategory("PLE", "Error", prefix_to_selector(RuleCodePrefix::PLE)),
|
||||
LinterCategory("PLR", "Refactor", prefix_to_selector(RuleCodePrefix::PLR)),
|
||||
LinterCategory("PLW", "Warning", prefix_to_selector(RuleCodePrefix::PLW)),
|
||||
];
|
||||
|
||||
impl Linter {
|
||||
pub fn categories(&self) -> Option<&'static [LinterCategory]> {
|
||||
match self {
|
||||
Linter::Pycodestyle => Some(&[
|
||||
LinterCategory("E", "Error", RuleSelector::E),
|
||||
LinterCategory("W", "Warning", RuleSelector::W),
|
||||
]),
|
||||
Linter::Pylint => Some(&[
|
||||
LinterCategory("PLC", "Convention", RuleSelector::PLC),
|
||||
LinterCategory("PLE", "Error", RuleSelector::PLE),
|
||||
LinterCategory("PLR", "Refactor", RuleSelector::PLR),
|
||||
LinterCategory("PLW", "Warning", RuleSelector::PLW),
|
||||
]),
|
||||
Linter::Pycodestyle => Some(PYCODESTYLE_CATEGORIES),
|
||||
Linter::Pylint => Some(PYLINT_CATEGORIES),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -571,6 +653,8 @@ impl Rule {
|
||||
| Rule::MixedSpacesAndTabs
|
||||
| Rule::NoNewLineAtEndOfFile
|
||||
| Rule::PEP3120UnnecessaryCodingComment
|
||||
| Rule::ShebangMissingExecutableFile
|
||||
| Rule::ShebangNotExecutable
|
||||
| Rule::ShebangNewline
|
||||
| Rule::ShebangPython
|
||||
| Rule::ShebangWhitespace => &LintSource::Lines,
|
||||
@@ -582,9 +666,9 @@ impl Rule {
|
||||
| Rule::BadQuotesInlineString
|
||||
| Rule::BadQuotesMultilineString
|
||||
| Rule::CommentedOutCode
|
||||
| Rule::MultiLineImplicitStringConcatenation
|
||||
| Rule::ExtraneousParentheses
|
||||
| Rule::InvalidEscapeSequence
|
||||
| Rule::MultiLineImplicitStringConcatenation
|
||||
| Rule::SingleLineImplicitStringConcatenation
|
||||
| Rule::TrailingCommaMissing
|
||||
| Rule::TrailingCommaOnBareTupleProhibited
|
||||
@@ -636,60 +720,6 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str)] = &[(
|
||||
Consider adding `D203` to `ignore`.",
|
||||
)];
|
||||
|
||||
/// A hash map from deprecated to latest `Rule`.
|
||||
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, Rule>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U001", Rule::UselessMetaclassType),
|
||||
("U003", Rule::TypeOfPrimitive),
|
||||
("U004", Rule::UselessObjectInheritance),
|
||||
("U005", Rule::DeprecatedUnittestAlias),
|
||||
("U006", Rule::UsePEP585Annotation),
|
||||
("U007", Rule::UsePEP604Annotation),
|
||||
("U008", Rule::SuperCallWithParameters),
|
||||
("U009", Rule::PEP3120UnnecessaryCodingComment),
|
||||
("U010", Rule::UnnecessaryFutureImport),
|
||||
("U011", Rule::LRUCacheWithoutParameters),
|
||||
("U012", Rule::UnnecessaryEncodeUTF8),
|
||||
("U013", Rule::ConvertTypedDictFunctionalToClass),
|
||||
("U014", Rule::ConvertNamedTupleFunctionalToClass),
|
||||
("U015", Rule::RedundantOpenModes),
|
||||
("U016", Rule::RemoveSixCompat),
|
||||
("U017", Rule::DatetimeTimezoneUTC),
|
||||
("U019", Rule::TypingTextStrAlias),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", Rule::RelativeImports),
|
||||
("M001", Rule::UnusedNOQA),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV002", Rule::UseOfInplaceArgument),
|
||||
("PDV003", Rule::UseOfDotIsNull),
|
||||
("PDV004", Rule::UseOfDotNotNull),
|
||||
("PDV007", Rule::UseOfDotIx),
|
||||
("PDV008", Rule::UseOfDotAt),
|
||||
("PDV009", Rule::UseOfDotIat),
|
||||
("PDV010", Rule::UseOfDotPivotOrUnstack),
|
||||
("PDV011", Rule::UseOfDotValues),
|
||||
("PDV012", Rule::UseOfDotReadTable),
|
||||
("PDV013", Rule::UseOfDotStack),
|
||||
("PDV015", Rule::UseOfPdMerge),
|
||||
("PDV901", Rule::DfIsABadVariableName),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R501", Rule::UnnecessaryReturnNone),
|
||||
("R502", Rule::ImplicitReturnValue),
|
||||
("R503", Rule::ImplicitReturn),
|
||||
("R504", Rule::UnnecessaryAssign),
|
||||
("R505", Rule::SuperfluousElseReturn),
|
||||
("R506", Rule::SuperfluousElseRaise),
|
||||
("R507", Rule::SuperfluousElseContinue),
|
||||
("R508", Rule::SuperfluousElseBreak),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("IC001", Rule::ImportAliasIsNotConventional),
|
||||
("IC002", Rule::ImportAliasIsNotConventional),
|
||||
("IC003", Rule::ImportAliasIsNotConventional),
|
||||
("IC004", Rule::ImportAliasIsNotConventional),
|
||||
])
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
85
src/rule_redirects.rs
Normal file
85
src/rule_redirects.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// Returns the redirect target for the given code.
|
||||
pub(crate) fn get_redirect_target(code: &str) -> Option<&'static str> {
|
||||
REDIRECTS.get(code).copied()
|
||||
}
|
||||
|
||||
/// Returns the code and the redirect target if the given code is a redirect.
|
||||
/// (The same code is returned to obtain it with a static lifetime).
|
||||
pub(crate) fn get_redirect(code: &str) -> Option<(&'static str, &'static str)> {
|
||||
REDIRECTS.get_key_value(code).map(|(k, v)| (*k, *v))
|
||||
}
|
||||
|
||||
static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
HashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("R", "RET"),
|
||||
("R5", "RET5"),
|
||||
("R50", "RET50"),
|
||||
("R501", "RET501"),
|
||||
("R502", "RET502"),
|
||||
("R503", "RET503"),
|
||||
("R504", "RET504"),
|
||||
("R505", "RET505"),
|
||||
("R506", "RET506"),
|
||||
("R507", "RET507"),
|
||||
("R508", "RET508"),
|
||||
("IC", "ICN"),
|
||||
("IC0", "ICN0"),
|
||||
("IC00", "ICN00"),
|
||||
("IC001", "ICN001"),
|
||||
("IC002", "ICN001"),
|
||||
("IC003", "ICN001"),
|
||||
("IC004", "ICN001"),
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U", "UP"),
|
||||
("U0", "UP0"),
|
||||
("U00", "UP00"),
|
||||
("U001", "UP001"),
|
||||
("U003", "UP003"),
|
||||
("U004", "UP004"),
|
||||
("U005", "UP005"),
|
||||
("U006", "UP006"),
|
||||
("U007", "UP007"),
|
||||
("U008", "UP008"),
|
||||
("U009", "UP009"),
|
||||
("U01", "UP01"),
|
||||
("U010", "UP010"),
|
||||
("U011", "UP011"),
|
||||
("U012", "UP012"),
|
||||
("U013", "UP013"),
|
||||
("U014", "UP014"),
|
||||
("U015", "UP015"),
|
||||
("U016", "UP016"),
|
||||
("U017", "UP017"),
|
||||
("U019", "UP019"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I2", "TID2"),
|
||||
("I25", "TID25"),
|
||||
("I252", "TID252"),
|
||||
("M", "RUF100"),
|
||||
("M0", "RUF100"),
|
||||
("M001", "RUF100"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("PDV", "PD"),
|
||||
("PDV0", "PD0"),
|
||||
("PDV002", "PD002"),
|
||||
("PDV003", "PD003"),
|
||||
("PDV004", "PD004"),
|
||||
("PDV007", "PD007"),
|
||||
("PDV008", "PD008"),
|
||||
("PDV009", "PD009"),
|
||||
("PDV01", "PD01"),
|
||||
("PDV010", "PD010"),
|
||||
("PDV011", "PD011"),
|
||||
("PDV012", "PD012"),
|
||||
("PDV013", "PD013"),
|
||||
("PDV015", "PD015"),
|
||||
("PDV9", "PD9"),
|
||||
("PDV90", "PD90"),
|
||||
("PDV901", "PD901"),
|
||||
])
|
||||
});
|
||||
183
src/rule_selector.rs
Normal file
183
src/rule_selector.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use schemars::_serde_json::Value;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::de::{self, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::registry::{Rule, RuleCodePrefix, RuleIter};
|
||||
use crate::rule_redirects::get_redirect;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum RuleSelector {
|
||||
/// All rules
|
||||
All,
|
||||
Prefix {
|
||||
prefix: RuleCodePrefix,
|
||||
redirected_from: Option<&'static str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl FromStr for RuleSelector {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "ALL" {
|
||||
Ok(Self::All)
|
||||
} else {
|
||||
let (s, redirected_from) = match get_redirect(s) {
|
||||
Some((from, target)) => (target, Some(from)),
|
||||
None => (s, None),
|
||||
};
|
||||
Ok(Self::Prefix {
|
||||
prefix: RuleCodePrefix::from_str(s)
|
||||
.map_err(|_| ParseError::Unknown(s.to_string()))?,
|
||||
redirected_from,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error("Unknown rule selector `{0}`")]
|
||||
// TODO(martin): tell the user how to discover rule codes via the CLI once such a command is
|
||||
// implemented (but that should of course be done only in ruff_cli and not here)
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
impl Serialize for RuleSelector {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
RuleSelector::All => serializer.serialize_str("ALL"),
|
||||
RuleSelector::Prefix { prefix, .. } => prefix.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RuleSelector {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
// We are not simply doing:
|
||||
// let s: &str = Deserialize::deserialize(deserializer)?;
|
||||
// FromStr::from_str(s).map_err(de::Error::custom)
|
||||
// here because the toml crate apparently doesn't support that
|
||||
// (as of toml v0.6.0 running `cargo test` failed with the above two lines)
|
||||
deserializer.deserialize_str(SelectorVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct SelectorVisitor;
|
||||
|
||||
impl Visitor<'_> for SelectorVisitor {
|
||||
type Value = RuleSelector;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str(
|
||||
"expected a string code identifying a linter or specific rule, or a partial rule code \
|
||||
or ALL to refer to all rules",
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
FromStr::from_str(v).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RuleCodePrefix> for RuleSelector {
|
||||
fn from(prefix: RuleCodePrefix) -> Self {
|
||||
Self::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &RuleSelector {
|
||||
type IntoIter = RuleSelectorIter;
|
||||
type Item = Rule;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
|
||||
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Prefix(prefix.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RuleSelectorIter {
|
||||
All(RuleIter),
|
||||
Prefix(std::vec::IntoIter<Rule>),
|
||||
}
|
||||
|
||||
impl Iterator for RuleSelectorIter {
|
||||
type Item = Rule;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
RuleSelectorIter::All(iter) => iter.next(),
|
||||
RuleSelectorIter::Prefix(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
|
||||
// to let us keep the fields of RuleSelector private.
|
||||
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
|
||||
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
|
||||
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
|
||||
pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector {
|
||||
RuleSelector::Prefix {
|
||||
prefix,
|
||||
redirected_from: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for RuleSelector {
|
||||
fn schema_name() -> String {
|
||||
"RuleSelector".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
std::iter::once("ALL".to_string())
|
||||
.chain(RuleCodePrefix::iter().map(|s| s.as_ref().to_string()))
|
||||
.map(Value::String)
|
||||
.collect(),
|
||||
),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RuleSelector {
|
||||
pub(crate) fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
RuleSelector::Prefix { prefix, .. } => prefix.specificity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) enum Specificity {
|
||||
All,
|
||||
Linter,
|
||||
Code1Char,
|
||||
Code2Chars,
|
||||
Code3Chars,
|
||||
Code4Chars,
|
||||
Code5Chars,
|
||||
}
|
||||
@@ -74,147 +74,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
DefinitionKind::Package => {}
|
||||
DefinitionKind::Class(_) => {}
|
||||
DefinitionKind::NestedClass(_) => {}
|
||||
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
|
||||
DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) => {
|
||||
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
|
||||
let (name, args, returns, body) = match_function_def(stmt);
|
||||
let mut has_any_typed_arg = false;
|
||||
|
||||
// ANN001, ANN401
|
||||
for arg in args
|
||||
.args
|
||||
.iter()
|
||||
.chain(args.posonlyargs.iter())
|
||||
.chain(args.kwonlyargs.iter())
|
||||
{
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
check_dynamically_typed(checker, expr, || arg.node.arg.to_string());
|
||||
};
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::MissingTypeFunctionArgument)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
violations::MissingTypeFunctionArgument(arg.node.arg.to_string()),
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN002, ANN401
|
||||
if let Some(arg) = &args.vararg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::MissingTypeArgs) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
violations::MissingTypeArgs(arg.node.arg.to_string()),
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN003, ANN401
|
||||
if let Some(arg) = &args.kwarg {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
let name = &arg.node.arg;
|
||||
check_dynamically_typed(checker, expr, || format!("**{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||
{
|
||||
if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
violations::MissingTypeKwargs(arg.node.arg.to_string()),
|
||||
Range::from_located(arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::DynamicallyTypedExpression)
|
||||
{
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
};
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
// only returns `None` (explicitly or implicitly).
|
||||
if checker.settings.flake8_annotations.suppress_none_returning
|
||||
&& is_none_returning(body)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
match visibility {
|
||||
Visibility::Public => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::MissingReturnTypePublicFunction)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
violations::MissingReturnTypePublicFunction(name.to_string()),
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
Visibility::Private => {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
.enabled(&Rule::MissingReturnTypePrivateFunction)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
violations::MissingReturnTypePrivateFunction(name.to_string()),
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DefinitionKind::Method(stmt) => {
|
||||
let (name, args, returns, body) = match_function_def(stmt);
|
||||
let mut has_any_typed_arg = false;
|
||||
|
||||
// ANN001
|
||||
for arg in args
|
||||
.args
|
||||
.iter()
|
||||
@@ -222,10 +89,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(!visibility::is_staticmethod(
|
||||
checker,
|
||||
cast::decorator_list(stmt),
|
||||
)),
|
||||
usize::from(
|
||||
is_method
|
||||
&& !visibility::is_staticmethod(checker, cast::decorator_list(stmt)),
|
||||
),
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed arguments
|
||||
@@ -313,7 +180,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
|
||||
if is_method && !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
|
||||
if let Some(arg) = args.args.first() {
|
||||
if arg.node.annotation.is_none() {
|
||||
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
|
||||
@@ -335,7 +202,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
}
|
||||
}
|
||||
|
||||
// ANN201, ANN202
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
if checker
|
||||
.settings
|
||||
@@ -353,7 +220,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
return;
|
||||
}
|
||||
|
||||
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
|
||||
if is_method && visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
@@ -364,7 +231,9 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
} else if visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
|
||||
} else if is_method
|
||||
&& visibility::is_staticmethod(checker, cast::decorator_list(stmt))
|
||||
{
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
@@ -375,7 +244,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
helpers::identifier_range(stmt, checker.locator),
|
||||
));
|
||||
}
|
||||
} else if visibility::is_init(cast::name(stmt)) {
|
||||
} else if is_method && visibility::is_init(cast::name(stmt)) {
|
||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||
// least one argument is typed.
|
||||
if checker
|
||||
@@ -401,7 +270,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
} else if visibility::is_magic(cast::name(stmt)) {
|
||||
} else if is_method && visibility::is_magic(cast::name(stmt)) {
|
||||
if checker
|
||||
.settings
|
||||
.rules
|
||||
|
||||
@@ -28,6 +28,7 @@ mod tests {
|
||||
#[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")]
|
||||
#[test_case(Rule::SnmpInsecureVersion, Path::new("S508.py"); "S508")]
|
||||
#[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"); "S509")]
|
||||
#[test_case(Rule::LoggingConfigInsecureListen, Path::new("S612.py"); "S612")]
|
||||
#[test_case(Rule::Jinja2AutoescapeFalse, Path::new("S701.py"); "S701")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use ruff_macros::derive_message_formats;
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
|
||||
use crate::ast::helpers::SimpleCallArgs;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::define_violation;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct LoggingConfigInsecureListen;
|
||||
);
|
||||
impl Violation for LoggingConfigInsecureListen {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of insecure `logging.config.listen` detected")
|
||||
}
|
||||
}
|
||||
|
||||
/// S612
|
||||
pub fn logging_config_insecure_listen(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if checker.resolve_call_path(func).map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["logging", "config", "listen"]
|
||||
}) {
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
if call_args.get_argument("verify", None).is_none() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
LoggingConfigInsecureListen,
|
||||
Range::from_located(func),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ pub use hardcoded_password_string::{
|
||||
pub use hardcoded_tmp_directory::hardcoded_tmp_directory;
|
||||
pub use hashlib_insecure_hash_functions::hashlib_insecure_hash_functions;
|
||||
pub use jinja2_autoescape_false::jinja2_autoescape_false;
|
||||
pub use logging_config_insecure_listen::{
|
||||
logging_config_insecure_listen, LoggingConfigInsecureListen,
|
||||
};
|
||||
pub use request_with_no_cert_validation::request_with_no_cert_validation;
|
||||
pub use request_without_timeout::request_without_timeout;
|
||||
pub use snmp_insecure_version::snmp_insecure_version;
|
||||
@@ -26,6 +29,7 @@ mod hardcoded_password_string;
|
||||
mod hardcoded_tmp_directory;
|
||||
mod hashlib_insecure_hash_functions;
|
||||
mod jinja2_autoescape_false;
|
||||
mod logging_config_insecure_listen;
|
||||
mod request_with_no_cert_validation;
|
||||
mod request_without_timeout;
|
||||
mod snmp_insecure_version;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: src/rules/flake8_bandit/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
LoggingConfigInsecureListen: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 25
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::unparse_stmt;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Generator;
|
||||
use crate::violations;
|
||||
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
@@ -48,10 +48,8 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
|
||||
|
||||
let mut diagnostic = Diagnostic::new(violations::DoNotAssertFalse, Range::from_located(test));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_stmt(&assertion_error(msg));
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_stmt(&assertion_error(msg), checker.stylist),
|
||||
stmt.location,
|
||||
stmt.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -3,11 +3,11 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::helpers::unparse_expr;
|
||||
use crate::ast::types::{CallPath, Range};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::source_code::Generator;
|
||||
use crate::violations;
|
||||
|
||||
fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
@@ -59,14 +59,12 @@ fn duplicate_handler_exceptions<'a>(
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
if unique_elts.len() == 1 {
|
||||
generator.unparse_expr(unique_elts[0], 0);
|
||||
} else {
|
||||
generator.unparse_expr(&type_pattern(unique_elts), 0);
|
||||
}
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
if unique_elts.len() == 1 {
|
||||
unparse_expr(unique_elts[0], checker.stylist)
|
||||
} else {
|
||||
unparse_expr(&type_pattern(unique_elts), checker.stylist)
|
||||
},
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
|
||||
|
||||
use crate::ast::helpers::unparse_expr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::python::identifiers::is_identifier;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Generator;
|
||||
use crate::violations;
|
||||
|
||||
fn attribute(value: &Expr, attr: &str) -> Expr {
|
||||
@@ -48,10 +48,8 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(violations::GetAttrWithConstant, Range::from_located(expr));
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(&attribute(obj, value), 0);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_expr(&attribute(obj, value), checker.stylist),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
|
||||
|
||||
use crate::ast::helpers::unparse_expr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::Generator;
|
||||
use crate::violations;
|
||||
|
||||
/// B013
|
||||
@@ -20,14 +20,12 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
|
||||
continue;
|
||||
};
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
violations::RedundantTupleInExceptionHandler(elt.to_string()),
|
||||
violations::RedundantTupleInExceptionHandler(unparse_expr(elt, checker.stylist)),
|
||||
Range::from_located(type_),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(elt, 0);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_expr(elt, checker.stylist),
|
||||
type_.location,
|
||||
type_.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::unparse_stmt;
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::python::identifiers::is_identifier;
|
||||
use crate::python::keyword::KWLIST;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::source_code::{Generator, Stylist};
|
||||
use crate::source_code::Stylist;
|
||||
use crate::violations;
|
||||
|
||||
fn assignment(obj: &Expr, name: &str, value: &Expr, stylist: &Stylist) -> String {
|
||||
@@ -27,9 +28,7 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, stylist: &Stylist) -> String
|
||||
type_comment: None,
|
||||
},
|
||||
);
|
||||
let mut generator: Generator = stylist.into();
|
||||
generator.unparse_stmt(&stmt);
|
||||
generator.generate()
|
||||
unparse_stmt(&stmt, stylist)
|
||||
}
|
||||
|
||||
/// B010
|
||||
|
||||
@@ -13,6 +13,12 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(Path::new("EXE001_1.py"); "EXE001_1")]
|
||||
#[test_case(Path::new("EXE001_2.py"); "EXE001_2")]
|
||||
#[test_case(Path::new("EXE001_3.py"); "EXE001_3")]
|
||||
#[test_case(Path::new("EXE002_1.py"); "EXE002_1")]
|
||||
#[test_case(Path::new("EXE002_2.py"); "EXE002_2")]
|
||||
#[test_case(Path::new("EXE002_3.py"); "EXE002_3")]
|
||||
#[test_case(Path::new("EXE003.py"); "EXE003")]
|
||||
#[test_case(Path::new("EXE004_1.py"); "EXE004_1")]
|
||||
#[test_case(Path::new("EXE004_2.py"); "EXE004_2")]
|
||||
@@ -27,6 +33,8 @@ mod tests {
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rules(vec![
|
||||
Rule::ShebangNotExecutable,
|
||||
Rule::ShebangMissingExecutableFile,
|
||||
Rule::ShebangWhitespace,
|
||||
Rule::ShebangNewline,
|
||||
Rule::ShebangPython,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub use shebang_missing::{shebang_missing, ShebangMissingExecutableFile};
|
||||
pub use shebang_newline::{shebang_newline, ShebangNewline};
|
||||
pub use shebang_not_executable::{shebang_not_executable, ShebangNotExecutable};
|
||||
pub use shebang_python::{shebang_python, ShebangPython};
|
||||
pub use shebang_whitespace::{shebang_whitespace, ShebangWhitespace};
|
||||
|
||||
mod shebang_missing;
|
||||
mod shebang_newline;
|
||||
mod shebang_not_executable;
|
||||
mod shebang_python;
|
||||
mod shebang_whitespace;
|
||||
|
||||
37
src/rules/flake8_executable/rules/shebang_missing.rs
Normal file
37
src/rules/flake8_executable/rules/shebang_missing.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use is_executable::IsExecutable;
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::ast::types::Range;
|
||||
use crate::define_violation;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct ShebangMissingExecutableFile;
|
||||
);
|
||||
impl Violation for ShebangMissingExecutableFile {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("The file is executable but no shebang is present")
|
||||
}
|
||||
}
|
||||
|
||||
/// EXE002
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn shebang_missing(filepath: &Path) -> Option<Diagnostic> {
|
||||
if filepath.is_executable() {
|
||||
let diagnostic = Diagnostic::new(ShebangMissingExecutableFile, Range::default());
|
||||
Some(diagnostic)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub fn shebang_missing(_filepath: &Path) -> Option<Diagnostic> {
|
||||
None
|
||||
}
|
||||
58
src/rules/flake8_executable/rules/shebang_not_executable.rs
Normal file
58
src/rules/flake8_executable/rules/shebang_not_executable.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use is_executable::IsExecutable;
|
||||
use ruff_macros::derive_message_formats;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rustpython_ast::Location;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::ast::types::Range;
|
||||
use crate::define_violation;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_executable::helpers::ShebangDirective;
|
||||
use crate::violation::Violation;
|
||||
|
||||
define_violation!(
|
||||
pub struct ShebangNotExecutable;
|
||||
);
|
||||
impl Violation for ShebangNotExecutable {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Shebang is present but file is not executable")
|
||||
}
|
||||
}
|
||||
|
||||
/// EXE001
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub fn shebang_not_executable(
|
||||
filepath: &Path,
|
||||
lineno: usize,
|
||||
shebang: &ShebangDirective,
|
||||
) -> Option<Diagnostic> {
|
||||
if let ShebangDirective::Match(_, start, end, _) = shebang {
|
||||
if filepath.is_executable() {
|
||||
None
|
||||
} else {
|
||||
let diagnostic = Diagnostic::new(
|
||||
ShebangNotExecutable,
|
||||
Range::new(
|
||||
Location::new(lineno + 1, *start),
|
||||
Location::new(lineno + 1, *end),
|
||||
),
|
||||
);
|
||||
Some(diagnostic)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub fn shebang_not_executable(
|
||||
_filepath: &Path,
|
||||
_lineno: usize,
|
||||
_shebang: &ShebangDirective,
|
||||
) -> Option<Diagnostic> {
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ShebangNotExecutable: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 2
|
||||
end_location:
|
||||
row: 1
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ShebangMissingExecutableFile: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 0
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/rules/flake8_executable/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Rules from [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/).
|
||||
pub(crate) mod rules;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -26,4 +27,24 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::SingleLineImplicitStringConcatenation, Path::new("ISC.py"); "ISC001")]
|
||||
#[test_case(Rule::MultiLineImplicitStringConcatenation, Path::new("ISC.py"); "ISC002")]
|
||||
#[test_case(Rule::ExplicitStringConcatenation, Path::new("ISC.py"); "ISC003")]
|
||||
fn multiline(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("multiline_{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_implicit_str_concat")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings {
|
||||
flake8_implicit_str_concat: super::settings::Settings {
|
||||
allow_multiline: false,
|
||||
},
|
||||
..settings::Settings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
insta::assert_yaml_snapshot!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,20 @@ use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::flake8_implicit_str_concat::settings::Settings;
|
||||
use crate::violations;
|
||||
|
||||
/// ISC001, ISC002
|
||||
pub fn implicit(tokens: &[LexResult]) -> Vec<Diagnostic> {
|
||||
pub fn implicit(tokens: &[LexResult], settings: &Settings) -> Vec<Diagnostic> {
|
||||
let mut diagnostics = vec![];
|
||||
for ((a_start, a_tok, a_end), (b_start, b_tok, b_end)) in
|
||||
tokens.iter().flatten().tuple_windows()
|
||||
for ((a_start, a_tok, a_end), (b_start, b_tok, b_end)) in tokens
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|(_, tok, _)| {
|
||||
!matches!(tok, Tok::Comment(..))
|
||||
&& (settings.allow_multiline || !matches!(tok, Tok::NonLogicalNewline))
|
||||
})
|
||||
.tuple_windows()
|
||||
{
|
||||
if matches!(a_tok, Tok::String { .. }) && matches!(b_tok, Tok::String { .. }) {
|
||||
if a_end.row() == b_start.row() {
|
||||
@@ -22,8 +29,6 @@ pub fn implicit(tokens: &[LexResult]) -> Vec<Diagnostic> {
|
||||
},
|
||||
));
|
||||
} else {
|
||||
// Not on the same line, and no NonLogicalNewline between a and b =>
|
||||
// concatantion over a continuation line.
|
||||
diagnostics.push(Diagnostic::new(
|
||||
violations::MultiLineImplicitStringConcatenation,
|
||||
Range {
|
||||
|
||||
57
src/rules/flake8_implicit_str_concat/settings.rs
Normal file
57
src/rules/flake8_implicit_str_concat/settings.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
//! Settings for the `flake8-implicit-str-concat` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema,
|
||||
)]
|
||||
#[serde(
|
||||
deny_unknown_fields,
|
||||
rename_all = "kebab-case",
|
||||
rename = "Flake8ImplicitStrConcatOptions"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
default = r#"true"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
allow-multiline = false
|
||||
"#
|
||||
)]
|
||||
/// Whether to allow implicit string concatenations for multiline strings.
|
||||
/// By default, implicit concatenations of multiline strings are
|
||||
/// allowed (but continuation lines, delimited with a backslash, are
|
||||
/// prohibited).
|
||||
pub allow_multiline: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Settings {
|
||||
pub allow_multiline: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allow_multiline: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Options> for Settings {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
allow_multiline: options.allow_multiline.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Settings> for Options {
|
||||
fn from(settings: Settings) -> Self {
|
||||
Self {
|
||||
allow_multiline: Some(settings.allow_multiline),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
source: src/rules/flake8_implicit_str_concat/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
SingleLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 4
|
||||
end_location:
|
||||
row: 1
|
||||
column: 11
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
SingleLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 8
|
||||
end_location:
|
||||
row: 1
|
||||
column: 15
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/rules/flake8_implicit_str_concat/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
MultiLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 6
|
||||
column: 9
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultiLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 24
|
||||
column: 2
|
||||
end_location:
|
||||
row: 25
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultiLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 29
|
||||
column: 2
|
||||
end_location:
|
||||
row: 30
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
MultiLineImplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 34
|
||||
column: 2
|
||||
end_location:
|
||||
row: 35
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/rules/flake8_implicit_str_concat/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
ExplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 17
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ExplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 2
|
||||
end_location:
|
||||
row: 10
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ExplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 14
|
||||
column: 2
|
||||
end_location:
|
||||
row: 15
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
ExplicitStringConcatenation: ~
|
||||
location:
|
||||
row: 19
|
||||
column: 2
|
||||
end_location:
|
||||
row: 20
|
||||
column: 8
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -12,10 +12,12 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(Rule::NoUnnecessaryPass, Path::new("PIE790.py"); "PIE790")]
|
||||
#[test_case(Rule::DupeClassFieldDefinitions, Path::new("PIE794.py"); "PIE794")]
|
||||
#[test_case(Rule::PreferUniqueEnums, Path::new("PIE796.py"); "PIE796")]
|
||||
#[test_case(Rule::NoUnnecessaryDictKwargs, Path::new("PIE804.py"); "PIE804")]
|
||||
#[test_case(Rule::NoUnnecessaryPass, Path::new("PIE790.py"); "PIE790")]
|
||||
#[test_case(Rule::NoUnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
|
||||
#[test_case(Rule::PreferListBuiltin, Path::new("PIE807.py"); "PIE807")]
|
||||
#[test_case(Rule::PreferUniqueEnums, Path::new("PIE796.py"); "PIE796")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use log::error;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::comparable::ComparableExpr;
|
||||
use crate::ast::helpers::unparse_expr;
|
||||
@@ -8,6 +8,7 @@ use crate::ast::types::{Range, RefEquality};
|
||||
use crate::autofix::helpers::delete_stmt;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
use crate::python::identifiers::is_identifier;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::violations;
|
||||
|
||||
@@ -152,6 +153,56 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// PIE800
|
||||
pub fn no_unnecessary_spread(checker: &mut Checker, keys: &[Option<Expr>], values: &[Expr]) {
|
||||
for item in keys.iter().zip(values.iter()) {
|
||||
if let (None, value) = item {
|
||||
// We only care about when the key is None which indicates a spread `**`
|
||||
// inside a dict.
|
||||
if let ExprKind::Dict { .. } = value.node {
|
||||
let diagnostic =
|
||||
Diagnostic::new(violations::NoUnnecessarySpread, Range::from_located(value));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if a key is a valid keyword argument name.
|
||||
fn is_valid_kwarg_name(key: &Expr) -> bool {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} = &key.node
|
||||
{
|
||||
is_identifier(value)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// PIE804
|
||||
pub fn no_unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs: &[Keyword]) {
|
||||
for kw in kwargs {
|
||||
// keyword is a spread operator (indicated by None)
|
||||
if kw.node.arg.is_none() {
|
||||
if let ExprKind::Dict { keys, .. } = &kw.node.value.node {
|
||||
// ensure foo(**{"bar-bar": 1}) doesn't error
|
||||
if keys.iter().all(|expr| expr.as_ref().map_or(false, is_valid_kwarg_name)) ||
|
||||
// handle case of foo(**{**bar})
|
||||
(keys.len() == 1 && keys[0].is_none())
|
||||
{
|
||||
let diagnostic = Diagnostic::new(
|
||||
violations::NoUnnecessaryDictKwargs,
|
||||
Range::from_located(expr),
|
||||
);
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PIE807
|
||||
pub fn prefer_list_builtin(checker: &mut Checker, expr: &Expr) {
|
||||
let ExprKind::Lambda { args, body } = &expr.node else {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 13
|
||||
end_location:
|
||||
row: 1
|
||||
column: 23
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 14
|
||||
end_location:
|
||||
row: 3
|
||||
column: 27
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 10
|
||||
end_location:
|
||||
row: 5
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessarySpread: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 18
|
||||
end_location:
|
||||
row: 7
|
||||
column: 27
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
source: src/rules/flake8_pie/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 20
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 21
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 35
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 38
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NoUnnecessaryDictKwargs: ~
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 29
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
||||
@@ -2,7 +2,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
|
||||
|
||||
use super::super::types;
|
||||
use super::helpers::{is_pytest_parametrize, split_names};
|
||||
use crate::ast::helpers::create_expr;
|
||||
use crate::ast::helpers::{create_expr, unparse_expr};
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::Fix;
|
||||
@@ -31,8 +31,7 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(
|
||||
Some(unparse_expr(
|
||||
&create_expr(ExprKind::Constant {
|
||||
value: Constant::Str(elts.iter().fold(String::new(), |mut acc, elt| {
|
||||
if let ExprKind::Constant {
|
||||
@@ -49,9 +48,8 @@ fn elts_to_csv(elts: &[Expr], checker: &Checker) -> Option<String> {
|
||||
})),
|
||||
kind: None,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
Some(generator.generate())
|
||||
checker.stylist,
|
||||
))
|
||||
}
|
||||
|
||||
/// PT006
|
||||
@@ -102,24 +100,22 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(
|
||||
&create_expr(ExprKind::List {
|
||||
elts: names
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
create_expr(ExprKind::Constant {
|
||||
value: Constant::Str(name.to_string()),
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_expr(
|
||||
&create_expr(ExprKind::List {
|
||||
elts: names
|
||||
.iter()
|
||||
.map(|&name| {
|
||||
create_expr(ExprKind::Constant {
|
||||
value: Constant::Str(name.to_string()),
|
||||
kind: None,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
checker.stylist,
|
||||
),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
@@ -144,16 +140,14 @@ fn check_names(checker: &mut Checker, expr: &Expr) {
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(
|
||||
&create_expr(ExprKind::List {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_expr(
|
||||
&create_expr(ExprKind::List {
|
||||
elts: elts.clone(),
|
||||
ctx: ExprContext::Load,
|
||||
}),
|
||||
checker.stylist,
|
||||
),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
@@ -285,10 +279,8 @@ fn handle_single_name(checker: &mut Checker, expr: &Expr, value: &Expr) {
|
||||
);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut generator: Generator = checker.stylist.into();
|
||||
generator.unparse_expr(&create_expr(value.node.clone()), 0);
|
||||
diagnostic.amend(Fix::replacement(
|
||||
generator.generate(),
|
||||
unparse_expr(&create_expr(value.node.clone()), checker.stylist),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -325,6 +325,11 @@ pub fn function(checker: &mut Checker, body: &[Stmt]) {
|
||||
visitor.stack
|
||||
};
|
||||
|
||||
// Avoid false positives for generators.
|
||||
if !stack.yields.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if checker.settings.rules.enabled(&Rule::SuperfluousElseReturn)
|
||||
|| checker.settings.rules.enabled(&Rule::SuperfluousElseRaise)
|
||||
|| checker
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user