Compare commits
1 Commits
v0.1.7
...
charlie/sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5120b36b67 |
94
CHANGELOG.md
94
CHANGELOG.md
@@ -1,99 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Preview features
|
||||
|
||||
- Implement multiline dictionary and list hugging for preview style ([#8293](https://github.com/astral-sh/ruff/pull/8293))
|
||||
- Implement the `fix_power_op_line_length` preview style ([#8947](https://github.com/astral-sh/ruff/pull/8947))
|
||||
- Use Python version to determine typing rewrite safety ([#8919](https://github.com/astral-sh/ruff/pull/8919))
|
||||
- \[`flake8-annotations`\] Enable auto-return-type involving `Optional` and `Union` annotations ([#8885](https://github.com/astral-sh/ruff/pull/8885))
|
||||
- \[`flake8-bandit`\] Implement `django-raw-sql` (`S611`) ([#8651](https://github.com/astral-sh/ruff/pull/8651))
|
||||
- \[`flake8-bandit`\] Implement `tarfile-unsafe-members` (`S202`) ([#8829](https://github.com/astral-sh/ruff/pull/8829))
|
||||
- \[`flake8-pyi`\] Implement fix for `unnecessary-literal-union` (`PYI030`) ([#7934](https://github.com/astral-sh/ruff/pull/7934))
|
||||
- \[`flake8-simplify`\] Extend `dict-get-with-none-default` (`SIM910`) to non-literals ([#8762](https://github.com/astral-sh/ruff/pull/8762))
|
||||
- \[`pylint`\] - add `unnecessary-list-index-lookup` (`PLR1736`) + autofix ([#7999](https://github.com/astral-sh/ruff/pull/7999))
|
||||
- \[`pylint`\] - implement R0202 and R0203 with autofixes ([#8335](https://github.com/astral-sh/ruff/pull/8335))
|
||||
- \[`pylint`\] Implement `repeated-keyword` (`PLe1132`) ([#8706](https://github.com/astral-sh/ruff/pull/8706))
|
||||
- \[`pylint`\] Implement `too-many-positional` (`PLR0917`) ([#8995](https://github.com/astral-sh/ruff/pull/8995))
|
||||
- \[`pylint`\] Implement `unnecessary-dict-index-lookup` (`PLR1733`) ([#8036](https://github.com/astral-sh/ruff/pull/8036))
|
||||
- \[`refurb`\] Implement `redundant-log-base` (`FURB163`) ([#8842](https://github.com/astral-sh/ruff/pull/8842))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-boolean-trap`\] Allow booleans in `@override` methods ([#8882](https://github.com/astral-sh/ruff/pull/8882))
|
||||
- \[`flake8-bugbear`\] Avoid `B015`,`B018` for last expression in a cell ([#8815](https://github.com/astral-sh/ruff/pull/8815))
|
||||
- \[`flake8-pie`\] Allow ellipses for enum values in stub files ([#8825](https://github.com/astral-sh/ruff/pull/8825))
|
||||
- \[`flake8-pyi`\] Check PEP 695 type aliases for `snake-case-type-alias` and `t-suffixed-type-alias` ([#8966](https://github.com/astral-sh/ruff/pull/8966))
|
||||
- \[`flake8-pyi`\] Check for kwarg and vararg `NoReturn` type annotations ([#8948](https://github.com/astral-sh/ruff/pull/8948))
|
||||
- \[`flake8-simplify`\] Omit select context managers from `SIM117` ([#8801](https://github.com/astral-sh/ruff/pull/8801))
|
||||
- \[`pep8-naming`\] Allow Django model loads in `non-lowercase-variable-in-function` (`N806`) ([#8917](https://github.com/astral-sh/ruff/pull/8917))
|
||||
- \[`pycodestyle`\] Avoid `E703` for last expression in a cell ([#8821](https://github.com/astral-sh/ruff/pull/8821))
|
||||
- \[`pycodestyle`\] Update `E402` to work at cell level for notebooks ([#8872](https://github.com/astral-sh/ruff/pull/8872))
|
||||
- \[`pydocstyle`\] Avoid `D100` for Jupyter Notebooks ([#8816](https://github.com/astral-sh/ruff/pull/8816))
|
||||
- \[`pylint`\] Implement fix for `unspecified-encoding` (`PLW1514`) ([#8928](https://github.com/astral-sh/ruff/pull/8928))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Avoid unstable formatting in ellipsis-only body with trailing comment ([#8984](https://github.com/astral-sh/ruff/pull/8984))
|
||||
- Inline trailing comments for type alias similar to assignments ([#8941](https://github.com/astral-sh/ruff/pull/8941))
|
||||
- Insert trailing comma when function breaks with single argument ([#8921](https://github.com/astral-sh/ruff/pull/8921))
|
||||
|
||||
### CLI
|
||||
|
||||
- Update `ruff check` and `ruff format` to default to the current directory ([#8791](https://github.com/astral-sh/ruff/pull/8791))
|
||||
- Stop at the first resolved parent configuration ([#8864](https://github.com/astral-sh/ruff/pull/8864))
|
||||
|
||||
### Configuration
|
||||
|
||||
- \[`pylint`\] Default `max-positional-args` to `max-args` ([#8998](https://github.com/astral-sh/ruff/pull/8998))
|
||||
- \[`pylint`\] Add `allow-dunder-method-names` setting for `bad-dunder-method-name` (`PLW3201`) ([#8812](https://github.com/astral-sh/ruff/pull/8812))
|
||||
- \[`isort`\] Add support for `from-first` setting ([#8663](https://github.com/astral-sh/ruff/pull/8663))
|
||||
- \[`isort`\] Add support for `length-sort` settings ([#8841](https://github.com/astral-sh/ruff/pull/8841))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add support for `@functools.singledispatch` ([#8934](https://github.com/astral-sh/ruff/pull/8934))
|
||||
- Avoid off-by-one error in stripping noqa following multi-byte char ([#8979](https://github.com/astral-sh/ruff/pull/8979))
|
||||
- Avoid off-by-one error in with-item named expressions ([#8915](https://github.com/astral-sh/ruff/pull/8915))
|
||||
- Avoid syntax error via invalid ur string prefix ([#8971](https://github.com/astral-sh/ruff/pull/8971))
|
||||
- Avoid underflow in `get_model` matching ([#8965](https://github.com/astral-sh/ruff/pull/8965))
|
||||
- Avoid unnecessary index diagnostics when value is modified ([#8970](https://github.com/astral-sh/ruff/pull/8970))
|
||||
- Convert over-indentation rule to use number of characters ([#8983](https://github.com/astral-sh/ruff/pull/8983))
|
||||
- Detect implicit returns in auto-return-types ([#8952](https://github.com/astral-sh/ruff/pull/8952))
|
||||
- Fix start >= end error in over-indentation ([#8982](https://github.com/astral-sh/ruff/pull/8982))
|
||||
- Ignore `@overload` and `@override` methods for too-many-arguments checks ([#8954](https://github.com/astral-sh/ruff/pull/8954))
|
||||
- Lexer start of line is false only for `Mode::Expression` ([#8880](https://github.com/astral-sh/ruff/pull/8880))
|
||||
- Mark `pydantic_settings.BaseSettings` as having default copy semantics ([#8793](https://github.com/astral-sh/ruff/pull/8793))
|
||||
- Respect dictionary unpacking in `NamedTuple` assignments ([#8810](https://github.com/astral-sh/ruff/pull/8810))
|
||||
- Respect local subclasses in `flake8-type-checking` ([#8768](https://github.com/astral-sh/ruff/pull/8768))
|
||||
- Support type alias statements in simple statement positions ([#8916](https://github.com/astral-sh/ruff/pull/8916))
|
||||
- \[`flake8-annotations`\] Avoid filtering out un-representable types in return annotation ([#8881](https://github.com/astral-sh/ruff/pull/8881))
|
||||
- \[`flake8-pie`\] Retain extra ellipses in protocols and abstract methods ([#8769](https://github.com/astral-sh/ruff/pull/8769))
|
||||
- \[`flake8-pyi`\] Respect local enum subclasses in `simple-defaults` (`PYI052`) ([#8767](https://github.com/astral-sh/ruff/pull/8767))
|
||||
- \[`flake8-trio`\] Use correct range for `TRIO115` fix ([#8933](https://github.com/astral-sh/ruff/pull/8933))
|
||||
- \[`flake8-trio`\] Use full arguments range for zero-sleep-call ([#8936](https://github.com/astral-sh/ruff/pull/8936))
|
||||
- \[`isort`\] fix: mark `__main__` as first-party import ([#8805](https://github.com/astral-sh/ruff/pull/8805))
|
||||
- \[`pep8-naming`\] Avoid `N806` errors for type alias statements ([#8785](https://github.com/astral-sh/ruff/pull/8785))
|
||||
- \[`perflint`\] Avoid `PERF101` if there's an append in loop body ([#8809](https://github.com/astral-sh/ruff/pull/8809))
|
||||
- \[`pycodestyle`\] Allow space-before-colon after end-of-slice ([#8838](https://github.com/astral-sh/ruff/pull/8838))
|
||||
- \[`pydocstyle`\] Avoid non-character breaks in `over-indentation` (`D208`) ([#8866](https://github.com/astral-sh/ruff/pull/8866))
|
||||
- \[`pydocstyle`\] Ignore underlines when determining docstring logical lines ([#8929](https://github.com/astral-sh/ruff/pull/8929))
|
||||
- \[`pylint`\] Extend `self-assigning-variable` to multi-target assignments ([#8839](https://github.com/astral-sh/ruff/pull/8839))
|
||||
- \[`tryceratops`\] Avoid repeated triggers in nested `tryceratops` diagnostics ([#8772](https://github.com/astral-sh/ruff/pull/8772))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add advice for fixing RUF008 when mutability is not desired ([#8853](https://github.com/astral-sh/ruff/pull/8853))
|
||||
- Added the command to run ruff using pkgx to the installation.md ([#8955](https://github.com/astral-sh/ruff/pull/8955))
|
||||
- Document fix safety for flake8-comprehensions and some pyupgrade rules ([#8918](https://github.com/astral-sh/ruff/pull/8918))
|
||||
- Fix doc formatting for zero-sleep-call ([#8937](https://github.com/astral-sh/ruff/pull/8937))
|
||||
- Remove duplicate imports from os-stat documentation ([#8930](https://github.com/astral-sh/ruff/pull/8930))
|
||||
- Replace generated reference to MkDocs ([#8806](https://github.com/astral-sh/ruff/pull/8806))
|
||||
- Update Arch Linux package URL in installation.md ([#8802](https://github.com/astral-sh/ruff/pull/8802))
|
||||
- \[`flake8-pyi`\] Fix error in `t-suffixed-type-alias` (`PYI043`) example ([#8963](https://github.com/astral-sh/ruff/pull/8963))
|
||||
- \[`flake8-pyi`\] Improve motivation for `custom-type-var-return-type` (`PYI019`) ([#8766](https://github.com/astral-sh/ruff/pull/8766))
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Preview features
|
||||
|
||||
64
Cargo.lock
generated
64
Cargo.lock
generated
@@ -808,7 +808,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -848,18 +848,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "2.11.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
|
||||
checksum = "fb5fd9bcbe8b1087cbd395b51498c01bc997cef73e778a80b77a811af5e2d29f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -987,9 +987,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
@@ -1170,9 +1170,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.66"
|
||||
version = "0.3.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
|
||||
checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1622,9 +1622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
@@ -2062,7 +2062,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.2",
|
||||
"anyhow",
|
||||
@@ -2198,7 +2198,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2450,7 +2450,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2618,9 +2618,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.16"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -2630,9 +2630,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.16"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3334,9 +3334,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.9.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
@@ -3350,9 +3350,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -3461,9 +3461,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.89"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||
checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -3471,9 +3471,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.89"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||
checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -3498,9 +3498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||
checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3508,9 +3508,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.89"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3521,9 +3521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.89"
|
||||
version = "0.2.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
|
||||
@@ -33,7 +33,7 @@ proc-macro2 = { version = "1.0.70" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.16" }
|
||||
schemars = { version = "0.8.15" }
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
serde_json = { version = "1.0.108" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
|
||||
@@ -150,7 +150,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.7
|
||||
rev: v0.1.6
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -34,8 +34,8 @@ harness = false
|
||||
once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
url = "2.5.0"
|
||||
ureq = "2.9.1"
|
||||
url = "2.3.1"
|
||||
ureq = "2.8.0"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
codspeed-criterion-compat = { version="2.3.3", default-features = false, optional = true}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_benchmark::criterion::{
|
||||
criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
||||
};
|
||||
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_python_formatter::{format_module_ast, PreviewMode, PyFormatOptions};
|
||||
use ruff_python_formatter::{format_module_ast, PyFormatOptions};
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
@@ -69,8 +69,7 @@ fn benchmark_formatter(criterion: &mut Criterion) {
|
||||
.expect("Input to be a valid python program");
|
||||
|
||||
b.iter(|| {
|
||||
let options = PyFormatOptions::from_extension(Path::new(case.name()))
|
||||
.with_preview(PreviewMode::Enabled);
|
||||
let options = PyFormatOptions::from_extension(Path::new(case.name()));
|
||||
let formatted =
|
||||
format_module_ast(&module, &comment_ranges, case.code(), options)
|
||||
.expect("Formatting to succeed");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -69,7 +69,7 @@ insta = { workspace = true, features = ["filters", "json"] }
|
||||
insta-cmd = { version = "0.4.0" }
|
||||
tempfile = "3.8.1"
|
||||
test-case = { workspace = true }
|
||||
ureq = { version = "2.9.1", features = [] }
|
||||
ureq = { version = "2.8.0", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.7"
|
||||
version = "0.1.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -39,18 +39,3 @@ def func():
|
||||
for i in range(1110):
|
||||
if True:
|
||||
break
|
||||
|
||||
# TODO(charlie): The `pass` here does not get properly redirected to the top of the
|
||||
# loop, unlike below.
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
x = 1
|
||||
|
||||
@@ -129,11 +129,3 @@ def func():
|
||||
print("Grass is green")
|
||||
case Color.BLUE:
|
||||
print("I'm feeling the blues :(")
|
||||
|
||||
|
||||
def func(point):
|
||||
match point:
|
||||
case (0, 0):
|
||||
print("Origin")
|
||||
case foo:
|
||||
raise ValueError("oops")
|
||||
|
||||
@@ -63,87 +63,3 @@ def func(x: int):
|
||||
return "str"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x:
|
||||
return 1
|
||||
|
||||
|
||||
def func():
|
||||
x = 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
if x > 0:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
match x:
|
||||
case [1, 2, 3]:
|
||||
return 1
|
||||
case 4 as y:
|
||||
return "foo"
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
return 1
|
||||
else:
|
||||
return 4
|
||||
|
||||
|
||||
def func(x: int):
|
||||
for i in range(5):
|
||||
if i > 0:
|
||||
break
|
||||
else:
|
||||
return 4
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
finally:
|
||||
return 2
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
|
||||
def func(x: int):
|
||||
try:
|
||||
return 1
|
||||
except:
|
||||
return 2
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def func(x: int):
|
||||
while x > 0:
|
||||
break
|
||||
return 1
|
||||
|
||||
@@ -22,7 +22,3 @@ Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type foo_bar = int | str
|
||||
type FooBar = int | str
|
||||
|
||||
@@ -22,7 +22,3 @@ Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type foo_bar = int | str
|
||||
type FooBar = int | str
|
||||
|
||||
@@ -21,7 +21,3 @@ _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type _FooT = str | int
|
||||
type Foo = str | int
|
||||
|
||||
@@ -21,7 +21,3 @@ _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
# PEP 695
|
||||
type _FooT = str | int
|
||||
type Foo = str | int
|
||||
|
||||
@@ -10,14 +10,3 @@ def foo_no_return_typing_extensions(
|
||||
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_never(arg: Never): ...
|
||||
def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_int_args(*args: int): ...
|
||||
def foo_int_kwargs(**kwargs: int): ...
|
||||
def foo_int_args_kwargs(*args: int, **kwargs: int): ...
|
||||
def foo_int_args_no_return(*args: int, **kwargs: NoReturn): ... # Error: PYI050
|
||||
def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI050
|
||||
def foo_args_never(*args: Never): ...
|
||||
def foo_kwargs_never(**kwargs: Never): ...
|
||||
def foo_args_kwargs_never(*args: Never, **kwargs: Never): ...
|
||||
|
||||
@@ -52,6 +52,3 @@ def model_assign() -> None:
|
||||
|
||||
Bad = import_string("django.core.exceptions.ValidationError") # N806
|
||||
ValidationError = import_string("django.core.exceptions.ValidationError") # OK
|
||||
|
||||
Bad = apps.get_model() # N806
|
||||
Bad = apps.get_model(model_name="Stream") # N806
|
||||
|
||||
@@ -43,6 +43,3 @@ regex = '''
|
||||
''' # noqa
|
||||
|
||||
regex = '\\\_'
|
||||
|
||||
#: W605:1:7
|
||||
u'foo\ bar'
|
||||
|
||||
@@ -1,54 +1,40 @@
|
||||
# Same as `W605_0.py` but using f-strings instead.
|
||||
|
||||
#: W605:1:10
|
||||
regex = f'\.png$'
|
||||
regex = '\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = f'''
|
||||
regex = '''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
f'\_'
|
||||
'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
f"""
|
||||
"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = f'new line\nand invalid escape \_ here'
|
||||
|
||||
def f():
|
||||
#: W605:1:11
|
||||
return'\.png$'
|
||||
|
||||
#: Okay
|
||||
regex = fr'\.png$'
|
||||
regex = f'\\.png$'
|
||||
regex = fr'''
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
regex = r'''
|
||||
\.png$
|
||||
'''
|
||||
regex = fr'''
|
||||
regex = r'''
|
||||
\\.png$
|
||||
'''
|
||||
s = f'\\'
|
||||
regex = f'\w' # noqa
|
||||
regex = f'''
|
||||
s = '\\'
|
||||
regex = '\w' # noqa
|
||||
regex = '''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = f'\\\_'
|
||||
value = f'\{{1}}'
|
||||
value = f'\{1}'
|
||||
value = f'{1:\}'
|
||||
value = f"{f"\{1}"}"
|
||||
value = rf"{f"\{1}"}"
|
||||
|
||||
# Okay
|
||||
value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
|
||||
54
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_2.py
vendored
Normal file
54
crates/ruff_linter/resources/test/fixtures/pycodestyle/W605_2.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# Same as `W605_0.py` but using f-strings instead.
|
||||
|
||||
#: W605:1:10
|
||||
regex = f'\.png$'
|
||||
|
||||
#: W605:2:1
|
||||
regex = f'''
|
||||
\.png$
|
||||
'''
|
||||
|
||||
#: W605:2:6
|
||||
f(
|
||||
f'\_'
|
||||
)
|
||||
|
||||
#: W605:4:6
|
||||
f"""
|
||||
multi-line
|
||||
literal
|
||||
with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = f'new line\nand invalid escape \_ here'
|
||||
|
||||
|
||||
#: Okay
|
||||
regex = fr'\.png$'
|
||||
regex = f'\\.png$'
|
||||
regex = fr'''
|
||||
\.png$
|
||||
'''
|
||||
regex = fr'''
|
||||
\\.png$
|
||||
'''
|
||||
s = f'\\'
|
||||
regex = f'\w' # noqa
|
||||
regex = f'''
|
||||
\w
|
||||
''' # noqa
|
||||
|
||||
regex = f'\\\_'
|
||||
value = f'\{{1}}'
|
||||
value = f'\{1}'
|
||||
value = f'{1:\}'
|
||||
value = f"{f"\{1}"}"
|
||||
value = rf"{f"\{1}"}"
|
||||
|
||||
# Okay
|
||||
value = rf'\{{1}}'
|
||||
value = rf'\{1}'
|
||||
value = rf'{1:\}'
|
||||
value = f"{rf"\{1}"}"
|
||||
@@ -1,16 +1,5 @@
|
||||
"""
|
||||
Author
|
||||
"""
|
||||
|
||||
|
||||
class Platform:
|
||||
""" Remove sampler
|
||||
Args:
|
||||
Returns:
|
||||
"""
|
||||
|
||||
|
||||
def memory_test():
|
||||
"""
|
||||
参数含义:precision:精确到小数点后几位
|
||||
"""
|
||||
|
||||
@@ -32,16 +32,3 @@ def f(x, y, z, *, u, v, w): # Too many arguments (6/5)
|
||||
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many arguments (9/5)
|
||||
pass
|
||||
|
||||
|
||||
from typing import override, overload
|
||||
|
||||
|
||||
@override
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
|
||||
@overload
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
def f(x, y, z, t, u, v, w, r): # Too many positional arguments (8/3)
|
||||
pass
|
||||
|
||||
|
||||
def f(x): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, _t, _u, _v, _w, r): # OK (underscore-prefixed names are ignored
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, *, u=1, v=1, r=1): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x=1, y=1, z=1): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, /, u, v, w): # Too many positional arguments (6/3)
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, *, u, v, w): # OK
|
||||
pass
|
||||
|
||||
|
||||
def f(x, y, z, a, b, c, *, u, v, w): # Too many positional arguments (6/3)
|
||||
pass
|
||||
@@ -1,10 +0,0 @@
|
||||
# Too many positional arguments (7/4) for max_positional=4
|
||||
# OK for dummy_variable_rgx ~ "skip_.*"
|
||||
def f(w, x, y, z, skip_t, skip_u, skip_v):
|
||||
pass
|
||||
|
||||
|
||||
# Too many positional arguments (7/4) for max_args=4
|
||||
# Too many positional arguments (7/3) for dummy_variable_rgx ~ "skip_.*"
|
||||
def f(w, x, y, z, t, u, v):
|
||||
pass
|
||||
@@ -1,40 +0,0 @@
|
||||
FRUITS = {"apple": 1, "orange": 10, "berry": 22}
|
||||
|
||||
def fix_these():
|
||||
[FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
{FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
{fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
print(FRUITS[fruit_name]) # PLR1733
|
||||
blah = FRUITS[fruit_name] # PLR1733
|
||||
assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
|
||||
|
||||
def dont_fix_these():
|
||||
# once there is an assignment to the dict[index], we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
FRUITS[fruit_name] = 0 # OK
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
# once there is an assignment to the key, we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
fruit_name = 0 # OK
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
# once there is an assignment to the value, we stop emitting diagnostics
|
||||
for fruit_name, fruit_count in FRUITS.items():
|
||||
if fruit_count < 5:
|
||||
fruit_count = -fruit_count
|
||||
assert FRUITS[fruit_name] == 0 # OK
|
||||
|
||||
|
||||
def value_intentionally_unused():
|
||||
[FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()] # OK
|
||||
{FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()} # OK
|
||||
{fruit_name: FRUITS[fruit_name] for fruit_name, _ in FRUITS.items()} # OK
|
||||
|
||||
for fruit_name, _ in FRUITS.items():
|
||||
print(FRUITS[fruit_name]) # OK
|
||||
blah = FRUITS[fruit_name] # OK
|
||||
assert FRUITS[fruit_name] == "pear" # OK
|
||||
@@ -12,7 +12,7 @@ def fix_these():
|
||||
print(letters[index]) # PLR1736
|
||||
blah = letters[index] # PLR1736
|
||||
assert letters[index] == "d" # PLR1736
|
||||
|
||||
|
||||
for index, letter in builtins.enumerate(letters):
|
||||
print(letters[index]) # PLR1736
|
||||
blah = letters[index] # PLR1736
|
||||
@@ -22,43 +22,38 @@ def fix_these():
|
||||
def dont_fix_these():
|
||||
# once there is an assignment to the sequence[index], we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letters[index] = "d" # OK
|
||||
letters[index] += "e" # OK
|
||||
assert letters[index] == "de" # OK
|
||||
|
||||
letters[index] = "d" # Ok
|
||||
letters[index] += "e" # Ok
|
||||
assert letters[index] == "de" # Ok
|
||||
|
||||
# once there is an assignment to the index, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
index += 1 # OK
|
||||
print(letters[index]) # OK
|
||||
|
||||
index += 1 # Ok
|
||||
print(letters[index]) # Ok
|
||||
|
||||
# once there is an assignment to the sequence, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letters = ["d", "e", "f"] # OK
|
||||
print(letters[index]) # OK
|
||||
|
||||
# once there is an assignment to the value, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
letter = "d"
|
||||
print(letters[index]) # OK
|
||||
letters = ["d", "e", "f"] # Ok
|
||||
print(letters[index]) # Ok
|
||||
|
||||
# once there is an deletion from or of the sequence or index, we stop emitting diagnostics
|
||||
for index, letter in enumerate(letters):
|
||||
del letters[index] # OK
|
||||
print(letters[index]) # OK
|
||||
del letters[index] # Ok
|
||||
print(letters[index]) # Ok
|
||||
for index, letter in enumerate(letters):
|
||||
del letters # OK
|
||||
print(letters[index]) # OK
|
||||
del letters # Ok
|
||||
print(letters[index]) # Ok
|
||||
for index, letter in enumerate(letters):
|
||||
del index # OK
|
||||
print(letters[index]) # OK
|
||||
del index # Ok
|
||||
print(letters[index]) # Ok
|
||||
|
||||
|
||||
def value_intentionally_unused():
|
||||
[letters[index] for index, _ in enumerate(letters)] # OK
|
||||
{letters[index] for index, _ in enumerate(letters)} # OK
|
||||
{index: letters[index] for index, _ in enumerate(letters)} # OK
|
||||
[letters[index] for index, _ in enumerate(letters)] # Ok
|
||||
{letters[index] for index, _ in enumerate(letters)} # Ok
|
||||
{index: letters[index] for index, _ in enumerate(letters)} # Ok
|
||||
|
||||
for index, _ in enumerate(letters):
|
||||
print(letters[index]) # OK
|
||||
blah = letters[index] # OK
|
||||
letters[index] = "d" # OK
|
||||
print(letters[index]) # Ok
|
||||
blah = letters[index] # Ok
|
||||
letters[index] = "d" # Ok
|
||||
|
||||
@@ -42,30 +42,3 @@ tempfile.SpooledTemporaryFile(0, "w", encoding="utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "w", -1, "utf-8")
|
||||
tempfile.SpooledTemporaryFile(0, "wb")
|
||||
tempfile.SpooledTemporaryFile(0, )
|
||||
|
||||
open("test.txt",)
|
||||
open()
|
||||
open(
|
||||
"test.txt", # comment
|
||||
)
|
||||
open(
|
||||
"test.txt",
|
||||
# comment
|
||||
)
|
||||
open(("test.txt"),)
|
||||
open(
|
||||
("test.txt"), # comment
|
||||
)
|
||||
open(
|
||||
("test.txt"),
|
||||
# comment
|
||||
)
|
||||
|
||||
open((("test.txt")),)
|
||||
open(
|
||||
(("test.txt")), # comment
|
||||
)
|
||||
open(
|
||||
(("test.txt")),
|
||||
# comment
|
||||
)
|
||||
|
||||
@@ -23,6 +23,3 @@ print(a) # noqa: E501, F821 # comment
|
||||
print(a) # noqa: E501, F821 # comment
|
||||
print(a) # noqa: E501, F821 comment
|
||||
print(a) # noqa: E501, F821 comment
|
||||
|
||||
print(a) # comment with unicode µ # noqa: E501
|
||||
print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
||||
@@ -1333,9 +1333,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1363,9 +1360,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_list_set_comprehension(
|
||||
checker, expr, elt, generators,
|
||||
@@ -1392,9 +1386,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryComprehension) {
|
||||
flake8_comprehensions::rules::unnecessary_dict_comprehension(
|
||||
checker, expr, key, value, generators,
|
||||
@@ -1422,9 +1413,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionUsesLoopVariable) {
|
||||
flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr));
|
||||
}
|
||||
|
||||
@@ -248,10 +248,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyArguments) {
|
||||
pylint::rules::too_many_arguments(checker, function_def);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyPositional) {
|
||||
pylint::rules::too_many_positional(checker, parameters, stmt);
|
||||
pylint::rules::too_many_arguments(checker, parameters, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyReturnStatements) {
|
||||
if let Some(diagnostic) = pylint::rules::too_many_return_statements(
|
||||
@@ -1283,9 +1280,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UnnecessaryListIndexLookup) {
|
||||
pylint::rules::unnecessary_list_index_lookup(checker, for_stmt);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictIndexLookup) {
|
||||
pylint::rules::unnecessary_dict_index_lookup(checker, for_stmt);
|
||||
}
|
||||
if !is_async {
|
||||
if checker.enabled(Rule::ReimplementedBuiltin) {
|
||||
flake8_simplify::rules::convert_for_loop_to_any_all(checker, stmt);
|
||||
@@ -1537,14 +1531,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias { name, .. }) => {
|
||||
if checker.enabled(Rule::SnakeCaseTypeAlias) {
|
||||
flake8_pyi::rules::snake_case_type_alias(checker, name);
|
||||
}
|
||||
if checker.enabled(Rule::TSuffixedTypeAlias) {
|
||||
flake8_pyi::rules::t_suffixed_type_alias(checker, name);
|
||||
}
|
||||
}
|
||||
Stmt::Delete(delete @ ast::StmtDelete { targets, range: _ }) => {
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
for target in targets {
|
||||
|
||||
@@ -816,7 +816,7 @@ where
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
// Step 0: Pre-processing
|
||||
if !self.semantic.in_f_string()
|
||||
&& !self.semantic.in_typing_literal()
|
||||
&& !self.semantic.in_literal()
|
||||
&& !self.semantic.in_deferred_type_definition()
|
||||
&& self.semantic.in_type_definition()
|
||||
&& self.semantic.future_annotations()
|
||||
@@ -1198,7 +1198,7 @@ where
|
||||
) {
|
||||
// Ex) Literal["Class"]
|
||||
Some(typing::SubscriptKind::Literal) => {
|
||||
self.semantic.flags |= SemanticModelFlags::TYPING_LITERAL;
|
||||
self.semantic.flags |= SemanticModelFlags::LITERAL;
|
||||
|
||||
self.visit_expr(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
@@ -1239,7 +1239,7 @@ where
|
||||
}
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
|
||||
if self.semantic.in_type_definition()
|
||||
&& !self.semantic.in_typing_literal()
|
||||
&& !self.semantic.in_literal()
|
||||
&& !self.semantic.in_f_string()
|
||||
{
|
||||
self.deferred.string_type_definitions.push((
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
use std::path::Path;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||
use ruff_python_trivia::{CommentRanges, PythonWhitespace};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
use crate::noqa;
|
||||
@@ -200,11 +200,17 @@ fn delete_noqa(range: TextRange, locator: &Locator) -> Edit {
|
||||
|
||||
// Compute the leading space.
|
||||
let prefix = locator.slice(TextRange::new(line_range.start(), range.start()));
|
||||
let leading_space_len = prefix.text_len() - prefix.trim_whitespace_end().text_len();
|
||||
let leading_space = prefix
|
||||
.rfind(|c: char| !c.is_whitespace())
|
||||
.map_or(prefix.len(), |i| prefix.len() - i - 1);
|
||||
let leading_space_len = TextSize::try_from(leading_space).unwrap();
|
||||
|
||||
// Compute the trailing space.
|
||||
let suffix = locator.slice(TextRange::new(range.end(), line_range.end()));
|
||||
let trailing_space_len = suffix.text_len() - suffix.trim_whitespace_start().text_len();
|
||||
let trailing_space = suffix
|
||||
.find(|c: char| !c.is_whitespace())
|
||||
.map_or(suffix.len(), |i| i);
|
||||
let trailing_space_len = TextSize::try_from(trailing_space).unwrap();
|
||||
|
||||
// Ex) `# noqa`
|
||||
if line_range
|
||||
|
||||
@@ -254,14 +254,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "R0913") => (RuleGroup::Stable, rules::pylint::rules::TooManyArguments),
|
||||
(Pylint, "R0915") => (RuleGroup::Stable, rules::pylint::rules::TooManyStatements),
|
||||
(Pylint, "R0916") => (RuleGroup::Preview, rules::pylint::rules::TooManyBooleanExpressions),
|
||||
(Pylint, "R0917") => (RuleGroup::Preview, rules::pylint::rules::TooManyPositional),
|
||||
(Pylint, "R1701") => (RuleGroup::Stable, rules::pylint::rules::RepeatedIsinstanceCalls),
|
||||
(Pylint, "R1704") => (RuleGroup::Preview, rules::pylint::rules::RedefinedArgumentFromLocal),
|
||||
(Pylint, "R1711") => (RuleGroup::Stable, rules::pylint::rules::UselessReturn),
|
||||
(Pylint, "R1714") => (RuleGroup::Stable, rules::pylint::rules::RepeatedEqualityComparison),
|
||||
(Pylint, "R1706") => (RuleGroup::Preview, rules::pylint::rules::AndOrTernary),
|
||||
(Pylint, "R1722") => (RuleGroup::Stable, rules::pylint::rules::SysExitAlias),
|
||||
(Pylint, "R1733") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryDictIndexLookup),
|
||||
(Pylint, "R1736") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryListIndexLookup),
|
||||
(Pylint, "R2004") => (RuleGroup::Stable, rules::pylint::rules::MagicValueComparison),
|
||||
(Pylint, "R5501") => (RuleGroup::Stable, rules::pylint::rules::CollapsibleElseIf),
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Stmt};
|
||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::{
|
||||
has_leading_content, is_python_whitespace, CommentRanges, PythonWhitespace, SimpleTokenKind,
|
||||
SimpleTokenizer,
|
||||
has_leading_content, is_python_whitespace, PythonWhitespace, SimpleTokenKind, SimpleTokenizer,
|
||||
};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
@@ -140,32 +138,6 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic function to add arguments or keyword arguments to function calls.
|
||||
pub(crate) fn add_argument(
|
||||
argument: &str,
|
||||
arguments: &Arguments,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Edit {
|
||||
if let Some(last) = arguments.arguments_source_order().last() {
|
||||
// Case 1: existing arguments, so append after the last argument.
|
||||
let last = parenthesized_range(
|
||||
match last {
|
||||
ArgOrKeyword::Arg(arg) => arg.into(),
|
||||
ArgOrKeyword::Keyword(keyword) => (&keyword.value).into(),
|
||||
},
|
||||
arguments.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(last.range());
|
||||
Edit::insertion(format!(", {argument}"), last.end())
|
||||
} else {
|
||||
// Case 2: no arguments. Add argument, without any trailing comma.
|
||||
Edit::insertion(argument.to_string(), arguments.start() + TextSize::from(1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a vector contains only one, specific element.
|
||||
fn is_only<T: PartialEq>(vec: &[T], value: &T) -> bool {
|
||||
vec.len() == 1 && vec[0] == *value
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use itertools::Itertools;
|
||||
use ruff_diagnostics::Edit;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use crate::importer::{ImportRequest, Importer};
|
||||
use ruff_python_ast::helpers::{
|
||||
implicit_return, pep_604_union, typing_optional, typing_union, ReturnStatementVisitor,
|
||||
pep_604_union, typing_optional, typing_union, ReturnStatementVisitor,
|
||||
};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext};
|
||||
@@ -12,7 +13,6 @@ use ruff_python_semantic::analyze::visibility;
|
||||
use ruff_python_semantic::{Definition, SemanticModel};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::importer::{ImportRequest, Importer};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
@@ -48,19 +48,14 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
|
||||
let returns = {
|
||||
let mut visitor = ReturnStatementVisitor::default();
|
||||
visitor.visit_body(&function.body);
|
||||
|
||||
// Ignore generators.
|
||||
if visitor.is_generator {
|
||||
return None;
|
||||
}
|
||||
|
||||
visitor.returns
|
||||
};
|
||||
|
||||
// Determine the return type of the first `return` statement.
|
||||
let Some((return_statement, returns)) = returns.split_first() else {
|
||||
return Some(AutoPythonType::Atom(PythonType::None));
|
||||
};
|
||||
let (return_statement, returns) = returns.split_first()?;
|
||||
let mut return_type = return_statement.value.as_deref().map_or(
|
||||
ResolvedPythonType::Atom(PythonType::None),
|
||||
ResolvedPythonType::from,
|
||||
@@ -74,16 +69,6 @@ pub(crate) fn auto_return_type(function: &ast::StmtFunctionDef) -> Option<AutoPy
|
||||
));
|
||||
}
|
||||
|
||||
// If the function has an implicit return, union with `None`, as in:
|
||||
// ```python
|
||||
// def func(x: int):
|
||||
// if x > 0:
|
||||
// return 1
|
||||
// ```
|
||||
if implicit_return(function) {
|
||||
return_type = return_type.union(ResolvedPythonType::Atom(PythonType::None));
|
||||
}
|
||||
|
||||
match return_type {
|
||||
ResolvedPythonType::Atom(python_type) => Some(AutoPythonType::Atom(python_type)),
|
||||
ResolvedPythonType::Union(python_types) => Some(AutoPythonType::Union(python_types)),
|
||||
|
||||
@@ -200,231 +200,4 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f
|
||||
61 61 | return 1
|
||||
62 62 | elif x > 5:
|
||||
|
||||
auto_return_type.py:68:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
68 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
69 | if x:
|
||||
70 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
65 65 | return None
|
||||
66 66 |
|
||||
67 67 |
|
||||
68 |-def func(x: int):
|
||||
68 |+def func(x: int) -> int | None:
|
||||
69 69 | if x:
|
||||
70 70 | return 1
|
||||
71 71 |
|
||||
|
||||
auto_return_type.py:73:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
73 | def func():
|
||||
| ^^^^ ANN201
|
||||
74 | x = 1
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
70 70 | return 1
|
||||
71 71 |
|
||||
72 72 |
|
||||
73 |-def func():
|
||||
73 |+def func() -> None:
|
||||
74 74 | x = 1
|
||||
75 75 |
|
||||
76 76 |
|
||||
|
||||
auto_return_type.py:77:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
77 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
78 | if x > 0:
|
||||
79 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
74 74 | x = 1
|
||||
75 75 |
|
||||
76 76 |
|
||||
77 |-def func(x: int):
|
||||
77 |+def func(x: int) -> int | None:
|
||||
78 78 | if x > 0:
|
||||
79 79 | return 1
|
||||
80 80 |
|
||||
|
||||
auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
82 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
83 | match x:
|
||||
84 | case [1, 2, 3]:
|
||||
|
|
||||
= help: Add return type annotation: `str | int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
79 79 | return 1
|
||||
80 80 |
|
||||
81 81 |
|
||||
82 |-def func(x: int):
|
||||
82 |+def func(x: int) -> str | int:
|
||||
83 83 | match x:
|
||||
84 84 | case [1, 2, 3]:
|
||||
85 85 | return 1
|
||||
|
||||
auto_return_type.py:90:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
90 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
91 | for i in range(5):
|
||||
92 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
87 87 | return "foo"
|
||||
88 88 |
|
||||
89 89 |
|
||||
90 |-def func(x: int):
|
||||
90 |+def func(x: int) -> int | None:
|
||||
91 91 | for i in range(5):
|
||||
92 92 | if i > 0:
|
||||
93 93 | return 1
|
||||
|
||||
auto_return_type.py:96:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
96 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
97 | for i in range(5):
|
||||
98 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
93 93 | return 1
|
||||
94 94 |
|
||||
95 95 |
|
||||
96 |-def func(x: int):
|
||||
96 |+def func(x: int) -> int:
|
||||
97 97 | for i in range(5):
|
||||
98 98 | if i > 0:
|
||||
99 99 | return 1
|
||||
|
||||
auto_return_type.py:104:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
104 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
105 | for i in range(5):
|
||||
106 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
101 101 | return 4
|
||||
102 102 |
|
||||
103 103 |
|
||||
104 |-def func(x: int):
|
||||
104 |+def func(x: int) -> int | None:
|
||||
105 105 | for i in range(5):
|
||||
106 106 | if i > 0:
|
||||
107 107 | break
|
||||
|
||||
auto_return_type.py:112:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
112 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
113 | try:
|
||||
114 | pass
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
109 109 | return 4
|
||||
110 110 |
|
||||
111 111 |
|
||||
112 |-def func(x: int):
|
||||
112 |+def func(x: int) -> int | None:
|
||||
113 113 | try:
|
||||
114 114 | pass
|
||||
115 115 | except:
|
||||
|
||||
auto_return_type.py:119:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
119 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
120 | try:
|
||||
121 | pass
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | return 1
|
||||
117 117 |
|
||||
118 118 |
|
||||
119 |-def func(x: int):
|
||||
119 |+def func(x: int) -> int:
|
||||
120 120 | try:
|
||||
121 121 | pass
|
||||
122 122 | except:
|
||||
|
||||
auto_return_type.py:128:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
128 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
129 | try:
|
||||
130 | pass
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
125 125 | return 2
|
||||
126 126 |
|
||||
127 127 |
|
||||
128 |-def func(x: int):
|
||||
128 |+def func(x: int) -> int:
|
||||
129 129 | try:
|
||||
130 130 | pass
|
||||
131 131 | except:
|
||||
|
||||
auto_return_type.py:137:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
137 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
138 | try:
|
||||
139 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
134 134 | return 2
|
||||
135 135 |
|
||||
136 136 |
|
||||
137 |-def func(x: int):
|
||||
137 |+def func(x: int) -> int:
|
||||
138 138 | try:
|
||||
139 139 | return 1
|
||||
140 140 | except:
|
||||
|
||||
auto_return_type.py:146:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
146 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
147 | while x > 0:
|
||||
148 | break
|
||||
|
|
||||
= help: Add return type annotation: `int | None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
143 143 | pass
|
||||
144 144 |
|
||||
145 145 |
|
||||
146 |-def func(x: int):
|
||||
146 |+def func(x: int) -> int | None:
|
||||
147 147 | while x > 0:
|
||||
148 148 | break
|
||||
149 149 | return 1
|
||||
|
||||
|
||||
|
||||
@@ -220,266 +220,4 @@ auto_return_type.py:59:5: ANN201 [*] Missing return type annotation for public f
|
||||
61 62 | return 1
|
||||
62 63 | elif x > 5:
|
||||
|
||||
auto_return_type.py:68:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
68 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
69 | if x:
|
||||
70 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
65 66 | return None
|
||||
66 67 |
|
||||
67 68 |
|
||||
68 |-def func(x: int):
|
||||
69 |+def func(x: int) -> Optional[int]:
|
||||
69 70 | if x:
|
||||
70 71 | return 1
|
||||
71 72 |
|
||||
|
||||
auto_return_type.py:73:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
73 | def func():
|
||||
| ^^^^ ANN201
|
||||
74 | x = 1
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
70 70 | return 1
|
||||
71 71 |
|
||||
72 72 |
|
||||
73 |-def func():
|
||||
73 |+def func() -> None:
|
||||
74 74 | x = 1
|
||||
75 75 |
|
||||
76 76 |
|
||||
|
||||
auto_return_type.py:77:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
77 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
78 | if x > 0:
|
||||
79 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
74 75 | x = 1
|
||||
75 76 |
|
||||
76 77 |
|
||||
77 |-def func(x: int):
|
||||
78 |+def func(x: int) -> Optional[int]:
|
||||
78 79 | if x > 0:
|
||||
79 80 | return 1
|
||||
80 81 |
|
||||
|
||||
auto_return_type.py:82:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
82 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
83 | match x:
|
||||
84 | case [1, 2, 3]:
|
||||
|
|
||||
= help: Add return type annotation: `Union[str | int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Union
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
79 80 | return 1
|
||||
80 81 |
|
||||
81 82 |
|
||||
82 |-def func(x: int):
|
||||
83 |+def func(x: int) -> Union[str | int]:
|
||||
83 84 | match x:
|
||||
84 85 | case [1, 2, 3]:
|
||||
85 86 | return 1
|
||||
|
||||
auto_return_type.py:90:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
90 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
91 | for i in range(5):
|
||||
92 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
87 88 | return "foo"
|
||||
88 89 |
|
||||
89 90 |
|
||||
90 |-def func(x: int):
|
||||
91 |+def func(x: int) -> Optional[int]:
|
||||
91 92 | for i in range(5):
|
||||
92 93 | if i > 0:
|
||||
93 94 | return 1
|
||||
|
||||
auto_return_type.py:96:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
96 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
97 | for i in range(5):
|
||||
98 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
93 93 | return 1
|
||||
94 94 |
|
||||
95 95 |
|
||||
96 |-def func(x: int):
|
||||
96 |+def func(x: int) -> int:
|
||||
97 97 | for i in range(5):
|
||||
98 98 | if i > 0:
|
||||
99 99 | return 1
|
||||
|
||||
auto_return_type.py:104:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
104 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
105 | for i in range(5):
|
||||
106 | if i > 0:
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
101 102 | return 4
|
||||
102 103 |
|
||||
103 104 |
|
||||
104 |-def func(x: int):
|
||||
105 |+def func(x: int) -> Optional[int]:
|
||||
105 106 | for i in range(5):
|
||||
106 107 | if i > 0:
|
||||
107 108 | break
|
||||
|
||||
auto_return_type.py:112:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
112 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
113 | try:
|
||||
114 | pass
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
109 110 | return 4
|
||||
110 111 |
|
||||
111 112 |
|
||||
112 |-def func(x: int):
|
||||
113 |+def func(x: int) -> Optional[int]:
|
||||
113 114 | try:
|
||||
114 115 | pass
|
||||
115 116 | except:
|
||||
|
||||
auto_return_type.py:119:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
119 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
120 | try:
|
||||
121 | pass
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | return 1
|
||||
117 117 |
|
||||
118 118 |
|
||||
119 |-def func(x: int):
|
||||
119 |+def func(x: int) -> int:
|
||||
120 120 | try:
|
||||
121 121 | pass
|
||||
122 122 | except:
|
||||
|
||||
auto_return_type.py:128:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
128 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
129 | try:
|
||||
130 | pass
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
125 125 | return 2
|
||||
126 126 |
|
||||
127 127 |
|
||||
128 |-def func(x: int):
|
||||
128 |+def func(x: int) -> int:
|
||||
129 129 | try:
|
||||
130 130 | pass
|
||||
131 131 | except:
|
||||
|
||||
auto_return_type.py:137:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
137 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
138 | try:
|
||||
139 | return 1
|
||||
|
|
||||
= help: Add return type annotation: `int`
|
||||
|
||||
ℹ Unsafe fix
|
||||
134 134 | return 2
|
||||
135 135 |
|
||||
136 136 |
|
||||
137 |-def func(x: int):
|
||||
137 |+def func(x: int) -> int:
|
||||
138 138 | try:
|
||||
139 139 | return 1
|
||||
140 140 | except:
|
||||
|
||||
auto_return_type.py:146:5: ANN201 [*] Missing return type annotation for public function `func`
|
||||
|
|
||||
146 | def func(x: int):
|
||||
| ^^^^ ANN201
|
||||
147 | while x > 0:
|
||||
148 | break
|
||||
|
|
||||
= help: Add return type annotation: `Optional[int]`
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |+from typing import Optional
|
||||
1 2 | def func():
|
||||
2 3 | return 1
|
||||
3 4 |
|
||||
--------------------------------------------------------------------------------
|
||||
143 144 | pass
|
||||
144 145 |
|
||||
145 146 |
|
||||
146 |-def func(x: int):
|
||||
147 |+def func(x: int) -> Optional[int]:
|
||||
147 148 | while x > 0:
|
||||
148 149 | break
|
||||
149 150 | return 1
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
|
||||
---
|
||||
annotation_presence.py:5:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
annotation_presence.py:5:5: ANN201 Missing return type annotation for public function `foo`
|
||||
|
|
||||
4 | # Error
|
||||
5 | def foo(a, b):
|
||||
| ^^^ ANN201
|
||||
6 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 | from typing_extensions import override
|
||||
3 3 |
|
||||
4 4 | # Error
|
||||
5 |-def foo(a, b):
|
||||
5 |+def foo(a, b) -> None:
|
||||
6 6 | pass
|
||||
7 7 |
|
||||
8 8 |
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:5:9: ANN001 Missing type annotation for function argument `a`
|
||||
|
|
||||
@@ -36,24 +26,14 @@ annotation_presence.py:5:12: ANN001 Missing type annotation for function argumen
|
||||
6 | pass
|
||||
|
|
||||
|
||||
annotation_presence.py:10:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
annotation_presence.py:10:5: ANN201 Missing return type annotation for public function `foo`
|
||||
|
|
||||
9 | # Error
|
||||
10 | def foo(a: int, b):
|
||||
| ^^^ ANN201
|
||||
11 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 |
|
||||
8 8 |
|
||||
9 9 | # Error
|
||||
10 |-def foo(a: int, b):
|
||||
10 |+def foo(a: int, b) -> None:
|
||||
11 11 | pass
|
||||
12 12 |
|
||||
13 13 |
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:10:17: ANN001 Missing type annotation for function argument `b`
|
||||
|
|
||||
@@ -71,43 +51,23 @@ annotation_presence.py:15:17: ANN001 Missing type annotation for function argume
|
||||
16 | pass
|
||||
|
|
||||
|
||||
annotation_presence.py:20:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
annotation_presence.py:20:5: ANN201 Missing return type annotation for public function `foo`
|
||||
|
|
||||
19 | # Error
|
||||
20 | def foo(a: int, b: int):
|
||||
| ^^^ ANN201
|
||||
21 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
= help: Add return type annotation
|
||||
|
||||
ℹ Unsafe fix
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 19 | # Error
|
||||
20 |-def foo(a: int, b: int):
|
||||
20 |+def foo(a: int, b: int) -> None:
|
||||
21 21 | pass
|
||||
22 22 |
|
||||
23 23 |
|
||||
|
||||
annotation_presence.py:25:5: ANN201 [*] Missing return type annotation for public function `foo`
|
||||
annotation_presence.py:25:5: ANN201 Missing return type annotation for public function `foo`
|
||||
|
|
||||
24 | # Error
|
||||
25 | def foo():
|
||||
| ^^^ ANN201
|
||||
26 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 |
|
||||
23 23 |
|
||||
24 24 | # Error
|
||||
25 |-def foo():
|
||||
25 |+def foo() -> None:
|
||||
26 26 | pass
|
||||
27 27 |
|
||||
28 28 |
|
||||
= help: Add return type annotation
|
||||
|
||||
annotation_presence.py:45:12: ANN401 Dynamically typed expressions (typing.Any) are disallowed in `a`
|
||||
|
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_annotations/mod.rs
|
||||
---
|
||||
ignore_fully_untyped.py:24:5: ANN201 [*] Missing return type annotation for public function `error_partially_typed_1`
|
||||
ignore_fully_untyped.py:24:5: ANN201 Missing return type annotation for public function `error_partially_typed_1`
|
||||
|
|
||||
24 | def error_partially_typed_1(a: int, b):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ ANN201
|
||||
25 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
21 21 | pass
|
||||
22 22 |
|
||||
23 23 |
|
||||
24 |-def error_partially_typed_1(a: int, b):
|
||||
24 |+def error_partially_typed_1(a: int, b) -> None:
|
||||
25 25 | pass
|
||||
26 26 |
|
||||
27 27 |
|
||||
= help: Add return type annotation
|
||||
|
||||
ignore_fully_untyped.py:24:37: ANN001 Missing type annotation for function argument `b`
|
||||
|
|
||||
@@ -33,25 +23,15 @@ ignore_fully_untyped.py:28:37: ANN001 Missing type annotation for function argum
|
||||
29 | pass
|
||||
|
|
||||
|
||||
ignore_fully_untyped.py:32:5: ANN201 [*] Missing return type annotation for public function `error_partially_typed_3`
|
||||
ignore_fully_untyped.py:32:5: ANN201 Missing return type annotation for public function `error_partially_typed_3`
|
||||
|
|
||||
32 | def error_partially_typed_3(a: int, b: int):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ ANN201
|
||||
33 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
= help: Add return type annotation
|
||||
|
||||
ℹ Unsafe fix
|
||||
29 29 | pass
|
||||
30 30 |
|
||||
31 31 |
|
||||
32 |-def error_partially_typed_3(a: int, b: int):
|
||||
32 |+def error_partially_typed_3(a: int, b: int) -> None:
|
||||
33 33 | pass
|
||||
34 34 |
|
||||
35 35 |
|
||||
|
||||
ignore_fully_untyped.py:43:9: ANN201 [*] Missing return type annotation for public function `error_typed_self`
|
||||
ignore_fully_untyped.py:43:9: ANN201 Missing return type annotation for public function `error_typed_self`
|
||||
|
|
||||
41 | pass
|
||||
42 |
|
||||
@@ -59,14 +39,6 @@ ignore_fully_untyped.py:43:9: ANN201 [*] Missing return type annotation for publ
|
||||
| ^^^^^^^^^^^^^^^^ ANN201
|
||||
44 | pass
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
40 40 | def ok_untyped_method(self):
|
||||
41 41 | pass
|
||||
42 42 |
|
||||
43 |- def error_typed_self(self: X):
|
||||
43 |+ def error_typed_self(self: X) -> None:
|
||||
44 44 | pass
|
||||
= help: Add return type annotation
|
||||
|
||||
|
||||
|
||||
@@ -41,24 +41,14 @@ mypy_init_return.py:11:9: ANN204 [*] Missing return type annotation for special
|
||||
13 13 |
|
||||
14 14 |
|
||||
|
||||
mypy_init_return.py:40:5: ANN202 [*] Missing return type annotation for private function `__init__`
|
||||
mypy_init_return.py:40:5: ANN202 Missing return type annotation for private function `__init__`
|
||||
|
|
||||
39 | # Error
|
||||
40 | def __init__(self, foo: int):
|
||||
| ^^^^^^^^ ANN202
|
||||
41 | ...
|
||||
|
|
||||
= help: Add return type annotation: `None`
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 39 | # Error
|
||||
40 |-def __init__(self, foo: int):
|
||||
40 |+def __init__(self, foo: int) -> None:
|
||||
41 41 | ...
|
||||
42 42 |
|
||||
43 43 |
|
||||
= help: Add return type annotation
|
||||
|
||||
mypy_init_return.py:47:9: ANN204 [*] Missing return type annotation for special method `__init__`
|
||||
|
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Parameters};
|
||||
use ruff_python_ast::Parameters;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -51,9 +51,6 @@ impl Violation for NoReturnArgumentAnnotationInStub {
|
||||
|
||||
/// PYI050
|
||||
pub(crate) fn no_return_argument_annotation(checker: &mut Checker, parameters: &Parameters) {
|
||||
// Ex) def func(arg: NoReturn): ...
|
||||
// Ex) def func(arg: NoReturn, /): ...
|
||||
// Ex) def func(*, arg: NoReturn): ...
|
||||
for annotation in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
@@ -61,36 +58,18 @@ pub(crate) fn no_return_argument_annotation(checker: &mut Checker, parameters: &
|
||||
.chain(¶meters.kwonlyargs)
|
||||
.filter_map(|arg| arg.parameter.annotation.as_ref())
|
||||
{
|
||||
check_no_return_argument_annotation(checker, annotation);
|
||||
}
|
||||
|
||||
// Ex) def func(*args: NoReturn): ...
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(annotation) = &arg.annotation {
|
||||
check_no_return_argument_annotation(checker, annotation);
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) def func(**kwargs: NoReturn): ...
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
if let Some(annotation) = &arg.annotation {
|
||||
check_no_return_argument_annotation(checker, annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_no_return_argument_annotation(checker: &mut Checker, annotation: &Expr) {
|
||||
if checker.semantic().match_typing_expr(annotation, "NoReturn") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoReturnArgumentAnnotationInStub {
|
||||
module: if checker.settings.target_version >= Py311 {
|
||||
TypingModule::Typing
|
||||
} else {
|
||||
TypingModule::TypingExtensions
|
||||
if checker.semantic().match_typing_expr(annotation, "NoReturn") {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoReturnArgumentAnnotationInStub {
|
||||
module: if checker.settings.target_version >= Py311 {
|
||||
TypingModule::Typing
|
||||
} else {
|
||||
TypingModule::TypingExtensions
|
||||
},
|
||||
},
|
||||
},
|
||||
annotation.range(),
|
||||
));
|
||||
annotation.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,16 +46,12 @@ impl Violation for SnakeCaseTypeAlias {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// _MyTypeT: TypeAlias = int
|
||||
/// MyTypeT = int
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// _MyType: TypeAlias = int
|
||||
/// MyType = int
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct TSuffixedTypeAlias {
|
||||
|
||||
@@ -29,12 +29,4 @@ PYI042.py:20:1: PYI042 Type alias `_snake_case_alias2` should be CamelCase
|
||||
21 | Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
|
||||
|
||||
PYI042.py:27:6: PYI042 Type alias `foo_bar` should be CamelCase
|
||||
|
|
||||
26 | # PEP 695
|
||||
27 | type foo_bar = int | str
|
||||
| ^^^^^^^ PYI042
|
||||
28 | type FooBar = int | str
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -29,12 +29,4 @@ PYI042.pyi:20:1: PYI042 Type alias `_snake_case_alias2` should be CamelCase
|
||||
21 | Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
|
||||
|
||||
PYI042.pyi:27:6: PYI042 Type alias `foo_bar` should be CamelCase
|
||||
|
|
||||
26 | # PEP 695
|
||||
27 | type foo_bar = int | str
|
||||
| ^^^^^^^ PYI042
|
||||
28 | type FooBar = int | str
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -30,12 +30,4 @@ PYI043.py:12:1: PYI043 Private type alias `_PrivateAliasT3` should not be suffix
|
||||
14 | ] # PYI043, since this ends in a T
|
||||
|
|
||||
|
||||
PYI043.py:26:6: PYI043 Private type alias `_FooT` should not be suffixed with `T` (the `T` suffix implies that an object is a `TypeVar`)
|
||||
|
|
||||
25 | # PEP 695
|
||||
26 | type _FooT = str | int
|
||||
| ^^^^^ PYI043
|
||||
27 | type Foo = str | int
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -30,12 +30,4 @@ PYI043.pyi:12:1: PYI043 Private type alias `_PrivateAliasT3` should not be suffi
|
||||
14 | ] # PYI043, since this ends in a T
|
||||
|
|
||||
|
||||
PYI043.pyi:26:6: PYI043 Private type alias `_FooT` should not be suffixed with `T` (the `T` suffix implies that an object is a `TypeVar`)
|
||||
|
|
||||
25 | # PEP 695
|
||||
26 | type _FooT = str | int
|
||||
| ^^^^^ PYI043
|
||||
27 | type Foo = str | int
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -28,67 +28,6 @@ PYI050.pyi:11:47: PYI050 Prefer `typing.Never` over `NoReturn` for argument anno
|
||||
11 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
12 | def foo_never(arg: Never): ...
|
||||
13 | def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
|
|
||||
|
||||
PYI050.pyi:13:21: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
11 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
12 | def foo_never(arg: Never): ...
|
||||
13 | def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
14 | def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
15 | def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
|
|
||||
|
||||
PYI050.pyi:14:26: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
12 | def foo_never(arg: Never): ...
|
||||
13 | def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
14 | def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
15 | def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
16 | def foo_int_args(*args: int): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:15:28: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
13 | def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
14 | def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
15 | def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
16 | def foo_int_args(*args: int): ...
|
||||
17 | def foo_int_kwargs(**kwargs: int): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:15:48: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
13 | def foo_args(*args: NoReturn): ... # Error: PYI050
|
||||
14 | def foo_kwargs(**kwargs: NoReturn): ... # Error: PYI050
|
||||
15 | def foo_args_kwargs(*args: NoReturn, **kwargs: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
16 | def foo_int_args(*args: int): ...
|
||||
17 | def foo_int_kwargs(**kwargs: int): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:19:50: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
17 | def foo_int_kwargs(**kwargs: int): ...
|
||||
18 | def foo_int_args_kwargs(*args: int, **kwargs: int): ...
|
||||
19 | def foo_int_args_no_return(*args: int, **kwargs: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
20 | def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI050
|
||||
21 | def foo_args_never(*args: Never): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:20:37: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
18 | def foo_int_args_kwargs(*args: int, **kwargs: int): ...
|
||||
19 | def foo_int_args_no_return(*args: int, **kwargs: NoReturn): ... # Error: PYI050
|
||||
20 | def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
21 | def foo_args_never(*args: Never): ...
|
||||
22 | def foo_kwargs_never(**kwargs: Never): ...
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -101,15 +101,11 @@ pub(super) fn is_django_model_import(name: &str, stmt: &Stmt, semantic: &Semanti
|
||||
return false;
|
||||
};
|
||||
|
||||
if arguments.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match against, e.g., `apps.get_model("zerver", "Attachment")`.
|
||||
if let Some(call_path) = collect_call_path(func.as_ref()) {
|
||||
if matches!(call_path.as_slice(), [.., "get_model"]) {
|
||||
if let Some(argument) =
|
||||
arguments.find_argument("model_name", arguments.args.len().saturating_sub(1))
|
||||
arguments.find_argument("model_name", arguments.args.len() - 1)
|
||||
{
|
||||
if let Some(string_literal) = argument.as_string_literal_expr() {
|
||||
return string_literal.value.to_str() == name;
|
||||
|
||||
@@ -38,20 +38,4 @@ N806.py:53:5: N806 Variable `Bad` in function should be lowercase
|
||||
54 | ValidationError = import_string("django.core.exceptions.ValidationError") # OK
|
||||
|
|
||||
|
||||
N806.py:56:5: N806 Variable `Bad` in function should be lowercase
|
||||
|
|
||||
54 | ValidationError = import_string("django.core.exceptions.ValidationError") # OK
|
||||
55 |
|
||||
56 | Bad = apps.get_model() # N806
|
||||
| ^^^ N806
|
||||
57 | Bad = apps.get_model(model_name="Stream") # N806
|
||||
|
|
||||
|
||||
N806.py:57:5: N806 Variable `Bad` in function should be lowercase
|
||||
|
|
||||
56 | Bad = apps.get_model() # N806
|
||||
57 | Bad = apps.get_model(model_name="Stream") # N806
|
||||
| ^^^ N806
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ mod tests {
|
||||
#[test_case(Rule::BlankLineWithWhitespace, Path::new("W29.py"))]
|
||||
#[test_case(Rule::InvalidEscapeSequence, Path::new("W605_0.py"))]
|
||||
#[test_case(Rule::InvalidEscapeSequence, Path::new("W605_1.py"))]
|
||||
#[test_case(Rule::InvalidEscapeSequence, Path::new("W605_2.py"))]
|
||||
#[test_case(Rule::LineTooLong, Path::new("E501.py"))]
|
||||
#[test_case(Rule::LineTooLong, Path::new("E501_3.py"))]
|
||||
#[test_case(Rule::MixedSpacesAndTabs, Path::new("E101.py"))]
|
||||
|
||||
@@ -53,16 +53,24 @@ impl Overlong {
|
||||
};
|
||||
|
||||
let mut chunks = line.split_whitespace();
|
||||
let (Some(_), Some(second_chunk)) = (chunks.next(), chunks.next()) else {
|
||||
// Single word / no printable chars - no way to make the line shorter.
|
||||
return None;
|
||||
};
|
||||
if let (Some(_), Some(second_chunk)) = (chunks.next(), chunks.next()) {
|
||||
// Do not enforce the line length for lines that end with a URL, as long as the URL
|
||||
// begins before the limit.
|
||||
let last_chunk = chunks.last().unwrap_or(second_chunk);
|
||||
if last_chunk.contains("://") {
|
||||
if width.get() - last_chunk.width() <= limit.value() as usize {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a single word. If's a URL, ignore it.
|
||||
if line.contains("://") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Do not enforce the line length for lines that end with a URL, as long as the URL
|
||||
// begins before the limit.
|
||||
let last_chunk = chunks.last().unwrap_or(second_chunk);
|
||||
if last_chunk.contains("://") {
|
||||
if width.get() - last_chunk.width() <= limit.value() as usize {
|
||||
// This is a single word. If there are no split points, ignore it.
|
||||
indexer.comment_ranges()
|
||||
if !line.contains(['(', '[', ')', ']', '{', '}', ';']) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use memchr::memchr_iter;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{StringKind, Tok};
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::fix::edits::pad_start;
|
||||
|
||||
@@ -58,6 +58,18 @@ impl AlwaysFixableViolation for InvalidEscapeSequence {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum FixTitle {
|
||||
AddBackslash,
|
||||
UseRawStringLiteral,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InvalidEscapeChar {
|
||||
ch: char,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
/// W605
|
||||
pub(crate) fn invalid_escape_sequence(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
@@ -183,77 +195,41 @@ pub(crate) fn invalid_escape_sequence(
|
||||
if contains_valid_escape_sequence {
|
||||
// Escape with backslash.
|
||||
for invalid_escape_char in &invalid_escape_chars {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
let diagnostic = Diagnostic::new(
|
||||
InvalidEscapeSequence {
|
||||
ch: invalid_escape_char.ch,
|
||||
fix_title: FixTitle::AddBackslash,
|
||||
},
|
||||
invalid_escape_char.range(),
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
invalid_escape_char.range,
|
||||
)
|
||||
.with_fix(Fix::safe_edit(Edit::insertion(
|
||||
r"\".to_string(),
|
||||
invalid_escape_char.start() + TextSize::from(1),
|
||||
invalid_escape_char.range.start() + TextSize::from(1),
|
||||
)));
|
||||
invalid_escape_sequence.push(diagnostic);
|
||||
}
|
||||
} else {
|
||||
// Turn into raw string.
|
||||
for invalid_escape_char in &invalid_escape_chars {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
let diagnostic = Diagnostic::new(
|
||||
InvalidEscapeSequence {
|
||||
ch: invalid_escape_char.ch,
|
||||
fix_title: FixTitle::UseRawStringLiteral,
|
||||
},
|
||||
invalid_escape_char.range(),
|
||||
);
|
||||
|
||||
if matches!(
|
||||
token,
|
||||
Tok::String {
|
||||
kind: StringKind::Unicode,
|
||||
..
|
||||
}
|
||||
) {
|
||||
// Replace the Unicode prefix with `r`.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::replacement(
|
||||
"r".to_string(),
|
||||
invalid_escape_char.range,
|
||||
)
|
||||
.with_fix(
|
||||
// If necessary, add a space between any leading keyword (`return`, `yield`,
|
||||
// `assert`, etc.) and the string. For example, `return"foo"` is valid, but
|
||||
// `returnr"foo"` is not.
|
||||
Fix::safe_edit(Edit::insertion(
|
||||
pad_start("r".to_string(), string_start_location, locator),
|
||||
string_start_location,
|
||||
string_start_location + TextSize::from(1),
|
||||
)));
|
||||
} else {
|
||||
// Insert the `r` prefix.
|
||||
diagnostic.set_fix(
|
||||
// If necessary, add a space between any leading keyword (`return`, `yield`,
|
||||
// `assert`, etc.) and the string. For example, `return"foo"` is valid, but
|
||||
// `returnr"foo"` is not.
|
||||
Fix::safe_edit(Edit::insertion(
|
||||
pad_start("r".to_string(), string_start_location, locator),
|
||||
string_start_location,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
)),
|
||||
);
|
||||
invalid_escape_sequence.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics.extend(invalid_escape_sequence);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum FixTitle {
|
||||
AddBackslash,
|
||||
UseRawStringLiteral,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InvalidEscapeChar {
|
||||
ch: char,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl Ranged for InvalidEscapeChar {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +125,6 @@ W605_0.py:45:12: W605 [*] Invalid escape sequence: `\_`
|
||||
44 |
|
||||
45 | regex = '\\\_'
|
||||
| ^^ W605
|
||||
46 |
|
||||
47 | #: W605:1:7
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
@@ -136,23 +134,5 @@ W605_0.py:45:12: W605 [*] Invalid escape sequence: `\_`
|
||||
44 44 |
|
||||
45 |-regex = '\\\_'
|
||||
45 |+regex = '\\\\_'
|
||||
46 46 |
|
||||
47 47 | #: W605:1:7
|
||||
48 48 | u'foo\ bar'
|
||||
|
||||
W605_0.py:48:6: W605 [*] Invalid escape sequence: `\ `
|
||||
|
|
||||
47 | #: W605:1:7
|
||||
48 | u'foo\ bar'
|
||||
| ^^ W605
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 | regex = '\\\_'
|
||||
46 46 |
|
||||
47 47 | #: W605:1:7
|
||||
48 |-u'foo\ bar'
|
||||
48 |+r'foo\ bar'
|
||||
|
||||
|
||||
|
||||
@@ -1,227 +1,104 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
W605_1.py:4:11: W605 [*] Invalid escape sequence: `\.`
|
||||
W605_1.py:2:10: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
3 | #: W605:1:10
|
||||
4 | regex = f'\.png$'
|
||||
| ^^ W605
|
||||
5 |
|
||||
6 | #: W605:2:1
|
||||
1 | #: W605:1:10
|
||||
2 | regex = '\.png$'
|
||||
| ^^ W605
|
||||
3 |
|
||||
4 | #: W605:2:1
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # Same as `W605_0.py` but using f-strings instead.
|
||||
2 2 |
|
||||
3 3 | #: W605:1:10
|
||||
4 |-regex = f'\.png$'
|
||||
4 |+regex = rf'\.png$'
|
||||
5 5 |
|
||||
6 6 | #: W605:2:1
|
||||
7 7 | regex = f'''
|
||||
1 1 | #: W605:1:10
|
||||
2 |-regex = '\.png$'
|
||||
2 |+regex = r'\.png$'
|
||||
3 3 |
|
||||
4 4 | #: W605:2:1
|
||||
5 5 | regex = '''
|
||||
|
||||
W605_1.py:8:1: W605 [*] Invalid escape sequence: `\.`
|
||||
W605_1.py:6:1: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
6 | #: W605:2:1
|
||||
7 | regex = f'''
|
||||
8 | \.png$
|
||||
4 | #: W605:2:1
|
||||
5 | regex = '''
|
||||
6 | \.png$
|
||||
| ^^ W605
|
||||
9 | '''
|
||||
7 | '''
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | regex = f'\.png$'
|
||||
5 5 |
|
||||
6 6 | #: W605:2:1
|
||||
7 |-regex = f'''
|
||||
7 |+regex = rf'''
|
||||
8 8 | \.png$
|
||||
9 9 | '''
|
||||
10 10 |
|
||||
2 2 | regex = '\.png$'
|
||||
3 3 |
|
||||
4 4 | #: W605:2:1
|
||||
5 |-regex = '''
|
||||
5 |+regex = r'''
|
||||
6 6 | \.png$
|
||||
7 7 | '''
|
||||
8 8 |
|
||||
|
||||
W605_1.py:13:7: W605 [*] Invalid escape sequence: `\_`
|
||||
W605_1.py:11:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
11 | #: W605:2:6
|
||||
12 | f(
|
||||
13 | f'\_'
|
||||
| ^^ W605
|
||||
14 | )
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | #: W605:2:6
|
||||
12 12 | f(
|
||||
13 |- f'\_'
|
||||
13 |+ rf'\_'
|
||||
14 14 | )
|
||||
15 15 |
|
||||
16 16 | #: W605:4:6
|
||||
|
||||
W605_1.py:20:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
18 | multi-line
|
||||
19 | literal
|
||||
20 | with \_ somewhere
|
||||
9 | #: W605:2:6
|
||||
10 | f(
|
||||
11 | '\_'
|
||||
| ^^ W605
|
||||
21 | in the middle
|
||||
22 | """
|
||||
12 | )
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | )
|
||||
15 15 |
|
||||
16 16 | #: W605:4:6
|
||||
17 |-f"""
|
||||
17 |+rf"""
|
||||
18 18 | multi-line
|
||||
19 19 | literal
|
||||
20 20 | with \_ somewhere
|
||||
8 8 |
|
||||
9 9 | #: W605:2:6
|
||||
10 10 | f(
|
||||
11 |- '\_'
|
||||
11 |+ r'\_'
|
||||
12 12 | )
|
||||
13 13 |
|
||||
14 14 | #: W605:4:6
|
||||
|
||||
W605_1.py:25:40: W605 [*] Invalid escape sequence: `\_`
|
||||
W605_1.py:18:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
24 | #: W605:1:38
|
||||
25 | value = f'new line\nand invalid escape \_ here'
|
||||
| ^^ W605
|
||||
16 | multi-line
|
||||
17 | literal
|
||||
18 | with \_ somewhere
|
||||
| ^^ W605
|
||||
19 | in the middle
|
||||
20 | """
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 | """
|
||||
23 23 |
|
||||
24 24 | #: W605:1:38
|
||||
25 |-value = f'new line\nand invalid escape \_ here'
|
||||
25 |+value = f'new line\nand invalid escape \\_ here'
|
||||
12 12 | )
|
||||
13 13 |
|
||||
14 14 | #: W605:4:6
|
||||
15 |-"""
|
||||
15 |+r"""
|
||||
16 16 | multi-line
|
||||
17 17 | literal
|
||||
18 18 | with \_ somewhere
|
||||
|
||||
W605_1.py:25:12: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
23 | def f():
|
||||
24 | #: W605:1:11
|
||||
25 | return'\.png$'
|
||||
| ^^ W605
|
||||
26 |
|
||||
27 | #: Okay
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 |
|
||||
23 23 | def f():
|
||||
24 24 | #: W605:1:11
|
||||
25 |- return'\.png$'
|
||||
25 |+ return r'\.png$'
|
||||
26 26 |
|
||||
27 27 |
|
||||
28 28 | #: Okay
|
||||
|
||||
W605_1.py:43:13: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
41 | ''' # noqa
|
||||
42 |
|
||||
43 | regex = f'\\\_'
|
||||
| ^^ W605
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Safe fix
|
||||
40 40 | \w
|
||||
41 41 | ''' # noqa
|
||||
42 42 |
|
||||
43 |-regex = f'\\\_'
|
||||
43 |+regex = f'\\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
|
||||
W605_1.py:44:11: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
43 | regex = f'\\\_'
|
||||
44 | value = f'\{{1}}'
|
||||
| ^^ W605
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | ''' # noqa
|
||||
42 42 |
|
||||
43 43 | regex = f'\\\_'
|
||||
44 |-value = f'\{{1}}'
|
||||
44 |+value = rf'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
|
||||
W605_1.py:45:11: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
43 | regex = f'\\\_'
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
| ^^ W605
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 |
|
||||
43 43 | regex = f'\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 |-value = f'\{1}'
|
||||
45 |+value = rf'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
|
||||
W605_1.py:46:14: W605 [*] Invalid escape sequence: `\}`
|
||||
|
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
| ^^ W605
|
||||
47 | value = f"{f"\{1}"}"
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
43 43 | regex = f'\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 |-value = f'{1:\}'
|
||||
46 |+value = rf'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
49 49 |
|
||||
|
||||
W605_1.py:47:14: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
| ^^ W605
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 |-value = f"{f"\{1}"}"
|
||||
47 |+value = f"{rf"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
49 49 |
|
||||
50 50 | # Okay
|
||||
|
||||
W605_1.py:48:15: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
| ^^ W605
|
||||
49 |
|
||||
50 | # Okay
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 |-value = rf"{f"\{1}"}"
|
||||
48 |+value = rf"{rf"\{1}"}"
|
||||
49 49 |
|
||||
50 50 | # Okay
|
||||
51 51 | value = rf'\{{1}}'
|
||||
27 27 | #: Okay
|
||||
28 28 | regex = r'\.png$'
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
W605_2.py:4:11: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
3 | #: W605:1:10
|
||||
4 | regex = f'\.png$'
|
||||
| ^^ W605
|
||||
5 |
|
||||
6 | #: W605:2:1
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | # Same as `W605_0.py` but using f-strings instead.
|
||||
2 2 |
|
||||
3 3 | #: W605:1:10
|
||||
4 |-regex = f'\.png$'
|
||||
4 |+regex = rf'\.png$'
|
||||
5 5 |
|
||||
6 6 | #: W605:2:1
|
||||
7 7 | regex = f'''
|
||||
|
||||
W605_2.py:8:1: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
6 | #: W605:2:1
|
||||
7 | regex = f'''
|
||||
8 | \.png$
|
||||
| ^^ W605
|
||||
9 | '''
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | regex = f'\.png$'
|
||||
5 5 |
|
||||
6 6 | #: W605:2:1
|
||||
7 |-regex = f'''
|
||||
7 |+regex = rf'''
|
||||
8 8 | \.png$
|
||||
9 9 | '''
|
||||
10 10 |
|
||||
|
||||
W605_2.py:13:7: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
11 | #: W605:2:6
|
||||
12 | f(
|
||||
13 | f'\_'
|
||||
| ^^ W605
|
||||
14 | )
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | #: W605:2:6
|
||||
12 12 | f(
|
||||
13 |- f'\_'
|
||||
13 |+ rf'\_'
|
||||
14 14 | )
|
||||
15 15 |
|
||||
16 16 | #: W605:4:6
|
||||
|
||||
W605_2.py:20:6: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
18 | multi-line
|
||||
19 | literal
|
||||
20 | with \_ somewhere
|
||||
| ^^ W605
|
||||
21 | in the middle
|
||||
22 | """
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | )
|
||||
15 15 |
|
||||
16 16 | #: W605:4:6
|
||||
17 |-f"""
|
||||
17 |+rf"""
|
||||
18 18 | multi-line
|
||||
19 19 | literal
|
||||
20 20 | with \_ somewhere
|
||||
|
||||
W605_2.py:25:40: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
24 | #: W605:1:38
|
||||
25 | value = f'new line\nand invalid escape \_ here'
|
||||
| ^^ W605
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Safe fix
|
||||
22 22 | """
|
||||
23 23 |
|
||||
24 24 | #: W605:1:38
|
||||
25 |-value = f'new line\nand invalid escape \_ here'
|
||||
25 |+value = f'new line\nand invalid escape \\_ here'
|
||||
26 26 |
|
||||
27 27 |
|
||||
28 28 | #: Okay
|
||||
|
||||
W605_2.py:43:13: W605 [*] Invalid escape sequence: `\_`
|
||||
|
|
||||
41 | ''' # noqa
|
||||
42 |
|
||||
43 | regex = f'\\\_'
|
||||
| ^^ W605
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
|
||||
ℹ Safe fix
|
||||
40 40 | \w
|
||||
41 41 | ''' # noqa
|
||||
42 42 |
|
||||
43 |-regex = f'\\\_'
|
||||
43 |+regex = f'\\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
|
||||
W605_2.py:44:11: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
43 | regex = f'\\\_'
|
||||
44 | value = f'\{{1}}'
|
||||
| ^^ W605
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | ''' # noqa
|
||||
42 42 |
|
||||
43 43 | regex = f'\\\_'
|
||||
44 |-value = f'\{{1}}'
|
||||
44 |+value = rf'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
|
||||
W605_2.py:45:11: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
43 | regex = f'\\\_'
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
| ^^ W605
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
42 42 |
|
||||
43 43 | regex = f'\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 |-value = f'\{1}'
|
||||
45 |+value = rf'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
|
||||
W605_2.py:46:14: W605 [*] Invalid escape sequence: `\}`
|
||||
|
|
||||
44 | value = f'\{{1}}'
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
| ^^ W605
|
||||
47 | value = f"{f"\{1}"}"
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
43 43 | regex = f'\\\_'
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 |-value = f'{1:\}'
|
||||
46 |+value = rf'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
49 49 |
|
||||
|
||||
W605_2.py:47:14: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
| ^^ W605
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | value = f'\{{1}}'
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 |-value = f"{f"\{1}"}"
|
||||
47 |+value = f"{rf"\{1}"}"
|
||||
48 48 | value = rf"{f"\{1}"}"
|
||||
49 49 |
|
||||
50 50 | # Okay
|
||||
|
||||
W605_2.py:48:15: W605 [*] Invalid escape sequence: `\{`
|
||||
|
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
| ^^ W605
|
||||
49 |
|
||||
50 | # Okay
|
||||
|
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 | value = f'\{1}'
|
||||
46 46 | value = f'{1:\}'
|
||||
47 47 | value = f"{f"\{1}"}"
|
||||
48 |-value = rf"{f"\{1}"}"
|
||||
48 |+value = rf"{rf"\{1}"}"
|
||||
49 49 |
|
||||
50 50 | # Okay
|
||||
51 51 | value = rf'\{{1}}'
|
||||
|
||||
|
||||
@@ -79,6 +79,15 @@ E501_2.py:14:6: E501 Line too long (7 > 6)
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:15:6: E501 Line too long (7 > 6)
|
||||
|
|
||||
13 | [12]
|
||||
14 | [12 ]
|
||||
15 | [1,2]
|
||||
| ^ E501
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:16:6: E501 Line too long (8 > 6)
|
||||
|
|
||||
14 | [12 ]
|
||||
|
||||
@@ -89,6 +89,15 @@ E501_2.py:10:3: E501 Line too long (13 > 6)
|
||||
12 | if True: # noqa: E501
|
||||
|
|
||||
|
||||
E501_2.py:13:4: E501 Line too long (8 > 6)
|
||||
|
|
||||
12 | if True: # noqa: E501
|
||||
13 | [12]
|
||||
| ^^ E501
|
||||
14 | [12 ]
|
||||
15 | [1,2]
|
||||
|
|
||||
|
||||
E501_2.py:14:4: E501 Line too long (9 > 6)
|
||||
|
|
||||
12 | if True: # noqa: E501
|
||||
@@ -99,6 +108,15 @@ E501_2.py:14:4: E501 Line too long (9 > 6)
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:15:4: E501 Line too long (9 > 6)
|
||||
|
|
||||
13 | [12]
|
||||
14 | [12 ]
|
||||
15 | [1,2]
|
||||
| ^^^ E501
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:16:4: E501 Line too long (10 > 6)
|
||||
|
|
||||
14 | [12 ]
|
||||
|
||||
@@ -89,6 +89,15 @@ E501_2.py:10:2: E501 Line too long (21 > 6)
|
||||
12 | if True: # noqa: E501
|
||||
|
|
||||
|
||||
E501_2.py:13:2: E501 Line too long (12 > 6)
|
||||
|
|
||||
12 | if True: # noqa: E501
|
||||
13 | [12]
|
||||
| ^^^^ E501
|
||||
14 | [12 ]
|
||||
15 | [1,2]
|
||||
|
|
||||
|
||||
E501_2.py:14:2: E501 Line too long (13 > 6)
|
||||
|
|
||||
12 | if True: # noqa: E501
|
||||
@@ -99,6 +108,15 @@ E501_2.py:14:2: E501 Line too long (13 > 6)
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:15:2: E501 Line too long (13 > 6)
|
||||
|
|
||||
13 | [12]
|
||||
14 | [12 ]
|
||||
15 | [1,2]
|
||||
| ^^^^^ E501
|
||||
16 | [1, 2]
|
||||
|
|
||||
|
||||
E501_2.py:16:2: E501 Line too long (14 > 6)
|
||||
|
|
||||
14 | [12 ]
|
||||
|
||||
@@ -172,9 +172,8 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
let mut has_seen_tab = docstring.indentation.contains('\t');
|
||||
let mut is_over_indented = true;
|
||||
let mut over_indented_lines = vec![];
|
||||
let mut over_indented_size = usize::MAX;
|
||||
let mut over_indented_offset = usize::MAX;
|
||||
|
||||
let docstring_indent_size = docstring.indentation.chars().count();
|
||||
for i in 0..lines.len() {
|
||||
// First lines and continuations doesn't need any indentation.
|
||||
if i == 0 || lines[i - 1].ends_with('\\') {
|
||||
@@ -190,7 +189,6 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
}
|
||||
|
||||
let line_indent = leading_space(line);
|
||||
let line_indent_size = line_indent.chars().count();
|
||||
|
||||
// We only report tab indentation once, so only check if we haven't seen a tab
|
||||
// yet.
|
||||
@@ -199,7 +197,9 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
if checker.enabled(Rule::UnderIndentation) {
|
||||
// We report under-indentation on every line. This isn't great, but enables
|
||||
// fix.
|
||||
if (i == lines.len() - 1 || !is_blank) && line_indent_size < docstring_indent_size {
|
||||
if (i == lines.len() - 1 || !is_blank)
|
||||
&& line_indent.len() < docstring.indentation.len()
|
||||
{
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(UnderIndentation, TextRange::empty(line.start()));
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
@@ -217,12 +217,14 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
// until we've viewed all the lines, so for now, just track
|
||||
// the over-indentation status of every line.
|
||||
if i < lines.len() - 1 {
|
||||
if line_indent_size > docstring_indent_size {
|
||||
if line_indent.len() > docstring.indentation.len() {
|
||||
over_indented_lines.push(line);
|
||||
|
||||
// Track the _smallest_ offset we see, in terms of characters.
|
||||
over_indented_size =
|
||||
std::cmp::min(line_indent_size - docstring_indent_size, over_indented_size);
|
||||
over_indented_offset = std::cmp::min(
|
||||
line_indent.chars().count() - docstring.indentation.chars().count(),
|
||||
over_indented_offset,
|
||||
);
|
||||
} else {
|
||||
is_over_indented = false;
|
||||
}
|
||||
@@ -248,21 +250,21 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
// enables the fix capability.
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OverIndentation, TextRange::empty(line.start()));
|
||||
|
||||
let edit = if indent.is_empty() {
|
||||
// Delete the entire indent.
|
||||
Edit::range_deletion(TextRange::at(line.start(), line_indent.text_len()))
|
||||
Edit::deletion(line.start(), line_indent.text_len())
|
||||
} else {
|
||||
// Convert the character count to an offset within the source.
|
||||
let offset = checker
|
||||
.locator()
|
||||
.after(line.start() + indent.text_len())
|
||||
.chars()
|
||||
.take(over_indented_size)
|
||||
.take(over_indented_offset)
|
||||
.map(TextLen::text_len)
|
||||
.sum::<TextSize>();
|
||||
let range = TextRange::at(line.start(), indent.text_len() + offset);
|
||||
Edit::range_replacement(indent, range)
|
||||
Edit::range_replacement(
|
||||
indent.clone(),
|
||||
TextRange::at(line.start(), indent.text_len() + offset),
|
||||
)
|
||||
};
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -272,8 +274,7 @@ pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
|
||||
// If the last line is over-indented...
|
||||
if let Some(last) = lines.last() {
|
||||
let line_indent = leading_space(last);
|
||||
let line_indent_size = line_indent.chars().count();
|
||||
if line_indent_size > docstring_indent_size {
|
||||
if line_indent.len() > docstring.indentation.len() {
|
||||
let mut diagnostic =
|
||||
Diagnostic::new(OverIndentation, TextRange::empty(last.start()));
|
||||
let indent = clean_space(docstring.indentation);
|
||||
|
||||
@@ -1,81 +1,57 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
---
|
||||
D208.py:2:1: D208 [*] Docstring is over-indented
|
||||
D208.py:3:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
1 | """
|
||||
2 | Author
|
||||
1 | class Platform:
|
||||
2 | """ Remove sampler
|
||||
3 | Args:
|
||||
| D208
|
||||
3 | """
|
||||
4 | Returns:
|
||||
5 | """
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | """
|
||||
2 |- Author
|
||||
2 |+Author
|
||||
3 3 | """
|
||||
4 4 |
|
||||
5 5 |
|
||||
1 1 | class Platform:
|
||||
2 2 | """ Remove sampler
|
||||
3 |- Args:
|
||||
3 |+ Args:
|
||||
4 4 | Returns:
|
||||
5 5 | """
|
||||
|
||||
D208.py:8:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
6 | class Platform:
|
||||
7 | """ Remove sampler
|
||||
8 | Args:
|
||||
| D208
|
||||
9 | Returns:
|
||||
10 | """
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
D208.py:4:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
2 | """ Remove sampler
|
||||
3 | Args:
|
||||
4 | Returns:
|
||||
| D208
|
||||
5 | """
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | class Platform:
|
||||
7 7 | """ Remove sampler
|
||||
8 |- Args:
|
||||
8 |+ Args:
|
||||
9 9 | Returns:
|
||||
10 10 | """
|
||||
11 11 |
|
||||
1 1 | class Platform:
|
||||
2 2 | """ Remove sampler
|
||||
3 3 | Args:
|
||||
4 |- Returns:
|
||||
4 |+ Returns:
|
||||
5 5 | """
|
||||
|
||||
D208.py:9:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
7 | """ Remove sampler
|
||||
8 | Args:
|
||||
9 | Returns:
|
||||
| D208
|
||||
10 | """
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
D208.py:5:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
3 | Args:
|
||||
4 | Returns:
|
||||
5 | """
|
||||
| D208
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | class Platform:
|
||||
7 7 | """ Remove sampler
|
||||
8 8 | Args:
|
||||
9 |- Returns:
|
||||
9 |+ Returns:
|
||||
10 10 | """
|
||||
11 11 |
|
||||
12 12 |
|
||||
|
||||
D208.py:10:1: D208 [*] Docstring is over-indented
|
||||
|
|
||||
8 | Args:
|
||||
9 | Returns:
|
||||
10 | """
|
||||
| D208
|
||||
|
|
||||
= help: Remove over-indentation
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | """ Remove sampler
|
||||
8 8 | Args:
|
||||
9 9 | Returns:
|
||||
10 |- """
|
||||
10 |+ """
|
||||
11 11 |
|
||||
12 12 |
|
||||
13 13 | def memory_test():
|
||||
2 2 | """ Remove sampler
|
||||
3 3 | Args:
|
||||
4 4 | Returns:
|
||||
5 |- """
|
||||
5 |+ """
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{visitor, Arguments, CmpOp, Expr, Stmt};
|
||||
use ruff_python_ast::{Arguments, CmpOp, Expr};
|
||||
use ruff_python_semantic::analyze::function_type;
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
@@ -84,116 +82,3 @@ impl fmt::Display for CmpOpExt {
|
||||
write!(f, "{representation}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor to track reads from an iterable in a loop.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SequenceIndexVisitor<'a> {
|
||||
/// `letters`, given `for index, letter in enumerate(letters)`.
|
||||
sequence_name: &'a str,
|
||||
/// `index`, given `for index, letter in enumerate(letters)`.
|
||||
index_name: &'a str,
|
||||
/// `letter`, given `for index, letter in enumerate(letters)`.
|
||||
value_name: &'a str,
|
||||
/// The ranges of any `letters[index]` accesses.
|
||||
accesses: Vec<TextRange>,
|
||||
/// Whether any of the variables have been modified.
|
||||
modified: bool,
|
||||
}
|
||||
|
||||
impl<'a> SequenceIndexVisitor<'a> {
|
||||
pub(crate) fn new(sequence_name: &'a str, index_name: &'a str, value_name: &'a str) -> Self {
|
||||
Self {
|
||||
sequence_name,
|
||||
index_name,
|
||||
value_name,
|
||||
accesses: Vec::new(),
|
||||
modified: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_accesses(self) -> Vec<TextRange> {
|
||||
self.accesses
|
||||
}
|
||||
}
|
||||
|
||||
impl SequenceIndexVisitor<'_> {
|
||||
fn is_assignment(&self, expr: &Expr) -> bool {
|
||||
// If we see the sequence, a subscript, or the index being modified, we'll stop emitting
|
||||
// diagnostics.
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
id == self.sequence_name || id == self.index_name || id == self.value_name
|
||||
}
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if id == self.sequence_name {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = slice.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if id == self.index_name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'_> for SequenceIndexVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
if self.modified {
|
||||
return;
|
||||
}
|
||||
match stmt {
|
||||
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
|
||||
self.modified = targets.iter().any(|target| self.is_assignment(target));
|
||||
self.visit_expr(value);
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => {
|
||||
if let Some(value) = value {
|
||||
self.modified = self.is_assignment(target);
|
||||
self.visit_expr(value);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
|
||||
self.modified = self.is_assignment(target);
|
||||
self.visit_expr(value);
|
||||
}
|
||||
Stmt::Delete(ast::StmtDelete { targets, .. }) => {
|
||||
self.modified = targets.iter().any(|target| self.is_assignment(target));
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if self.modified {
|
||||
return;
|
||||
}
|
||||
match expr {
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
range,
|
||||
..
|
||||
}) => {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id == self.sequence_name {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = slice.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id == self.index_name {
|
||||
self.accesses.push(*range);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ mod tests {
|
||||
#[test_case(Rule::RedefinedLoopName, Path::new("redefined_loop_name.py"))]
|
||||
#[test_case(Rule::ReturnInInit, Path::new("return_in_init.py"))]
|
||||
#[test_case(Rule::TooManyArguments, Path::new("too_many_arguments.py"))]
|
||||
#[test_case(Rule::TooManyPositional, Path::new("too_many_positional.py"))]
|
||||
#[test_case(Rule::TooManyBranches, Path::new("too_many_branches.py"))]
|
||||
#[test_case(
|
||||
Rule::TooManyReturnStatements,
|
||||
@@ -161,10 +160,6 @@ mod tests {
|
||||
)]
|
||||
#[test_case(Rule::NoClassmethodDecorator, Path::new("no_method_decorator.py"))]
|
||||
#[test_case(Rule::NoStaticmethodDecorator, Path::new("no_method_decorator.py"))]
|
||||
#[test_case(
|
||||
Rule::UnnecessaryDictIndexLookup,
|
||||
Path::new("unnecessary_dict_index_lookup.py")
|
||||
)]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -250,22 +245,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_positional_args() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/too_many_positional_params.py"),
|
||||
&LinterSettings {
|
||||
pylint: pylint::settings::Settings {
|
||||
max_positional_args: 4,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..LinterSettings::for_rule(Rule::TooManyPositional)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_branches() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
@@ -345,15 +324,4 @@ mod tests {
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unspecified_encoding_python39_or_lower() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/unspecified_encoding.py"),
|
||||
&LinterSettings::for_rule(Rule::UnspecifiedEncoding)
|
||||
.with_target_version(PythonVersion::Py39),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ pub(crate) use sys_exit_alias::*;
|
||||
pub(crate) use too_many_arguments::*;
|
||||
pub(crate) use too_many_boolean_expressions::*;
|
||||
pub(crate) use too_many_branches::*;
|
||||
pub(crate) use too_many_positional::*;
|
||||
pub(crate) use too_many_public_methods::*;
|
||||
pub(crate) use too_many_return_statements::*;
|
||||
pub(crate) use too_many_statements::*;
|
||||
@@ -63,7 +62,6 @@ pub(crate) use type_bivariance::*;
|
||||
pub(crate) use type_name_incorrect_variance::*;
|
||||
pub(crate) use type_param_name_mismatch::*;
|
||||
pub(crate) use unexpected_special_method_signature::*;
|
||||
pub(crate) use unnecessary_dict_index_lookup::*;
|
||||
pub(crate) use unnecessary_direct_lambda_call::*;
|
||||
pub(crate) use unnecessary_lambda::*;
|
||||
pub(crate) use unnecessary_list_index_lookup::*;
|
||||
@@ -132,7 +130,6 @@ mod sys_exit_alias;
|
||||
mod too_many_arguments;
|
||||
mod too_many_boolean_expressions;
|
||||
mod too_many_branches;
|
||||
mod too_many_positional;
|
||||
mod too_many_public_methods;
|
||||
mod too_many_return_statements;
|
||||
mod too_many_statements;
|
||||
@@ -140,7 +137,6 @@ mod type_bivariance;
|
||||
mod type_name_incorrect_variance;
|
||||
mod type_param_name_mismatch;
|
||||
mod unexpected_special_method_signature;
|
||||
mod unnecessary_dict_index_lookup;
|
||||
mod unnecessary_direct_lambda_call;
|
||||
mod unnecessary_lambda;
|
||||
mod unnecessary_list_index_lookup;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use ruff_python_ast::{Parameters, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -58,13 +58,12 @@ impl Violation for TooManyArguments {
|
||||
}
|
||||
|
||||
/// PLR0913
|
||||
pub(crate) fn too_many_arguments(checker: &mut Checker, function_def: &ast::StmtFunctionDef) {
|
||||
let num_arguments = function_def
|
||||
.parameters
|
||||
pub(crate) fn too_many_arguments(checker: &mut Checker, parameters: &Parameters, stmt: &Stmt) {
|
||||
let num_arguments = parameters
|
||||
.args
|
||||
.iter()
|
||||
.chain(&function_def.parameters.kwonlyargs)
|
||||
.chain(&function_def.parameters.posonlyargs)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
.chain(¶meters.posonlyargs)
|
||||
.filter(|arg| {
|
||||
!checker
|
||||
.settings
|
||||
@@ -72,22 +71,13 @@ pub(crate) fn too_many_arguments(checker: &mut Checker, function_def: &ast::Stmt
|
||||
.is_match(&arg.parameter.name)
|
||||
})
|
||||
.count();
|
||||
|
||||
if num_arguments > checker.settings.pylint.max_args {
|
||||
// Allow excessive arguments in `@override` or `@overload` methods, since they're required
|
||||
// to adhere to the parent signature.
|
||||
if visibility::is_override(&function_def.decorator_list, checker.semantic())
|
||||
|| visibility::is_overload(&function_def.decorator_list, checker.semantic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyArguments {
|
||||
c_args: num_arguments,
|
||||
max_args: checker.settings.pylint.max_args,
|
||||
},
|
||||
function_def.identifier(),
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{identifier::Identifier, Parameters, Stmt};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for function definitions that include too many positional arguments.
|
||||
///
|
||||
/// By default, this rule allows up to five arguments, as configured by the
|
||||
/// [`pylint.max-positional-args`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Functions with many arguments are harder to understand, maintain, and call.
|
||||
/// This is especially true for functions with many positional arguments, as
|
||||
/// providing arguments positionally is more error-prone and less clear to
|
||||
/// readers than providing arguments by name.
|
||||
///
|
||||
/// Consider refactoring functions with many arguments into smaller functions
|
||||
/// with fewer arguments, using objects to group related arguments, or
|
||||
/// migrating to keyword-only arguments.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def plot(x, y, z, color, mark, add_trendline):
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// plot(1, 2, 3, "r", "*", True)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def plot(x, y, z, *, color, mark, add_trendline):
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// plot(1, 2, 3, color="r", mark="*", add_trendline=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `pylint.max-positional-args`
|
||||
#[violation]
|
||||
pub struct TooManyPositional {
|
||||
c_pos: usize,
|
||||
max_pos: usize,
|
||||
}
|
||||
|
||||
impl Violation for TooManyPositional {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TooManyPositional { c_pos, max_pos } = self;
|
||||
format!("Too many positional arguments: ({c_pos}/{max_pos})")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR0917
|
||||
pub(crate) fn too_many_positional(checker: &mut Checker, parameters: &Parameters, stmt: &Stmt) {
|
||||
let num_positional_args = parameters
|
||||
.args
|
||||
.iter()
|
||||
.chain(¶meters.posonlyargs)
|
||||
.filter(|arg| {
|
||||
!checker
|
||||
.settings
|
||||
.dummy_variable_rgx
|
||||
.is_match(&arg.parameter.name)
|
||||
})
|
||||
.count();
|
||||
if num_positional_args > checker.settings.pylint.max_positional_args {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyPositional {
|
||||
c_pos: num_positional_args,
|
||||
max_pos: checker.settings.pylint.max_positional_args,
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, StmtFor};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::SequenceIndexVisitor;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for key-based dict accesses during `.items()` iterations.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When iterating over a dict via `.items()`, the current value is already
|
||||
/// available alongside its key. Using the key to look up the value is
|
||||
/// unnecessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// FRUITS = {"apple": 1, "orange": 10, "berry": 22}
|
||||
///
|
||||
/// for fruit_name, fruit_count in FRUITS.items():
|
||||
/// print(FRUITS[fruit_name])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// FRUITS = {"apple": 1, "orange": 10, "berry": 22}
|
||||
///
|
||||
/// for fruit_name, fruit_count in FRUITS.items():
|
||||
/// print(fruit_count)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UnnecessaryDictIndexLookup;
|
||||
|
||||
impl AlwaysFixableViolation for UnnecessaryDictIndexLookup {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary lookup of dictionary value by key")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Use existing variable")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR1733
|
||||
pub(crate) fn unnecessary_dict_index_lookup(checker: &mut Checker, stmt_for: &StmtFor) {
|
||||
let Some((dict_name, index_name, value_name)) = dict_items(&stmt_for.iter, &stmt_for.target)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let ranges = {
|
||||
let mut visitor = SequenceIndexVisitor::new(dict_name, index_name, value_name);
|
||||
visitor.visit_body(&stmt_for.body);
|
||||
visitor.visit_body(&stmt_for.orelse);
|
||||
visitor.into_accesses()
|
||||
};
|
||||
|
||||
for range in ranges {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryDictIndexLookup, range);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
value_name.to_string(),
|
||||
range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
/// PLR1733
|
||||
pub(crate) fn unnecessary_dict_index_lookup_comprehension(checker: &mut Checker, expr: &Expr) {
|
||||
let (Expr::GeneratorExp(ast::ExprGeneratorExp {
|
||||
elt, generators, ..
|
||||
})
|
||||
| Expr::DictComp(ast::ExprDictComp {
|
||||
value: elt,
|
||||
generators,
|
||||
..
|
||||
})
|
||||
| Expr::SetComp(ast::ExprSetComp {
|
||||
elt, generators, ..
|
||||
})
|
||||
| Expr::ListComp(ast::ExprListComp {
|
||||
elt, generators, ..
|
||||
})) = expr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
for comp in generators {
|
||||
let Some((dict_name, index_name, value_name)) = dict_items(&comp.iter, &comp.target) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let ranges = {
|
||||
let mut visitor = SequenceIndexVisitor::new(dict_name, index_name, value_name);
|
||||
visitor.visit_expr(elt.as_ref());
|
||||
for expr in &comp.ifs {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
visitor.into_accesses()
|
||||
};
|
||||
|
||||
for range in ranges {
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryDictIndexLookup, range);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
value_name.to_string(),
|
||||
range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dict_items<'a>(
|
||||
call_expr: &'a Expr,
|
||||
tuple_expr: &'a Expr,
|
||||
) -> Option<(&'a str, &'a str, &'a str)> {
|
||||
let ast::ExprCall {
|
||||
func, arguments, ..
|
||||
} = call_expr.as_call_expr()?;
|
||||
|
||||
if !arguments.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if attr != "items" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id: dict_name, .. }) = value.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = tuple_expr else {
|
||||
return None;
|
||||
};
|
||||
let [index, value] = elts.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Grab the variable names.
|
||||
let Expr::Name(ast::ExprName { id: index_name, .. }) = index else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Expr::Name(ast::ExprName { id: value_name, .. }) = value else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// If either of the variable names are intentionally ignored by naming them `_`, then don't
|
||||
// emit.
|
||||
if index_name == "_" || value_name == "_" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((dict_name, index_name, value_name))
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt, StmtFor};
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, StmtFor};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::pylint::helpers::SequenceIndexVisitor;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for index-based list accesses during `enumerate` iterations.
|
||||
@@ -53,10 +55,10 @@ pub(crate) fn unnecessary_list_index_lookup(checker: &mut Checker, stmt_for: &St
|
||||
};
|
||||
|
||||
let ranges = {
|
||||
let mut visitor = SequenceIndexVisitor::new(sequence, index_name, value_name);
|
||||
let mut visitor = SubscriptVisitor::new(sequence, index_name);
|
||||
visitor.visit_body(&stmt_for.body);
|
||||
visitor.visit_body(&stmt_for.orelse);
|
||||
visitor.into_accesses()
|
||||
visitor.diagnostic_ranges
|
||||
};
|
||||
|
||||
for range in ranges {
|
||||
@@ -97,9 +99,9 @@ pub(crate) fn unnecessary_list_index_lookup_comprehension(checker: &mut Checker,
|
||||
};
|
||||
|
||||
let ranges = {
|
||||
let mut visitor = SequenceIndexVisitor::new(sequence, index_name, value_name);
|
||||
let mut visitor = SubscriptVisitor::new(sequence, index_name);
|
||||
visitor.visit_expr(elt.as_ref());
|
||||
visitor.into_accesses()
|
||||
visitor.diagnostic_ranges
|
||||
};
|
||||
|
||||
for range in ranges {
|
||||
@@ -159,3 +161,104 @@ fn enumerate_items<'a>(
|
||||
|
||||
Some((sequence, index_name, value_name))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SubscriptVisitor<'a> {
|
||||
sequence_name: &'a str,
|
||||
index_name: &'a str,
|
||||
diagnostic_ranges: Vec<TextRange>,
|
||||
modified: bool,
|
||||
}
|
||||
|
||||
impl<'a> SubscriptVisitor<'a> {
|
||||
fn new(sequence_name: &'a str, index_name: &'a str) -> Self {
|
||||
Self {
|
||||
sequence_name,
|
||||
index_name,
|
||||
diagnostic_ranges: Vec::new(),
|
||||
modified: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubscriptVisitor<'_> {
|
||||
fn is_assignment(&self, expr: &Expr) -> bool {
|
||||
// If we see the sequence, a subscript, or the index being modified, we'll stop emitting
|
||||
// diagnostics.
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
id == self.sequence_name || id == self.index_name
|
||||
}
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if id == self.sequence_name {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = slice.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if id == self.index_name {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'_> for SubscriptVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) {
|
||||
if self.modified {
|
||||
return;
|
||||
}
|
||||
match stmt {
|
||||
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
|
||||
self.modified = targets.iter().any(|target| self.is_assignment(target));
|
||||
self.visit_expr(value);
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => {
|
||||
if let Some(value) = value {
|
||||
self.modified = self.is_assignment(target);
|
||||
self.visit_expr(value);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
|
||||
self.modified = self.is_assignment(target);
|
||||
self.visit_expr(value);
|
||||
}
|
||||
Stmt::Delete(ast::StmtDelete { targets, .. }) => {
|
||||
self.modified = targets.iter().any(|target| self.is_assignment(target));
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
if self.modified {
|
||||
return;
|
||||
}
|
||||
match expr {
|
||||
Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
range,
|
||||
..
|
||||
}) => {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id == self.sequence_name {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = slice.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id == self.index_name {
|
||||
self.diagnostic_ranges.push(*range);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::call_path::{format_call_path, CallPath};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::add_argument;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `open` and related calls without an explicit `encoding`
|
||||
@@ -21,9 +15,7 @@ use crate::settings::types::PythonVersion;
|
||||
/// non-portable code, with differing behavior across platforms.
|
||||
///
|
||||
/// Instead, consider using the `encoding` parameter to enforce a specific
|
||||
/// encoding. [PEP 597] recommends using `locale.getpreferredencoding(False)`
|
||||
/// as the default encoding on versions earlier than Python 3.10, and
|
||||
/// `encoding="locale"` on Python 3.10 and later.
|
||||
/// encoding.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -37,15 +29,13 @@ use crate::settings::types::PythonVersion;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
///
|
||||
/// [PEP 597]: https://peps.python.org/pep-0597/
|
||||
#[violation]
|
||||
pub struct UnspecifiedEncoding {
|
||||
function_name: String,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for UnspecifiedEncoding {
|
||||
impl Violation for UnspecifiedEncoding {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnspecifiedEncoding {
|
||||
@@ -62,10 +52,6 @@ impl AlwaysFixableViolation for UnspecifiedEncoding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
format!("Add explicit `encoding` argument")
|
||||
}
|
||||
}
|
||||
|
||||
/// PLW1514
|
||||
@@ -84,63 +70,17 @@ pub(crate) fn unspecified_encoding(checker: &mut Checker, call: &ast::ExprCall)
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnspecifiedEncoding {
|
||||
function_name,
|
||||
mode,
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
|
||||
if checker.settings.target_version >= PythonVersion::Py310 {
|
||||
diagnostic.set_fix(generate_keyword_fix(checker, call));
|
||||
} else {
|
||||
diagnostic.try_set_fix(|| generate_import_fix(checker, call));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] for Python 3.10 and later.
|
||||
fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
|
||||
Fix::unsafe_edit(add_argument(
|
||||
&format!(
|
||||
"encoding={}",
|
||||
checker
|
||||
.generator()
|
||||
.expr(&Expr::StringLiteral(ast::ExprStringLiteral {
|
||||
value: ast::StringLiteralValue::single(ast::StringLiteral {
|
||||
value: "locale".to_string(),
|
||||
unicode: false,
|
||||
range: TextRange::default(),
|
||||
}),
|
||||
range: TextRange::default(),
|
||||
}))
|
||||
),
|
||||
&call.arguments,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generate an [`Edit`] for Python 3.9 and earlier.
|
||||
fn generate_import_fix(checker: &Checker, call: &ast::ExprCall) -> Result<Fix> {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("locale", "getpreferredencoding"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let argument_edit = add_argument(
|
||||
&format!("encoding={binding}(False)"),
|
||||
&call.arguments,
|
||||
checker.indexer().comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
Ok(Fix::unsafe_edits(import_edit, [argument_edit]))
|
||||
));
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is a string literal containing a `b` character.
|
||||
fn is_binary_mode(expr: &Expr) -> Option<bool> {
|
||||
fn is_binary_mode(expr: &ast::Expr) -> Option<bool> {
|
||||
Some(
|
||||
expr.as_string_literal_expr()?
|
||||
.value
|
||||
@@ -152,7 +92,12 @@ fn is_binary_mode(expr: &Expr) -> Option<bool> {
|
||||
/// Returns `true` if the given call lacks an explicit `encoding`.
|
||||
fn is_violation(call: &ast::ExprCall, call_path: &CallPath) -> bool {
|
||||
// If we have something like `*args`, which might contain the encoding argument, abort.
|
||||
if call.arguments.args.iter().any(Expr::is_starred_expr) {
|
||||
if call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(ruff_python_ast::Expr::is_starred_expr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If we have something like `**kwargs`, which might contain the encoding argument, abort.
|
||||
|
||||
@@ -39,7 +39,6 @@ pub struct Settings {
|
||||
pub allow_magic_value_types: Vec<ConstantType>,
|
||||
pub allow_dunder_method_names: FxHashSet<String>,
|
||||
pub max_args: usize,
|
||||
pub max_positional_args: usize,
|
||||
pub max_returns: usize,
|
||||
pub max_bool_expr: usize,
|
||||
pub max_branches: usize,
|
||||
@@ -53,7 +52,6 @@ impl Default for Settings {
|
||||
allow_magic_value_types: vec![ConstantType::Str, ConstantType::Bytes],
|
||||
allow_dunder_method_names: FxHashSet::default(),
|
||||
max_args: 5,
|
||||
max_positional_args: 5,
|
||||
max_returns: 6,
|
||||
max_bool_expr: 5,
|
||||
max_branches: 12,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
too_many_positional.py:1:5: PLR0917 Too many positional arguments: (8/5)
|
||||
|
|
||||
1 | def f(x, y, z, t, u, v, w, r): # Too many positional arguments (8/3)
|
||||
| ^ PLR0917
|
||||
2 | pass
|
||||
|
|
||||
|
||||
too_many_positional.py:21:5: PLR0917 Too many positional arguments: (6/5)
|
||||
|
|
||||
21 | def f(x, y, z, /, u, v, w): # Too many positional arguments (6/3)
|
||||
| ^ PLR0917
|
||||
22 | pass
|
||||
|
|
||||
|
||||
too_many_positional.py:29:5: PLR0917 Too many positional arguments: (6/5)
|
||||
|
|
||||
29 | def f(x, y, z, a, b, c, *, u, v, w): # Too many positional arguments (6/3)
|
||||
| ^ PLR0917
|
||||
30 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
unnecessary_dict_index_lookup.py:4:6: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
3 | def fix_these():
|
||||
4 | [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
5 | {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | FRUITS = {"apple": 1, "orange": 10, "berry": 22}
|
||||
2 2 |
|
||||
3 3 | def fix_these():
|
||||
4 |- [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
4 |+ [fruit_count for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
5 5 | {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
7 7 |
|
||||
|
||||
unnecessary_dict_index_lookup.py:5:6: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
3 | def fix_these():
|
||||
4 | [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
5 | {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 |
|
||||
3 3 | def fix_these():
|
||||
4 4 | [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
5 |- {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
5 |+ {fruit_count for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
7 7 |
|
||||
8 8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
|
||||
unnecessary_dict_index_lookup.py:6:18: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
4 | [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
5 | {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
7 |
|
||||
8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | def fix_these():
|
||||
4 4 | [FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()] # PLR1733
|
||||
5 5 | {FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 |- {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
6 |+ {fruit_name: fruit_count for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
7 7 |
|
||||
8 8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
|
||||
unnecessary_dict_index_lookup.py:9:15: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
10 | blah = FRUITS[fruit_name] # PLR1733
|
||||
11 | assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | {fruit_name: FRUITS[fruit_name] for fruit_name, fruit_count in FRUITS.items()} # PLR1733
|
||||
7 7 |
|
||||
8 8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 |- print(FRUITS[fruit_name]) # PLR1733
|
||||
9 |+ print(fruit_count) # PLR1733
|
||||
10 10 | blah = FRUITS[fruit_name] # PLR1733
|
||||
11 11 | assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
12 12 |
|
||||
|
||||
unnecessary_dict_index_lookup.py:10:16: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
10 | blah = FRUITS[fruit_name] # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
11 | assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 |
|
||||
8 8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
10 |- blah = FRUITS[fruit_name] # PLR1733
|
||||
10 |+ blah = fruit_count # PLR1733
|
||||
11 11 | assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
12 12 |
|
||||
13 13 |
|
||||
|
||||
unnecessary_dict_index_lookup.py:11:16: PLR1733 [*] Unnecessary lookup of dictionary value by key
|
||||
|
|
||||
9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
10 | blah = FRUITS[fruit_name] # PLR1733
|
||||
11 | assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR1733
|
||||
|
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | for fruit_name, fruit_count in FRUITS.items():
|
||||
9 9 | print(FRUITS[fruit_name]) # PLR1733
|
||||
10 10 | blah = FRUITS[fruit_name] # PLR1733
|
||||
11 |- assert FRUITS[fruit_name] == "pear" # PLR1733
|
||||
11 |+ assert fruit_count == "pear" # PLR1733
|
||||
12 12 |
|
||||
13 13 |
|
||||
14 14 | def dont_fix_these():
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ unnecessary_list_index_lookup.py:12:15: PLR1736 [*] Unnecessary lookup of list i
|
||||
12 |+ print(letter) # PLR1736
|
||||
13 13 | blah = letters[index] # PLR1736
|
||||
14 14 | assert letters[index] == "d" # PLR1736
|
||||
15 15 |
|
||||
15 15 |
|
||||
|
||||
unnecessary_list_index_lookup.py:13:16: PLR1736 [*] Unnecessary lookup of list item by index
|
||||
|
|
||||
@@ -99,7 +99,7 @@ unnecessary_list_index_lookup.py:13:16: PLR1736 [*] Unnecessary lookup of list i
|
||||
13 |- blah = letters[index] # PLR1736
|
||||
13 |+ blah = letter # PLR1736
|
||||
14 14 | assert letters[index] == "d" # PLR1736
|
||||
15 15 |
|
||||
15 15 |
|
||||
16 16 | for index, letter in builtins.enumerate(letters):
|
||||
|
||||
unnecessary_list_index_lookup.py:14:16: PLR1736 [*] Unnecessary lookup of list item by index
|
||||
@@ -108,7 +108,7 @@ unnecessary_list_index_lookup.py:14:16: PLR1736 [*] Unnecessary lookup of list i
|
||||
13 | blah = letters[index] # PLR1736
|
||||
14 | assert letters[index] == "d" # PLR1736
|
||||
| ^^^^^^^^^^^^^^ PLR1736
|
||||
15 |
|
||||
15 |
|
||||
16 | for index, letter in builtins.enumerate(letters):
|
||||
|
|
||||
= help: Use existing variable
|
||||
@@ -119,7 +119,7 @@ unnecessary_list_index_lookup.py:14:16: PLR1736 [*] Unnecessary lookup of list i
|
||||
13 13 | blah = letters[index] # PLR1736
|
||||
14 |- assert letters[index] == "d" # PLR1736
|
||||
14 |+ assert letter == "d" # PLR1736
|
||||
15 15 |
|
||||
15 15 |
|
||||
16 16 | for index, letter in builtins.enumerate(letters):
|
||||
17 17 | print(letters[index]) # PLR1736
|
||||
|
||||
@@ -135,7 +135,7 @@ unnecessary_list_index_lookup.py:17:15: PLR1736 [*] Unnecessary lookup of list i
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | assert letters[index] == "d" # PLR1736
|
||||
15 15 |
|
||||
15 15 |
|
||||
16 16 | for index, letter in builtins.enumerate(letters):
|
||||
17 |- print(letters[index]) # PLR1736
|
||||
17 |+ print(letter) # PLR1736
|
||||
@@ -154,7 +154,7 @@ unnecessary_list_index_lookup.py:18:16: PLR1736 [*] Unnecessary lookup of list i
|
||||
= help: Use existing variable
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 |
|
||||
15 15 |
|
||||
16 16 | for index, letter in builtins.enumerate(letters):
|
||||
17 17 | print(letters[index]) # PLR1736
|
||||
18 |- blah = letters[index] # PLR1736
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
unspecified_encoding.py:8:1: PLW1514 `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
@@ -9,19 +9,8 @@ unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `e
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
5 5 | import codecs
|
||||
6 6 |
|
||||
7 7 | # Errors.
|
||||
8 |-open("test.txt")
|
||||
8 |+open("test.txt", encoding="locale")
|
||||
9 9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
|
||||
unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument
|
||||
unspecified_encoding.py:9:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
@@ -30,19 +19,8 @@ unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `en
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
6 6 |
|
||||
7 7 | # Errors.
|
||||
8 8 | open("test.txt")
|
||||
9 |-io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
9 |+io.TextIOWrapper(io.FileIO("test.txt"), encoding="locale")
|
||||
10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
12 12 | tempfile.TemporaryFile("w")
|
||||
|
||||
unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument
|
||||
unspecified_encoding.py:10:1: PLW1514 `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
@@ -51,19 +29,8 @@ unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `e
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 | # Errors.
|
||||
8 8 | open("test.txt")
|
||||
9 9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 |-hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
10 |+hugo.TextIOWrapper(hugo.FileIO("test.txt"), encoding="locale")
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
12 12 | tempfile.TemporaryFile("w")
|
||||
13 13 | codecs.open("test.txt")
|
||||
|
||||
unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
||||
unspecified_encoding.py:11:1: PLW1514 `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
@@ -72,19 +39,8 @@ unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | open("test.txt")
|
||||
9 9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 |-tempfile.NamedTemporaryFile("w")
|
||||
11 |+tempfile.NamedTemporaryFile("w", encoding="locale")
|
||||
12 12 | tempfile.TemporaryFile("w")
|
||||
13 13 | codecs.open("test.txt")
|
||||
14 14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
||||
unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
||||
unspecified_encoding.py:12:1: PLW1514 `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
@@ -93,19 +49,8 @@ unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
9 9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
12 |-tempfile.TemporaryFile("w")
|
||||
12 |+tempfile.TemporaryFile("w", encoding="locale")
|
||||
13 13 | codecs.open("test.txt")
|
||||
14 14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
15 15 |
|
||||
|
||||
unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without explicit `encoding` argument
|
||||
unspecified_encoding.py:13:1: PLW1514 `codecs.open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
@@ -113,19 +58,8 @@ unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without exp
|
||||
| ^^^^^^^^^^^ PLW1514
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
10 10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
12 12 | tempfile.TemporaryFile("w")
|
||||
13 |-codecs.open("test.txt")
|
||||
13 |+codecs.open("test.txt", encoding="locale")
|
||||
14 14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
15 15 |
|
||||
16 16 | # Non-errors.
|
||||
|
||||
unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
||||
unspecified_encoding.py:14:1: PLW1514 `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
@@ -134,223 +68,5 @@ unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in tex
|
||||
15 |
|
||||
16 | # Non-errors.
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
11 11 | tempfile.NamedTemporaryFile("w")
|
||||
12 12 | tempfile.TemporaryFile("w")
|
||||
13 13 | codecs.open("test.txt")
|
||||
14 |-tempfile.SpooledTemporaryFile(0, "w")
|
||||
14 |+tempfile.SpooledTemporaryFile(0, "w", encoding="locale")
|
||||
15 15 |
|
||||
16 16 | # Non-errors.
|
||||
17 17 | open("test.txt", encoding="utf-8")
|
||||
|
||||
unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
44 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 |
|
||||
46 | open("test.txt",)
|
||||
| ^^^^ PLW1514
|
||||
47 | open()
|
||||
48 | open(
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
43 43 | tempfile.SpooledTemporaryFile(0, "wb")
|
||||
44 44 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 45 |
|
||||
46 |-open("test.txt",)
|
||||
46 |+open("test.txt", encoding="locale",)
|
||||
47 47 | open()
|
||||
48 48 | open(
|
||||
49 49 | "test.txt", # comment
|
||||
|
||||
unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
46 | open("test.txt",)
|
||||
47 | open()
|
||||
| ^^^^ PLW1514
|
||||
48 | open(
|
||||
49 | "test.txt", # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
44 44 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 45 |
|
||||
46 46 | open("test.txt",)
|
||||
47 |-open()
|
||||
47 |+open(encoding="locale")
|
||||
48 48 | open(
|
||||
49 49 | "test.txt", # comment
|
||||
50 50 | )
|
||||
|
||||
unspecified_encoding.py:48:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
46 | open("test.txt",)
|
||||
47 | open()
|
||||
48 | open(
|
||||
| ^^^^ PLW1514
|
||||
49 | "test.txt", # comment
|
||||
50 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
46 46 | open("test.txt",)
|
||||
47 47 | open()
|
||||
48 48 | open(
|
||||
49 |- "test.txt", # comment
|
||||
49 |+ "test.txt", encoding="locale", # comment
|
||||
50 50 | )
|
||||
51 51 | open(
|
||||
52 52 | "test.txt",
|
||||
|
||||
unspecified_encoding.py:51:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
49 | "test.txt", # comment
|
||||
50 | )
|
||||
51 | open(
|
||||
| ^^^^ PLW1514
|
||||
52 | "test.txt",
|
||||
53 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
49 49 | "test.txt", # comment
|
||||
50 50 | )
|
||||
51 51 | open(
|
||||
52 |- "test.txt",
|
||||
52 |+ "test.txt", encoding="locale",
|
||||
53 53 | # comment
|
||||
54 54 | )
|
||||
55 55 | open(("test.txt"),)
|
||||
|
||||
unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
53 | # comment
|
||||
54 | )
|
||||
55 | open(("test.txt"),)
|
||||
| ^^^^ PLW1514
|
||||
56 | open(
|
||||
57 | ("test.txt"), # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
52 52 | "test.txt",
|
||||
53 53 | # comment
|
||||
54 54 | )
|
||||
55 |-open(("test.txt"),)
|
||||
55 |+open(("test.txt"), encoding="locale",)
|
||||
56 56 | open(
|
||||
57 57 | ("test.txt"), # comment
|
||||
58 58 | )
|
||||
|
||||
unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
54 | )
|
||||
55 | open(("test.txt"),)
|
||||
56 | open(
|
||||
| ^^^^ PLW1514
|
||||
57 | ("test.txt"), # comment
|
||||
58 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
54 54 | )
|
||||
55 55 | open(("test.txt"),)
|
||||
56 56 | open(
|
||||
57 |- ("test.txt"), # comment
|
||||
57 |+ ("test.txt"), encoding="locale", # comment
|
||||
58 58 | )
|
||||
59 59 | open(
|
||||
60 60 | ("test.txt"),
|
||||
|
||||
unspecified_encoding.py:59:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
57 | ("test.txt"), # comment
|
||||
58 | )
|
||||
59 | open(
|
||||
| ^^^^ PLW1514
|
||||
60 | ("test.txt"),
|
||||
61 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
57 57 | ("test.txt"), # comment
|
||||
58 58 | )
|
||||
59 59 | open(
|
||||
60 |- ("test.txt"),
|
||||
60 |+ ("test.txt"), encoding="locale",
|
||||
61 61 | # comment
|
||||
62 62 | )
|
||||
63 63 |
|
||||
|
||||
unspecified_encoding.py:64:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
62 | )
|
||||
63 |
|
||||
64 | open((("test.txt")),)
|
||||
| ^^^^ PLW1514
|
||||
65 | open(
|
||||
66 | (("test.txt")), # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
61 61 | # comment
|
||||
62 62 | )
|
||||
63 63 |
|
||||
64 |-open((("test.txt")),)
|
||||
64 |+open((("test.txt")), encoding="locale",)
|
||||
65 65 | open(
|
||||
66 66 | (("test.txt")), # comment
|
||||
67 67 | )
|
||||
|
||||
unspecified_encoding.py:65:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
64 | open((("test.txt")),)
|
||||
65 | open(
|
||||
| ^^^^ PLW1514
|
||||
66 | (("test.txt")), # comment
|
||||
67 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
63 63 |
|
||||
64 64 | open((("test.txt")),)
|
||||
65 65 | open(
|
||||
66 |- (("test.txt")), # comment
|
||||
66 |+ (("test.txt")), encoding="locale", # comment
|
||||
67 67 | )
|
||||
68 68 | open(
|
||||
69 69 | (("test.txt")),
|
||||
|
||||
unspecified_encoding.py:68:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
66 | (("test.txt")), # comment
|
||||
67 | )
|
||||
68 | open(
|
||||
| ^^^^ PLW1514
|
||||
69 | (("test.txt")),
|
||||
70 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
66 66 | (("test.txt")), # comment
|
||||
67 67 | )
|
||||
68 68 | open(
|
||||
69 |- (("test.txt")),
|
||||
69 |+ (("test.txt")), encoding="locale",
|
||||
70 70 | # comment
|
||||
71 71 | )
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
too_many_positional_params.py:3:5: PLR0917 Too many positional arguments: (7/4)
|
||||
|
|
||||
1 | # Too many positional arguments (7/4) for max_positional=4
|
||||
2 | # OK for dummy_variable_rgx ~ "skip_.*"
|
||||
3 | def f(w, x, y, z, skip_t, skip_u, skip_v):
|
||||
| ^ PLR0917
|
||||
4 | pass
|
||||
|
|
||||
|
||||
too_many_positional_params.py:9:5: PLR0917 Too many positional arguments: (7/4)
|
||||
|
|
||||
7 | # Too many positional arguments (7/4) for max_args=4
|
||||
8 | # Too many positional arguments (7/3) for dummy_variable_rgx ~ "skip_.*"
|
||||
9 | def f(w, x, y, z, t, u, v):
|
||||
| ^ PLR0917
|
||||
10 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -1,477 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
unspecified_encoding.py:8:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
| ^^^^ PLW1514
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 |-open("test.txt")
|
||||
9 |+open("test.txt", encoding=locale.getpreferredencoding(False))
|
||||
9 10 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
|
||||
unspecified_encoding.py:9:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
7 | # Errors.
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^ PLW1514
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
9 |-io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 |+io.TextIOWrapper(io.FileIO("test.txt"), encoding=locale.getpreferredencoding(False))
|
||||
10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
12 13 | tempfile.TemporaryFile("w")
|
||||
|
||||
unspecified_encoding.py:10:1: PLW1514 [*] `io.TextIOWrapper` without explicit `encoding` argument
|
||||
|
|
||||
8 | open("test.txt")
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
| ^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
9 10 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 |-hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 |+hugo.TextIOWrapper(hugo.FileIO("test.txt"), encoding=locale.getpreferredencoding(False))
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
12 13 | tempfile.TemporaryFile("w")
|
||||
13 14 | codecs.open("test.txt")
|
||||
|
||||
unspecified_encoding.py:11:1: PLW1514 [*] `tempfile.NamedTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
9 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
9 10 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 |-tempfile.NamedTemporaryFile("w")
|
||||
12 |+tempfile.NamedTemporaryFile("w", encoding=locale.getpreferredencoding(False))
|
||||
12 13 | tempfile.TemporaryFile("w")
|
||||
13 14 | codecs.open("test.txt")
|
||||
14 15 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
||||
unspecified_encoding.py:12:1: PLW1514 [*] `tempfile.TemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
10 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
9 10 | io.TextIOWrapper(io.FileIO("test.txt"))
|
||||
10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
12 |-tempfile.TemporaryFile("w")
|
||||
13 |+tempfile.TemporaryFile("w", encoding=locale.getpreferredencoding(False))
|
||||
13 14 | codecs.open("test.txt")
|
||||
14 15 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
15 16 |
|
||||
|
||||
unspecified_encoding.py:13:1: PLW1514 [*] `codecs.open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
11 | tempfile.NamedTemporaryFile("w")
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
| ^^^^^^^^^^^ PLW1514
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
10 11 | hugo.TextIOWrapper(hugo.FileIO("test.txt"))
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
12 13 | tempfile.TemporaryFile("w")
|
||||
13 |-codecs.open("test.txt")
|
||||
14 |+codecs.open("test.txt", encoding=locale.getpreferredencoding(False))
|
||||
14 15 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
15 16 |
|
||||
16 17 | # Non-errors.
|
||||
|
||||
unspecified_encoding.py:14:1: PLW1514 [*] `tempfile.SpooledTemporaryFile` in text mode without explicit `encoding` argument
|
||||
|
|
||||
12 | tempfile.TemporaryFile("w")
|
||||
13 | codecs.open("test.txt")
|
||||
14 | tempfile.SpooledTemporaryFile(0, "w")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW1514
|
||||
15 |
|
||||
16 | # Non-errors.
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
11 12 | tempfile.NamedTemporaryFile("w")
|
||||
12 13 | tempfile.TemporaryFile("w")
|
||||
13 14 | codecs.open("test.txt")
|
||||
14 |-tempfile.SpooledTemporaryFile(0, "w")
|
||||
15 |+tempfile.SpooledTemporaryFile(0, "w", encoding=locale.getpreferredencoding(False))
|
||||
15 16 |
|
||||
16 17 | # Non-errors.
|
||||
17 18 | open("test.txt", encoding="utf-8")
|
||||
|
||||
unspecified_encoding.py:46:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
44 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 |
|
||||
46 | open("test.txt",)
|
||||
| ^^^^ PLW1514
|
||||
47 | open()
|
||||
48 | open(
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
43 44 | tempfile.SpooledTemporaryFile(0, "wb")
|
||||
44 45 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 46 |
|
||||
46 |-open("test.txt",)
|
||||
47 |+open("test.txt", encoding=locale.getpreferredencoding(False),)
|
||||
47 48 | open()
|
||||
48 49 | open(
|
||||
49 50 | "test.txt", # comment
|
||||
|
||||
unspecified_encoding.py:47:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
46 | open("test.txt",)
|
||||
47 | open()
|
||||
| ^^^^ PLW1514
|
||||
48 | open(
|
||||
49 | "test.txt", # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
44 45 | tempfile.SpooledTemporaryFile(0, )
|
||||
45 46 |
|
||||
46 47 | open("test.txt",)
|
||||
47 |-open()
|
||||
48 |+open(encoding=locale.getpreferredencoding(False))
|
||||
48 49 | open(
|
||||
49 50 | "test.txt", # comment
|
||||
50 51 | )
|
||||
|
||||
unspecified_encoding.py:48:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
46 | open("test.txt",)
|
||||
47 | open()
|
||||
48 | open(
|
||||
| ^^^^ PLW1514
|
||||
49 | "test.txt", # comment
|
||||
50 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
46 47 | open("test.txt",)
|
||||
47 48 | open()
|
||||
48 49 | open(
|
||||
49 |- "test.txt", # comment
|
||||
50 |+ "test.txt", encoding=locale.getpreferredencoding(False), # comment
|
||||
50 51 | )
|
||||
51 52 | open(
|
||||
52 53 | "test.txt",
|
||||
|
||||
unspecified_encoding.py:51:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
49 | "test.txt", # comment
|
||||
50 | )
|
||||
51 | open(
|
||||
| ^^^^ PLW1514
|
||||
52 | "test.txt",
|
||||
53 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
49 50 | "test.txt", # comment
|
||||
50 51 | )
|
||||
51 52 | open(
|
||||
52 |- "test.txt",
|
||||
53 |+ "test.txt", encoding=locale.getpreferredencoding(False),
|
||||
53 54 | # comment
|
||||
54 55 | )
|
||||
55 56 | open(("test.txt"),)
|
||||
|
||||
unspecified_encoding.py:55:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
53 | # comment
|
||||
54 | )
|
||||
55 | open(("test.txt"),)
|
||||
| ^^^^ PLW1514
|
||||
56 | open(
|
||||
57 | ("test.txt"), # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
52 53 | "test.txt",
|
||||
53 54 | # comment
|
||||
54 55 | )
|
||||
55 |-open(("test.txt"),)
|
||||
56 |+open(("test.txt"), encoding=locale.getpreferredencoding(False),)
|
||||
56 57 | open(
|
||||
57 58 | ("test.txt"), # comment
|
||||
58 59 | )
|
||||
|
||||
unspecified_encoding.py:56:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
54 | )
|
||||
55 | open(("test.txt"),)
|
||||
56 | open(
|
||||
| ^^^^ PLW1514
|
||||
57 | ("test.txt"), # comment
|
||||
58 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
54 55 | )
|
||||
55 56 | open(("test.txt"),)
|
||||
56 57 | open(
|
||||
57 |- ("test.txt"), # comment
|
||||
58 |+ ("test.txt"), encoding=locale.getpreferredencoding(False), # comment
|
||||
58 59 | )
|
||||
59 60 | open(
|
||||
60 61 | ("test.txt"),
|
||||
|
||||
unspecified_encoding.py:59:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
57 | ("test.txt"), # comment
|
||||
58 | )
|
||||
59 | open(
|
||||
| ^^^^ PLW1514
|
||||
60 | ("test.txt"),
|
||||
61 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
57 58 | ("test.txt"), # comment
|
||||
58 59 | )
|
||||
59 60 | open(
|
||||
60 |- ("test.txt"),
|
||||
61 |+ ("test.txt"), encoding=locale.getpreferredencoding(False),
|
||||
61 62 | # comment
|
||||
62 63 | )
|
||||
63 64 |
|
||||
|
||||
unspecified_encoding.py:64:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
62 | )
|
||||
63 |
|
||||
64 | open((("test.txt")),)
|
||||
| ^^^^ PLW1514
|
||||
65 | open(
|
||||
66 | (("test.txt")), # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
61 62 | # comment
|
||||
62 63 | )
|
||||
63 64 |
|
||||
64 |-open((("test.txt")),)
|
||||
65 |+open((("test.txt")), encoding=locale.getpreferredencoding(False),)
|
||||
65 66 | open(
|
||||
66 67 | (("test.txt")), # comment
|
||||
67 68 | )
|
||||
|
||||
unspecified_encoding.py:65:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
64 | open((("test.txt")),)
|
||||
65 | open(
|
||||
| ^^^^ PLW1514
|
||||
66 | (("test.txt")), # comment
|
||||
67 | )
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
63 64 |
|
||||
64 65 | open((("test.txt")),)
|
||||
65 66 | open(
|
||||
66 |- (("test.txt")), # comment
|
||||
67 |+ (("test.txt")), encoding=locale.getpreferredencoding(False), # comment
|
||||
67 68 | )
|
||||
68 69 | open(
|
||||
69 70 | (("test.txt")),
|
||||
|
||||
unspecified_encoding.py:68:1: PLW1514 [*] `open` in text mode without explicit `encoding` argument
|
||||
|
|
||||
66 | (("test.txt")), # comment
|
||||
67 | )
|
||||
68 | open(
|
||||
| ^^^^ PLW1514
|
||||
69 | (("test.txt")),
|
||||
70 | # comment
|
||||
|
|
||||
= help: Add explicit `encoding` argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | import tempfile
|
||||
4 4 | import io as hugo
|
||||
5 5 | import codecs
|
||||
6 |+import locale
|
||||
6 7 |
|
||||
7 8 | # Errors.
|
||||
8 9 | open("test.txt")
|
||||
--------------------------------------------------------------------------------
|
||||
66 67 | (("test.txt")), # comment
|
||||
67 68 | )
|
||||
68 69 | open(
|
||||
69 |- (("test.txt")),
|
||||
70 |+ (("test.txt")), encoding=locale.getpreferredencoding(False),
|
||||
70 71 | # comment
|
||||
71 72 | )
|
||||
|
||||
|
||||
@@ -238,65 +238,4 @@ flowchart TD
|
||||
block0 --> return
|
||||
```
|
||||
|
||||
## Function 8
|
||||
### Source
|
||||
```python
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
```
|
||||
|
||||
### Control Flow Graph
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start(("Start"))
|
||||
return(("End"))
|
||||
block0["pass\n"]
|
||||
block1["return 1\n"]
|
||||
block2["for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1\n"]
|
||||
|
||||
start --> block2
|
||||
block2 -- "range(5)" --> block0
|
||||
block2 -- "else" --> block1
|
||||
block1 --> return
|
||||
block0 --> return
|
||||
```
|
||||
|
||||
## Function 9
|
||||
### Source
|
||||
```python
|
||||
def func():
|
||||
for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1
|
||||
x = 1
|
||||
```
|
||||
|
||||
### Control Flow Graph
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start(("Start"))
|
||||
return(("End"))
|
||||
block0["x = 1\n"]
|
||||
block1["pass\n"]
|
||||
block2["return 1\n"]
|
||||
block3["for i in range(5):
|
||||
pass
|
||||
else:
|
||||
return 1\n"]
|
||||
|
||||
start --> block3
|
||||
block3 -- "range(5)" --> block1
|
||||
block3 -- "else" --> block2
|
||||
block2 --> return
|
||||
block1 --> block3
|
||||
block0 --> return
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -773,43 +773,4 @@ flowchart TD
|
||||
block0 --> return
|
||||
```
|
||||
|
||||
## Function 14
|
||||
### Source
|
||||
```python
|
||||
def func(point):
|
||||
match point:
|
||||
case (0, 0):
|
||||
print("Origin")
|
||||
case foo:
|
||||
raise ValueError("oops")
|
||||
```
|
||||
|
||||
### Control Flow Graph
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start(("Start"))
|
||||
return(("End"))
|
||||
block0[["`*(empty)*`"]]
|
||||
block1["raise ValueError(#quot;oops#quot;)\n"]
|
||||
block2["match point:
|
||||
case (0, 0):
|
||||
print(#quot;Origin#quot;)
|
||||
case foo:
|
||||
raise ValueError(#quot;oops#quot;)\n"]
|
||||
block3["print(#quot;Origin#quot;)\n"]
|
||||
block4["match point:
|
||||
case (0, 0):
|
||||
print(#quot;Origin#quot;)
|
||||
case foo:
|
||||
raise ValueError(#quot;oops#quot;)\n"]
|
||||
|
||||
start --> block4
|
||||
block4 -- "case (0, 0)" --> block3
|
||||
block4 -- "else" --> block2
|
||||
block3 --> block0
|
||||
block2 --> block1
|
||||
block1 --> return
|
||||
block0 --> return
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::{fmt, iter, usize};
|
||||
|
||||
use log::error;
|
||||
use ruff_python_ast::{
|
||||
Expr, ExprBooleanLiteral, Identifier, MatchCase, Pattern, PatternMatchAs, PatternMatchOr, Stmt,
|
||||
StmtFor, StmtMatch, StmtReturn, StmtTry, StmtWhile, StmtWith,
|
||||
Expr, ExprBooleanLiteral, Identifier, MatchCase, Pattern, PatternMatchAs, Stmt, StmtFor,
|
||||
StmtMatch, StmtReturn, StmtTry, StmtWhile, StmtWith,
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -416,6 +416,13 @@ fn match_case<'stmt>(
|
||||
}
|
||||
last_statement_index
|
||||
};
|
||||
// TODO: handle named arguments, e.g.
|
||||
// ```python
|
||||
// match $subject:
|
||||
// case $binding:
|
||||
// print($binding)
|
||||
// ```
|
||||
// These should also return `NextBlock::Always`.
|
||||
let next = if is_wildcard(case) {
|
||||
// Wildcard case is always taken.
|
||||
NextBlock::Always(next_block_index)
|
||||
@@ -429,25 +436,10 @@ fn match_case<'stmt>(
|
||||
BasicBlock { stmts, next }
|
||||
}
|
||||
|
||||
/// Returns true if the [`MatchCase`] is a wildcard pattern.
|
||||
/// Returns true if `pattern` is a wildcard (`_`) pattern.
|
||||
fn is_wildcard(pattern: &MatchCase) -> bool {
|
||||
/// Returns true if the [`Pattern`] is a wildcard pattern.
|
||||
fn is_wildcard_pattern(pattern: &Pattern) -> bool {
|
||||
match pattern {
|
||||
Pattern::MatchValue(_)
|
||||
| Pattern::MatchSingleton(_)
|
||||
| Pattern::MatchSequence(_)
|
||||
| Pattern::MatchMapping(_)
|
||||
| Pattern::MatchClass(_)
|
||||
| Pattern::MatchStar(_) => false,
|
||||
Pattern::MatchAs(PatternMatchAs { pattern, .. }) => pattern.is_none(),
|
||||
Pattern::MatchOr(PatternMatchOr { patterns, .. }) => {
|
||||
patterns.iter().all(is_wildcard_pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pattern.guard.is_none() && is_wildcard_pattern(&pattern.pattern)
|
||||
pattern.guard.is_none()
|
||||
&& matches!(&pattern.pattern, Pattern::MatchAs(PatternMatchAs { pattern, name, .. }) if pattern.is_none() && name.is_none())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -485,8 +477,6 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||
| Stmt::AugAssign(_)
|
||||
| Stmt::AnnAssign(_)
|
||||
| Stmt::Break(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::IpyEscapeCommand(_)
|
||||
| Stmt::Pass(_) => self.unconditional_next_block(after),
|
||||
Stmt::Continue(_) => {
|
||||
// NOTE: the next branch gets fixed up in `change_next_block`.
|
||||
@@ -648,7 +638,6 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||
| Expr::Starred(_)
|
||||
| Expr::Name(_)
|
||||
| Expr::List(_)
|
||||
| Expr::IpyEscapeCommand(_)
|
||||
| Expr::Tuple(_)
|
||||
| Expr::Slice(_) => self.unconditional_next_block(after),
|
||||
// TODO: handle these expressions.
|
||||
@@ -662,10 +651,13 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||
| Expr::Await(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::YieldFrom(_) => self.unconditional_next_block(after),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
// The tough branches are done, here is an easy one.
|
||||
Stmt::Return(_) => NextBlock::Terminate,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
};
|
||||
|
||||
// Include any statements in the block that don't divert the control flow.
|
||||
@@ -898,8 +890,6 @@ fn needs_next_block(stmts: &[Stmt]) -> bool {
|
||||
| Stmt::AnnAssign(_)
|
||||
| Stmt::Expr(_)
|
||||
| Stmt::Pass(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::IpyEscapeCommand(_)
|
||||
// TODO: check below.
|
||||
| Stmt::Break(_)
|
||||
| Stmt::Continue(_)
|
||||
@@ -909,6 +899,8 @@ fn needs_next_block(stmts: &[Stmt]) -> bool {
|
||||
| Stmt::Match(_)
|
||||
| Stmt::Try(_)
|
||||
| Stmt::Assert(_) => true,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,8 +919,6 @@ fn is_control_flow_stmt(stmt: &Stmt) -> bool {
|
||||
| Stmt::AugAssign(_)
|
||||
| Stmt::AnnAssign(_)
|
||||
| Stmt::Expr(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::IpyEscapeCommand(_)
|
||||
| Stmt::Pass(_) => false,
|
||||
Stmt::Return(_)
|
||||
| Stmt::For(_)
|
||||
@@ -941,6 +931,8 @@ fn is_control_flow_stmt(stmt: &Stmt) -> bool {
|
||||
| Stmt::Assert(_)
|
||||
| Stmt::Break(_)
|
||||
| Stmt::Continue(_) => true,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,6 @@ RUF100_3.py:23:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
23 |+print(a) # noqa: F821 # comment
|
||||
24 24 | print(a) # noqa: E501, F821 comment
|
||||
25 25 | print(a) # noqa: E501, F821 comment
|
||||
26 26 |
|
||||
|
||||
RUF100_3.py:24:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
@@ -363,8 +362,6 @@ RUF100_3.py:24:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
24 |-print(a) # noqa: E501, F821 comment
|
||||
24 |+print(a) # noqa: F821 comment
|
||||
25 25 | print(a) # noqa: E501, F821 comment
|
||||
26 26 |
|
||||
27 27 | print(a) # comment with unicode µ # noqa: E501
|
||||
|
||||
RUF100_3.py:25:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
@@ -372,8 +369,6 @@ RUF100_3.py:25:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
24 | print(a) # noqa: E501, F821 comment
|
||||
25 | print(a) # noqa: E501, F821 comment
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
26 |
|
||||
27 | print(a) # comment with unicode µ # noqa: E501
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
@@ -383,50 +378,5 @@ RUF100_3.py:25:11: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
24 24 | print(a) # noqa: E501, F821 comment
|
||||
25 |-print(a) # noqa: E501, F821 comment
|
||||
25 |+print(a) # noqa: F821 comment
|
||||
26 26 |
|
||||
27 27 | print(a) # comment with unicode µ # noqa: E501
|
||||
28 28 | print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
||||
RUF100_3.py:27:7: F821 Undefined name `a`
|
||||
|
|
||||
25 | print(a) # noqa: E501, F821 comment
|
||||
26 |
|
||||
27 | print(a) # comment with unicode µ # noqa: E501
|
||||
| ^ F821
|
||||
28 | print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
|
||||
|
||||
RUF100_3.py:27:39: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
25 | print(a) # noqa: E501, F821 comment
|
||||
26 |
|
||||
27 | print(a) # comment with unicode µ # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
28 | print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Safe fix
|
||||
24 24 | print(a) # noqa: E501, F821 comment
|
||||
25 25 | print(a) # noqa: E501, F821 comment
|
||||
26 26 |
|
||||
27 |-print(a) # comment with unicode µ # noqa: E501
|
||||
27 |+print(a) # comment with unicode µ
|
||||
28 28 | print(a) # comment with unicode µ # noqa: E501, F821
|
||||
|
||||
RUF100_3.py:28:39: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
27 | print(a) # comment with unicode µ # noqa: E501
|
||||
28 | print(a) # comment with unicode µ # noqa: E501, F821
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Safe fix
|
||||
25 25 | print(a) # noqa: E501, F821 comment
|
||||
26 26 |
|
||||
27 27 | print(a) # comment with unicode µ # noqa: E501
|
||||
28 |-print(a) # comment with unicode µ # noqa: E501, F821
|
||||
28 |+print(a) # comment with unicode µ # noqa: F821
|
||||
|
||||
|
||||
|
||||
@@ -911,180 +911,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the function has an implicit return.
|
||||
pub fn implicit_return(function: &ast::StmtFunctionDef) -> bool {
|
||||
/// Returns `true` if the body may break via a `break` statement.
|
||||
fn sometimes_breaks(stmts: &[Stmt]) -> bool {
|
||||
for stmt in stmts {
|
||||
match stmt {
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. }) => {
|
||||
if returns(body) {
|
||||
return false;
|
||||
}
|
||||
if sometimes_breaks(orelse) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
if returns(body) {
|
||||
return false;
|
||||
}
|
||||
if sometimes_breaks(orelse) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::If(ast::StmtIf {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
if std::iter::once(body)
|
||||
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
|
||||
.any(|body| sometimes_breaks(body))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
if cases.iter().any(|case| sometimes_breaks(&case.body)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
}) => {
|
||||
if sometimes_breaks(body)
|
||||
|| handlers.iter().any(|handler| {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
body,
|
||||
..
|
||||
}) = handler;
|
||||
sometimes_breaks(body)
|
||||
})
|
||||
|| sometimes_breaks(orelse)
|
||||
|| sometimes_breaks(finalbody)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::With(ast::StmtWith { body, .. }) => {
|
||||
if sometimes_breaks(body) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Break(_) => return true,
|
||||
Stmt::Return(_) => return false,
|
||||
Stmt::Raise(_) => return false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if the body may break via a `break` statement.
|
||||
fn always_breaks(stmts: &[Stmt]) -> bool {
|
||||
for stmt in stmts {
|
||||
match stmt {
|
||||
Stmt::Break(_) => return true,
|
||||
Stmt::Return(_) => return false,
|
||||
Stmt::Raise(_) => return false,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if the body contains a branch that ends without an explicit `return` or
|
||||
/// `raise` statement.
|
||||
fn returns(stmts: &[Stmt]) -> bool {
|
||||
for stmt in stmts.iter().rev() {
|
||||
match stmt {
|
||||
Stmt::For(ast::StmtFor { body, orelse, .. }) => {
|
||||
if always_breaks(body) {
|
||||
return false;
|
||||
}
|
||||
if returns(body) {
|
||||
return true;
|
||||
}
|
||||
if returns(orelse) && !sometimes_breaks(body) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
|
||||
if always_breaks(body) {
|
||||
return false;
|
||||
}
|
||||
if returns(body) {
|
||||
return true;
|
||||
}
|
||||
if returns(orelse) && !sometimes_breaks(body) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::If(ast::StmtIf {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
if elif_else_clauses.iter().any(|clause| clause.test.is_none())
|
||||
&& std::iter::once(body)
|
||||
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
|
||||
.all(|body| returns(body))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Match(ast::StmtMatch { cases, .. }) => {
|
||||
// Note: we assume the `match` is exhaustive.
|
||||
if cases.iter().all(|case| returns(&case.body)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
}) => {
|
||||
// If the `finally` block returns, the `try` block must also return.
|
||||
if returns(finalbody) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the `body` or the `else` block returns, the `try` block must also return.
|
||||
if (returns(body) || returns(orelse))
|
||||
&& handlers.iter().all(|handler| {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
body,
|
||||
..
|
||||
}) = handler;
|
||||
returns(body)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::With(ast::StmtWith { body, .. }) => {
|
||||
if returns(body) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Stmt::Return(_) => return true,
|
||||
Stmt::Raise(_) => return true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
!returns(&function.body)
|
||||
}
|
||||
|
||||
/// A [`StatementVisitor`] that collects all `raise` statements in a function or method.
|
||||
#[derive(Default)]
|
||||
pub struct RaiseStatementVisitor<'a> {
|
||||
|
||||
@@ -3729,6 +3729,204 @@ impl Ranged for crate::nodes::ParameterWithDefault {
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression that may be parenthesized.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParenthesizedExpr {
|
||||
/// The range of the expression, including any parentheses.
|
||||
pub range: TextRange,
|
||||
/// The underlying expression.
|
||||
pub expr: Expr,
|
||||
}
|
||||
impl ParenthesizedExpr {
|
||||
/// Returns `true` if the expression is may be parenthesized.
|
||||
pub fn is_parenthesized(&self) -> bool {
|
||||
self.range != self.expr.range()
|
||||
}
|
||||
}
|
||||
impl Ranged for ParenthesizedExpr {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
impl From<Expr> for ParenthesizedExpr {
|
||||
fn from(expr: Expr) -> Self {
|
||||
ParenthesizedExpr {
|
||||
range: expr.range(),
|
||||
expr,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ParenthesizedExpr> for Expr {
|
||||
fn from(parenthesized_expr: ParenthesizedExpr) -> Self {
|
||||
parenthesized_expr.expr
|
||||
}
|
||||
}
|
||||
impl From<ExprIpyEscapeCommand> for ParenthesizedExpr {
|
||||
fn from(payload: ExprIpyEscapeCommand) -> Self {
|
||||
Expr::IpyEscapeCommand(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprBoolOp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprBoolOp) -> Self {
|
||||
Expr::BoolOp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprNamedExpr> for ParenthesizedExpr {
|
||||
fn from(payload: ExprNamedExpr) -> Self {
|
||||
Expr::NamedExpr(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprBinOp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprBinOp) -> Self {
|
||||
Expr::BinOp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprUnaryOp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprUnaryOp) -> Self {
|
||||
Expr::UnaryOp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprLambda> for ParenthesizedExpr {
|
||||
fn from(payload: ExprLambda) -> Self {
|
||||
Expr::Lambda(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprIfExp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprIfExp) -> Self {
|
||||
Expr::IfExp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprDict> for ParenthesizedExpr {
|
||||
fn from(payload: ExprDict) -> Self {
|
||||
Expr::Dict(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprSet> for ParenthesizedExpr {
|
||||
fn from(payload: ExprSet) -> Self {
|
||||
Expr::Set(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprListComp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprListComp) -> Self {
|
||||
Expr::ListComp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprSetComp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprSetComp) -> Self {
|
||||
Expr::SetComp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprDictComp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprDictComp) -> Self {
|
||||
Expr::DictComp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprGeneratorExp> for ParenthesizedExpr {
|
||||
fn from(payload: ExprGeneratorExp) -> Self {
|
||||
Expr::GeneratorExp(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprAwait> for ParenthesizedExpr {
|
||||
fn from(payload: ExprAwait) -> Self {
|
||||
Expr::Await(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprYield> for ParenthesizedExpr {
|
||||
fn from(payload: ExprYield) -> Self {
|
||||
Expr::Yield(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprYieldFrom> for ParenthesizedExpr {
|
||||
fn from(payload: ExprYieldFrom) -> Self {
|
||||
Expr::YieldFrom(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprCompare> for ParenthesizedExpr {
|
||||
fn from(payload: ExprCompare) -> Self {
|
||||
Expr::Compare(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprCall> for ParenthesizedExpr {
|
||||
fn from(payload: ExprCall) -> Self {
|
||||
Expr::Call(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprFormattedValue> for ParenthesizedExpr {
|
||||
fn from(payload: ExprFormattedValue) -> Self {
|
||||
Expr::FormattedValue(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprFString> for ParenthesizedExpr {
|
||||
fn from(payload: ExprFString) -> Self {
|
||||
Expr::FString(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprStringLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprStringLiteral) -> Self {
|
||||
Expr::StringLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprBytesLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprBytesLiteral) -> Self {
|
||||
Expr::BytesLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprNumberLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprNumberLiteral) -> Self {
|
||||
Expr::NumberLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprBooleanLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprBooleanLiteral) -> Self {
|
||||
Expr::BooleanLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprNoneLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprNoneLiteral) -> Self {
|
||||
Expr::NoneLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprEllipsisLiteral> for ParenthesizedExpr {
|
||||
fn from(payload: ExprEllipsisLiteral) -> Self {
|
||||
Expr::EllipsisLiteral(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprAttribute> for ParenthesizedExpr {
|
||||
fn from(payload: ExprAttribute) -> Self {
|
||||
Expr::Attribute(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprSubscript> for ParenthesizedExpr {
|
||||
fn from(payload: ExprSubscript) -> Self {
|
||||
Expr::Subscript(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprStarred> for ParenthesizedExpr {
|
||||
fn from(payload: ExprStarred) -> Self {
|
||||
Expr::Starred(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprName> for ParenthesizedExpr {
|
||||
fn from(payload: ExprName) -> Self {
|
||||
Expr::Name(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprList> for ParenthesizedExpr {
|
||||
fn from(payload: ExprList) -> Self {
|
||||
Expr::List(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprTuple> for ParenthesizedExpr {
|
||||
fn from(payload: ExprTuple) -> Self {
|
||||
Expr::Tuple(payload).into()
|
||||
}
|
||||
}
|
||||
impl From<ExprSlice> for ParenthesizedExpr {
|
||||
fn from(payload: ExprSlice) -> Self {
|
||||
Expr::Slice(payload).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
mod size_assertions {
|
||||
use static_assertions::assert_eq_size;
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
###############################################################################
|
||||
# DOCTEST CODE EXAMPLES
|
||||
#
|
||||
# This section shows examples of docstrings that contain code snippets in
|
||||
# Python's "doctest" format.
|
||||
#
|
||||
# See: https://docs.python.org/3/library/doctest.html
|
||||
###############################################################################
|
||||
|
||||
# The simplest doctest to ensure basic formatting works.
|
||||
def doctest_simple():
|
||||
"""
|
||||
|
||||
@@ -68,10 +68,6 @@ with True:
|
||||
with True:
|
||||
... # comment
|
||||
|
||||
with True:
|
||||
...
|
||||
# comment
|
||||
|
||||
match x:
|
||||
case 1:
|
||||
...
|
||||
@@ -103,4 +99,4 @@ try:
|
||||
except:
|
||||
... # comment
|
||||
finally:
|
||||
... # comment
|
||||
... # comment
|
||||
@@ -16,12 +16,10 @@ type Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
type Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[Aaaaaaaaaaaaaaaaaaaaaaaaaaaa] = int
|
||||
type Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[Aaaaaaaaaaaaaaaaaaaaaaaaaaaa, Bbbbbbbbbbbbb] = int
|
||||
type Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = Tttttttttttttttttttttttttttttttttttttttttttttttttttttttt
|
||||
type Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = Tttttttttttttttttttttttttttttttttttttttttttttttttttttttt # with comment
|
||||
|
||||
# long value
|
||||
type X = Ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
|
||||
type X = Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | Ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
|
||||
type XXXXXXXXXXXXX = Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt # with comment
|
||||
|
||||
# soft keyword as alias name
|
||||
type type = int
|
||||
|
||||
@@ -74,11 +74,6 @@ impl<'a> PyFormatContext<'a> {
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if preview mode is enabled.
|
||||
pub(crate) const fn is_preview(&self) -> bool {
|
||||
self.options.preview().is_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatContext for PyFormatContext<'_> {
|
||||
|
||||
@@ -13,15 +13,14 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::comments::{leading_comments, trailing_comments, Comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
in_parentheses_only_group, in_parentheses_only_if_group_breaks,
|
||||
in_parentheses_only_soft_line_break, in_parentheses_only_soft_line_break_or_space,
|
||||
is_expression_parenthesized, write_in_parentheses_only_group_end_tag,
|
||||
write_in_parentheses_only_group_start_tag, Parentheses,
|
||||
in_parentheses_only_group, in_parentheses_only_soft_line_break,
|
||||
in_parentheses_only_soft_line_break_or_space, is_expression_parenthesized,
|
||||
write_in_parentheses_only_group_end_tag, write_in_parentheses_only_group_start_tag,
|
||||
Parentheses,
|
||||
};
|
||||
use crate::expression::string::{AnyString, FormatString, StringLayout};
|
||||
use crate::expression::OperatorPrecedence;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_fix_power_op_line_length_enabled;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(super) enum BinaryLike<'a> {
|
||||
@@ -719,11 +718,7 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
|
||||
)
|
||||
{
|
||||
hard_line_break().fmt(f)?;
|
||||
} else if is_pow {
|
||||
if is_fix_power_op_line_length_enabled(f.context()) {
|
||||
in_parentheses_only_if_group_breaks(&space()).fmt(f)?;
|
||||
}
|
||||
} else {
|
||||
} else if !is_pow {
|
||||
space().fmt(f)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builders::parenthesize_if_expands;
|
||||
use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments};
|
||||
use crate::comments::{
|
||||
leading_comments, trailing_comments, LeadingDanglingTrailingComments, SourceComment,
|
||||
};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::expr_generator_exp::is_generator_parenthesized;
|
||||
use crate::expression::expr_tuple::is_tuple_parenthesized;
|
||||
@@ -21,7 +23,7 @@ use crate::expression::parentheses::{
|
||||
OptionalParentheses, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled;
|
||||
use crate::PyFormatOptions;
|
||||
|
||||
mod binary_like;
|
||||
pub(crate) mod expr_attribute;
|
||||
@@ -129,7 +131,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
|
||||
let node_comments = comments.leading_dangling_trailing(expression);
|
||||
if !node_comments.has_leading() && !node_comments.has_trailing() {
|
||||
parenthesized("(", &format_expr, ")")
|
||||
.with_indent(!is_expression_huggable(expression, f.context()))
|
||||
.with_indent(!is_expression_huggable(expression, f.options()))
|
||||
.fmt(f)
|
||||
} else {
|
||||
format_with_parentheses_comments(expression, &node_comments, f)
|
||||
@@ -432,23 +434,113 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
||||
}
|
||||
|
||||
Parenthesize::IfBreaks => {
|
||||
if node_comments.has_trailing() {
|
||||
expression.format().with_options(Parentheses::Always).fmt(f)
|
||||
// Is the expression the last token in the parent statement.
|
||||
// Excludes `await` and `yield` for which Black doesn't seem to apply the layout?
|
||||
let last_expression = parent.is_stmt_assign()
|
||||
|| parent.is_stmt_ann_assign()
|
||||
|| parent.is_stmt_aug_assign()
|
||||
|| parent.is_stmt_return();
|
||||
|
||||
// Format the statements and value's trailing end of line comments:
|
||||
// * after the expression if the expression needs no parentheses (necessary or the `expand_parent` makes the group never fit).
|
||||
// * inside the parentheses if the expression exceeds the line-width.
|
||||
//
|
||||
// ```python
|
||||
// a = long # with_comment
|
||||
// b = (
|
||||
// short # with_comment
|
||||
// )
|
||||
//
|
||||
// # formatted
|
||||
// a = (
|
||||
// long # with comment
|
||||
// )
|
||||
// b = short # with comment
|
||||
// ```
|
||||
// This matches Black's formatting with the exception that ruff applies this style also for
|
||||
// attribute chains and non-fluent call expressions. See https://github.com/psf/black/issues/4001#issuecomment-1786681792
|
||||
//
|
||||
// This logic isn't implemented in [`place_comment`] by associating trailing statement comments to the expression because
|
||||
// doing so breaks the suite empty lines formatting that relies on trailing comments to be stored on the statement.
|
||||
let (inline_comments, expression_trailing_comments) = if last_expression
|
||||
&& !(
|
||||
// Ignore non-fluent attribute chains for black compatibility.
|
||||
// See https://github.com/psf/black/issues/4001#issuecomment-1786681792
|
||||
expression.is_attribute_expr()
|
||||
|| expression.is_call_expr()
|
||||
|| expression.is_yield_from_expr()
|
||||
|| expression.is_yield_expr()
|
||||
|| expression.is_await_expr()
|
||||
) {
|
||||
let parent_trailing_comments = comments.trailing(*parent);
|
||||
let after_end_of_line = parent_trailing_comments
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
let (stmt_inline_comments, _) =
|
||||
parent_trailing_comments.split_at(after_end_of_line);
|
||||
|
||||
let after_end_of_line = node_comments
|
||||
.trailing
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
|
||||
let (expression_inline_comments, expression_trailing_comments) =
|
||||
node_comments.trailing.split_at(after_end_of_line);
|
||||
|
||||
(
|
||||
OptionalParenthesesInlinedComments {
|
||||
expression: expression_inline_comments,
|
||||
statement: stmt_inline_comments,
|
||||
},
|
||||
expression_trailing_comments,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
OptionalParenthesesInlinedComments::default(),
|
||||
node_comments.trailing,
|
||||
)
|
||||
};
|
||||
|
||||
if expression_trailing_comments.is_empty() {
|
||||
// The group id is necessary because the nested expressions may reference it.
|
||||
let group_id = f.group_id("optional_parentheses");
|
||||
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
|
||||
|
||||
best_fit_parenthesize(&expression.format().with_options(Parentheses::Never))
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)
|
||||
best_fit_parenthesize(&format_with(|f| {
|
||||
inline_comments.mark_formatted();
|
||||
|
||||
expression
|
||||
.format()
|
||||
.with_options(Parentheses::Never)
|
||||
.fmt(f)?;
|
||||
|
||||
if !inline_comments.is_empty() {
|
||||
// If the expressions exceeds the line width, format the comments in the parentheses
|
||||
if_group_breaks(&inline_comments)
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
|
||||
if !inline_comments.is_empty() {
|
||||
// If the line fits into the line width, format the comments after the parenthesized expression
|
||||
if_group_fits_on_line(&inline_comments)
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
expression.format().with_options(Parentheses::Always).fmt(f)
|
||||
}
|
||||
}
|
||||
},
|
||||
OptionalParentheses::Never => match parenthesize {
|
||||
Parenthesize::IfBreaksOrIfRequired => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.with_indent(!is_expression_huggable(expression, f.context()))
|
||||
.with_indent(!is_expression_huggable(expression, f.options()))
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
@@ -1061,8 +1153,8 @@ pub(crate) fn has_own_parentheses(
|
||||
/// ]
|
||||
/// )
|
||||
/// ```
|
||||
pub(crate) fn is_expression_huggable(expr: &Expr, context: &PyFormatContext) -> bool {
|
||||
if !is_hug_parens_with_braces_and_square_brackets_enabled(context) {
|
||||
pub(crate) fn is_expression_huggable(expr: &Expr, options: &PyFormatOptions) -> bool {
|
||||
if !options.preview().is_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1156,3 +1248,41 @@ impl From<Operator> for OperatorPrecedence {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OptionalParenthesesInlinedComments<'a> {
|
||||
expression: &'a [SourceComment],
|
||||
statement: &'a [SourceComment],
|
||||
}
|
||||
|
||||
impl<'a> OptionalParenthesesInlinedComments<'a> {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.expression.is_empty() && self.statement.is_empty()
|
||||
}
|
||||
|
||||
fn iter_comments(&self) -> impl Iterator<Item = &'a SourceComment> {
|
||||
self.expression.iter().chain(self.statement)
|
||||
}
|
||||
|
||||
fn mark_formatted(&self) {
|
||||
for comment in self.iter_comments() {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for OptionalParenthesesInlinedComments<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
for comment in self.iter_comments() {
|
||||
comment.mark_unformatted();
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
trailing_comments(self.expression),
|
||||
trailing_comments(self.statement)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,26 +355,6 @@ pub(super) fn write_in_parentheses_only_group_end_tag(f: &mut PyFormatter) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows prints `content` only if the expression is enclosed by (optional) parentheses (`()`, `[]`, or `{}`)
|
||||
/// and splits across multiple lines.
|
||||
pub(super) fn in_parentheses_only_if_group_breaks<'a, T>(
|
||||
content: T,
|
||||
) -> impl Format<PyFormatContext<'a>>
|
||||
where
|
||||
T: Format<PyFormatContext<'a>>,
|
||||
{
|
||||
format_with(move |f: &mut PyFormatter| match f.context().node_level() {
|
||||
NodeLevel::TopLevel(_) | NodeLevel::CompoundStatement | NodeLevel::Expression(None) => {
|
||||
// no-op, not parenthesized
|
||||
Ok(())
|
||||
}
|
||||
NodeLevel::Expression(Some(parentheses_id)) => if_group_breaks(&content)
|
||||
.with_group_id(Some(parentheses_id))
|
||||
.fmt(f),
|
||||
NodeLevel::ParenthesizedExpression => if_group_breaks(&content).fmt(f),
|
||||
})
|
||||
}
|
||||
|
||||
/// Format comments inside empty parentheses, brackets or curly braces.
|
||||
///
|
||||
/// Empty `()`, `[]` and `{}` are special because there can be dangling comments, and they can be in
|
||||
|
||||
@@ -244,10 +244,10 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
mut lines: std::iter::Peekable<std::str::Lines<'src>>,
|
||||
) -> FormatResult<()> {
|
||||
while let Some(line) = lines.next() {
|
||||
let line = InputDocstringLine {
|
||||
line,
|
||||
let line = DocstringLine {
|
||||
line: Cow::Borrowed(line),
|
||||
offset: self.offset,
|
||||
next: lines.peek().copied(),
|
||||
is_last: lines.peek().is_none(),
|
||||
};
|
||||
// We know that the normalized string has \n line endings.
|
||||
self.offset += line.line.text_len() + "\n".text_len();
|
||||
@@ -262,7 +262,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
/// immediately to the underlying buffer. If the line starts or is part
|
||||
/// of an existing code snippet, then the lines will get buffered until
|
||||
/// the code snippet is complete.
|
||||
fn add_one(&mut self, line: InputDocstringLine<'src>) -> FormatResult<()> {
|
||||
fn add_one(&mut self, line: DocstringLine<'src>) -> FormatResult<()> {
|
||||
// Just pass through the line as-is without looking for a code snippet
|
||||
// when docstring code formatting is disabled. And also when we are
|
||||
// formatting a code snippet so as to avoid arbitrarily nested code
|
||||
@@ -271,49 +271,45 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
// not clear that it's worth the effort to support.
|
||||
if !self.f.options().docstring_code().is_enabled() || self.f.context().docstring().is_some()
|
||||
{
|
||||
return self.print_one(&line.as_output());
|
||||
return self.print_one(&line);
|
||||
}
|
||||
match self.code_example.add(line) {
|
||||
CodeExampleAddAction::Print { original } => self.print_one(&original.as_output())?,
|
||||
CodeExampleAddAction::Print { original } => self.print_one(&original)?,
|
||||
CodeExampleAddAction::Kept => {}
|
||||
CodeExampleAddAction::Reset { code, original } => {
|
||||
for codeline in code {
|
||||
self.print_one(&codeline.original.as_output())?;
|
||||
}
|
||||
self.print_one(&original.as_output())?;
|
||||
}
|
||||
CodeExampleAddAction::Format { mut kind, original } => {
|
||||
let Some(formatted_lines) = self.format(kind.code())? else {
|
||||
CodeExampleAddAction::Format {
|
||||
kind,
|
||||
code,
|
||||
original,
|
||||
} => {
|
||||
let Some(formatted_lines) = self.format(&code)? else {
|
||||
// If formatting failed in a way that should not be
|
||||
// allowed, we back out what we're doing and print the
|
||||
// original lines we found as-is as if we did nothing.
|
||||
for codeline in kind.code() {
|
||||
self.print_one(&codeline.original.as_output())?;
|
||||
for codeline in code {
|
||||
self.print_one(&codeline.original)?;
|
||||
}
|
||||
if let Some(original) = original {
|
||||
self.print_one(&original.as_output())?;
|
||||
self.print_one(&original)?;
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.already_normalized = false;
|
||||
match kind {
|
||||
CodeExampleKind::Doctest(CodeExampleDoctest { ps1_indent, .. }) => {
|
||||
CodeExampleKind::Doctest(CodeExampleDoctest { indent }) => {
|
||||
let mut lines = formatted_lines.into_iter();
|
||||
if let Some(first) = lines.next() {
|
||||
self.print_one(
|
||||
&first.map(|line| std::format!("{ps1_indent}>>> {line}")),
|
||||
)?;
|
||||
self.print_one(&first.map(|line| std::format!("{indent}>>> {line}")))?;
|
||||
for docline in lines {
|
||||
self.print_one(
|
||||
&docline.map(|line| std::format!("{ps1_indent}... {line}")),
|
||||
&docline.map(|line| std::format!("{indent}... {line}")),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(original) = original {
|
||||
self.print_one(&original.as_output())?;
|
||||
self.print_one(&original)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +321,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
/// This mostly just handles indentation and ensuring line breaks are
|
||||
/// inserted as appropriate before passing it on to the formatter to
|
||||
/// print to the buffer.
|
||||
fn print_one(&mut self, line: &OutputDocstringLine<'_>) -> FormatResult<()> {
|
||||
fn print_one(&mut self, line: &DocstringLine<'_>) -> FormatResult<()> {
|
||||
let trim_end = line.line.trim_end();
|
||||
if trim_end.is_empty() {
|
||||
return if line.is_last {
|
||||
@@ -395,14 +391,10 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
/// routine is silent about it. So from the user's perspective, this will
|
||||
/// fail silently. Ideally, this would at least emit a warning message,
|
||||
/// but at time of writing, it wasn't clear to me how to best do that.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This panics when the given slice is empty.
|
||||
fn format(
|
||||
&mut self,
|
||||
code: &[CodeExampleLine<'_>],
|
||||
) -> FormatResult<Option<Vec<OutputDocstringLine<'static>>>> {
|
||||
code: &[CodeExampleLine<'src>],
|
||||
) -> FormatResult<Option<Vec<DocstringLine<'static>>>> {
|
||||
use ruff_python_parser::AsMode;
|
||||
|
||||
let offset = code
|
||||
@@ -414,10 +406,10 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
.last()
|
||||
.expect("code blob must be non-empty")
|
||||
.original
|
||||
.is_last();
|
||||
.is_last;
|
||||
let codeblob = code
|
||||
.iter()
|
||||
.map(|line| line.code)
|
||||
.map(|line| &*line.code)
|
||||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
let printed = match docstring_format_source(self.f.options(), self.quote_style, &codeblob) {
|
||||
@@ -459,8 +451,8 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
let mut lines = printed
|
||||
.as_code()
|
||||
.lines()
|
||||
.map(|line| OutputDocstringLine {
|
||||
line: Cow::Owned(line.to_string()),
|
||||
.map(|line| DocstringLine {
|
||||
line: Cow::Owned(line.into()),
|
||||
offset,
|
||||
is_last: false,
|
||||
})
|
||||
@@ -474,72 +466,29 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||
|
||||
/// Represents a single line in a docstring.
|
||||
///
|
||||
/// This type is only used to represent the original lines in a docstring.
|
||||
/// Specifically, the line contained in this type has no changes from the input
|
||||
/// source.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct InputDocstringLine<'src> {
|
||||
/// This type is used to both represent the original lines in a docstring
|
||||
/// (the line will be borrowed) and also the newly formatted lines from code
|
||||
/// snippets (the line will be owned).
|
||||
#[derive(Clone, Debug)]
|
||||
struct DocstringLine<'src> {
|
||||
/// The actual text of the line, not including the line terminator.
|
||||
///
|
||||
/// In practice, this line is borrowed when it corresponds to an original
|
||||
/// unformatted line in a docstring, and owned when it corresponds to a
|
||||
/// reformatted line (e.g., from a code snippet) in a docstring.
|
||||
line: &'src str,
|
||||
/// The offset into the source document which this line corresponds to.
|
||||
offset: TextSize,
|
||||
/// For any input line that isn't the last line, this contains a reference
|
||||
/// to the line immediately following this one.
|
||||
///
|
||||
/// This is `None` if and only if this is the last line in the docstring.
|
||||
next: Option<&'src str>,
|
||||
}
|
||||
|
||||
impl<'src> InputDocstringLine<'src> {
|
||||
/// Borrow this input docstring line as an output docstring line.
|
||||
fn as_output(&self) -> OutputDocstringLine<'src> {
|
||||
OutputDocstringLine {
|
||||
line: Cow::Borrowed(self.line),
|
||||
offset: self.offset,
|
||||
is_last: self.is_last(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this is the last line in the docstring or not.
|
||||
fn is_last(&self) -> bool {
|
||||
self.next.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single reformatted code line in a docstring.
|
||||
///
|
||||
/// An input source line may be cheaply converted to an output source line.
|
||||
/// This is the common case: an input source line is printed pretty much as it
|
||||
/// is, with perhaps some whitespace normalization applied. The less common
|
||||
/// case is that the output docstring line owns its `line` because it was
|
||||
/// produced by reformatting a code snippet.
|
||||
#[derive(Clone, Debug)]
|
||||
struct OutputDocstringLine<'src> {
|
||||
/// The output line.
|
||||
///
|
||||
/// This is an owned variant in precisely the cases where it corresponds to
|
||||
/// a line from a reformatted code snippet. In other cases, it is borrowed
|
||||
/// from the input docstring line as-is.
|
||||
line: Cow<'src, str>,
|
||||
/// The offset into the source document which this line corresponds to.
|
||||
/// Currently, this is an estimate.
|
||||
offset: TextSize,
|
||||
/// Whether this is the last line in a docstring or not. This is determined
|
||||
/// by whether the last line in the code snippet was also the last line in
|
||||
/// the docstring. If it was, then it follows that the last line in the
|
||||
/// reformatted code snippet is also the last line in the docstring.
|
||||
/// Whether this is the last line in a docstring or not. "Last" lines have
|
||||
/// some special treatment when printing.
|
||||
is_last: bool,
|
||||
}
|
||||
|
||||
impl<'src> OutputDocstringLine<'src> {
|
||||
/// Return this reformatted line, but with the given function applied to
|
||||
/// the text of the line.
|
||||
fn map(self, mut map: impl FnMut(&str) -> String) -> OutputDocstringLine<'static> {
|
||||
OutputDocstringLine {
|
||||
impl<'src> DocstringLine<'src> {
|
||||
/// Return this line, but with the given function applied to the text of
|
||||
/// the line.
|
||||
fn map(self, mut map: impl FnMut(&str) -> String) -> DocstringLine<'static> {
|
||||
DocstringLine {
|
||||
line: Cow::Owned(map(&self.line)),
|
||||
..self
|
||||
}
|
||||
@@ -558,10 +507,9 @@ impl<'src> OutputDocstringLine<'src> {
|
||||
struct CodeExample<'src> {
|
||||
/// The kind of code example being collected, or `None` if no code example
|
||||
/// has been observed.
|
||||
///
|
||||
/// The kind is split out into a separate type so that we can pass it
|
||||
/// around and have a guarantee that a code example actually exists.
|
||||
kind: Option<CodeExampleKind<'src>>,
|
||||
kind: Option<CodeExampleKind>,
|
||||
/// The lines that have been seen so far that make up the code example.
|
||||
lines: Vec<CodeExampleLine<'src>>,
|
||||
}
|
||||
|
||||
impl<'src> CodeExample<'src> {
|
||||
@@ -572,7 +520,7 @@ impl<'src> CodeExample<'src> {
|
||||
/// the caller to perform. The typical case is a "print" action, which
|
||||
/// instructs the caller to just print the line as though it were not part
|
||||
/// of a code snippet.
|
||||
fn add(&mut self, original: InputDocstringLine<'src>) -> CodeExampleAddAction<'src> {
|
||||
fn add(&mut self, original: DocstringLine<'src>) -> CodeExampleAddAction<'src> {
|
||||
match self.kind.take() {
|
||||
// There's no existing code example being built, so we look for
|
||||
// the start of one or otherwise tell the caller we couldn't find
|
||||
@@ -581,15 +529,19 @@ impl<'src> CodeExample<'src> {
|
||||
None => CodeExampleAddAction::Kept,
|
||||
Some(original) => CodeExampleAddAction::Print { original },
|
||||
},
|
||||
Some(CodeExampleKind::Doctest(mut doctest)) => {
|
||||
if doctest.add_code_line(original) {
|
||||
Some(CodeExampleKind::Doctest(doctest)) => {
|
||||
if let Some(code) = doctest_find_ps2_prompt(&doctest.indent, &original.line) {
|
||||
let code = code.to_string();
|
||||
self.lines.push(CodeExampleLine { original, code });
|
||||
// Stay with the doctest kind while we accumulate all
|
||||
// PS2 prompts.
|
||||
self.kind = Some(CodeExampleKind::Doctest(doctest));
|
||||
return CodeExampleAddAction::Kept;
|
||||
}
|
||||
let code = std::mem::take(&mut self.lines);
|
||||
let original = self.add_start(original);
|
||||
CodeExampleAddAction::Format {
|
||||
code,
|
||||
kind: CodeExampleKind::Doctest(doctest),
|
||||
original,
|
||||
}
|
||||
@@ -606,13 +558,13 @@ impl<'src> CodeExample<'src> {
|
||||
/// This panics when the existing code-example is any non-None value. That
|
||||
/// is, this routine assumes that there is no ongoing code example being
|
||||
/// collected and looks for the beginning of another code example.
|
||||
fn add_start(
|
||||
&mut self,
|
||||
original: InputDocstringLine<'src>,
|
||||
) -> Option<InputDocstringLine<'src>> {
|
||||
assert!(self.kind.is_none(), "expected no existing code example");
|
||||
if let Some(doctest) = CodeExampleDoctest::new(original) {
|
||||
self.kind = Some(CodeExampleKind::Doctest(doctest));
|
||||
fn add_start(&mut self, original: DocstringLine<'src>) -> Option<DocstringLine<'src>> {
|
||||
assert_eq!(None, self.kind, "expected no existing code example");
|
||||
if let Some((indent, code)) = doctest_find_ps1_prompt(&original.line) {
|
||||
let indent = indent.to_string();
|
||||
let code = code.to_string();
|
||||
self.lines.push(CodeExampleLine { original, code });
|
||||
self.kind = Some(CodeExampleKind::Doctest(CodeExampleDoctest { indent }));
|
||||
return None;
|
||||
}
|
||||
Some(original)
|
||||
@@ -620,8 +572,8 @@ impl<'src> CodeExample<'src> {
|
||||
}
|
||||
|
||||
/// The kind of code example observed in a docstring.
|
||||
#[derive(Debug)]
|
||||
enum CodeExampleKind<'src> {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum CodeExampleKind {
|
||||
/// Code found in Python "doctests."
|
||||
///
|
||||
/// Documentation describing doctests and how they're recognized can be
|
||||
@@ -632,90 +584,17 @@ enum CodeExampleKind<'src> {
|
||||
/// doctest module to determine more precisely how it works.)
|
||||
///
|
||||
/// [regex matching]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L611-L622
|
||||
Doctest(CodeExampleDoctest<'src>),
|
||||
}
|
||||
|
||||
impl<'src> CodeExampleKind<'src> {
|
||||
/// Return the lines of code collected so far for this example.
|
||||
///
|
||||
/// This is borrowed mutably because it may need to mutate the code lines
|
||||
/// based on the state accrued so far.
|
||||
fn code(&mut self) -> &[CodeExampleLine<'src>] {
|
||||
match *self {
|
||||
CodeExampleKind::Doctest(ref doctest) => &doctest.lines,
|
||||
}
|
||||
}
|
||||
Doctest(CodeExampleDoctest),
|
||||
}
|
||||
|
||||
/// State corresponding to a single doctest code example found in a docstring.
|
||||
#[derive(Debug)]
|
||||
struct CodeExampleDoctest<'src> {
|
||||
/// The lines that have been seen so far that make up the doctest.
|
||||
lines: Vec<CodeExampleLine<'src>>,
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct CodeExampleDoctest {
|
||||
/// The indent observed in the first doctest line.
|
||||
///
|
||||
/// More precisely, this corresponds to the whitespace observed before
|
||||
/// the starting `>>> ` (the "PS1 prompt").
|
||||
ps1_indent: &'src str,
|
||||
}
|
||||
|
||||
impl<'src> CodeExampleDoctest<'src> {
|
||||
/// Looks for a valid doctest PS1 prompt in the line given.
|
||||
///
|
||||
/// If one was found, then state for a new doctest code example is
|
||||
/// returned, along with the code example line.
|
||||
fn new(original: InputDocstringLine<'src>) -> Option<CodeExampleDoctest<'src>> {
|
||||
let trim_start = original.line.trim_start();
|
||||
// Prompts must be followed by an ASCII space character[1].
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L809-L812
|
||||
let code = trim_start.strip_prefix(">>> ")?;
|
||||
let indent_len = original
|
||||
.line
|
||||
.len()
|
||||
.checked_sub(trim_start.len())
|
||||
.expect("suffix is <= original");
|
||||
let lines = vec![CodeExampleLine { original, code }];
|
||||
let ps1_indent = &original.line[..indent_len];
|
||||
let doctest = CodeExampleDoctest { lines, ps1_indent };
|
||||
Some(doctest)
|
||||
}
|
||||
|
||||
/// Looks for a valid doctest PS2 prompt in the line given.
|
||||
///
|
||||
/// If one is found, then the code portion of the line following the PS2 prompt
|
||||
/// is returned.
|
||||
///
|
||||
/// Callers must provide a string containing the original indentation of the
|
||||
/// PS1 prompt that started the doctest containing the potential PS2 prompt
|
||||
/// in the line given. If the line contains a PS2 prompt, its indentation must
|
||||
/// match the indentation used for the corresponding PS1 prompt (otherwise
|
||||
/// `None` will be returned).
|
||||
fn add_code_line(&mut self, original: InputDocstringLine<'src>) -> bool {
|
||||
let Some((ps2_indent, ps2_after)) = original.line.split_once("...") else {
|
||||
return false;
|
||||
};
|
||||
// PS2 prompts must have the same indentation as their
|
||||
// corresponding PS1 prompt.[1] While the 'doctest' Python
|
||||
// module will error in this case, we just treat this line as a
|
||||
// non-doctest line.
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L733
|
||||
if self.ps1_indent != ps2_indent {
|
||||
return false;
|
||||
}
|
||||
// PS2 prompts must be followed by an ASCII space character unless
|
||||
// it's an otherwise empty line[1].
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L809-L812
|
||||
let code = match ps2_after.strip_prefix(' ') {
|
||||
None if ps2_after.is_empty() => "",
|
||||
None => return false,
|
||||
Some(code) => code,
|
||||
};
|
||||
self.lines.push(CodeExampleLine { original, code });
|
||||
true
|
||||
}
|
||||
indent: String,
|
||||
}
|
||||
|
||||
/// A single line in a code example found in a docstring.
|
||||
@@ -736,9 +615,9 @@ struct CodeExampleLine<'src> {
|
||||
/// The normalized (but original) line from the doc string. This might, for
|
||||
/// example, contain a `>>> ` or `... ` prefix if this code example is a
|
||||
/// doctest.
|
||||
original: InputDocstringLine<'src>,
|
||||
original: DocstringLine<'src>,
|
||||
/// The code extracted from the line.
|
||||
code: &'src str,
|
||||
code: String,
|
||||
}
|
||||
|
||||
/// An action that a caller should perform after attempting to add a line from
|
||||
@@ -754,7 +633,7 @@ enum CodeExampleAddAction<'src> {
|
||||
///
|
||||
/// This is the common case. That is, most lines in most docstrings are not
|
||||
/// part of a code example.
|
||||
Print { original: InputDocstringLine<'src> },
|
||||
Print { original: DocstringLine<'src> },
|
||||
/// The line added was kept by `CodeExample` as part of a new or existing
|
||||
/// code example.
|
||||
///
|
||||
@@ -768,31 +647,70 @@ enum CodeExampleAddAction<'src> {
|
||||
/// callers should pass it through to the formatter as-is.
|
||||
Format {
|
||||
/// The kind of code example that was found.
|
||||
kind: CodeExampleKind,
|
||||
/// The Python code that should be formatted, indented and printed.
|
||||
///
|
||||
/// This is guaranteed to have a non-empty code snippet.
|
||||
kind: CodeExampleKind<'src>,
|
||||
/// This is guaranteed to be non-empty.
|
||||
code: Vec<CodeExampleLine<'src>>,
|
||||
/// When set, the line is considered not part of any code example and
|
||||
/// should be formatted as if the [`Print`] action were returned.
|
||||
/// Otherwise, if there is no line, then either one does not exist
|
||||
/// or it is part of another code example and should be treated as a
|
||||
/// [`Kept`] action.
|
||||
original: Option<InputDocstringLine<'src>>,
|
||||
},
|
||||
/// This occurs when adding a line to an existing code example
|
||||
/// results in that code example becoming invalid. In this case,
|
||||
/// we don't want to treat it as a code example, but instead write
|
||||
/// back the lines to the docstring unchanged.
|
||||
#[allow(dead_code)] // FIXME: remove when reStructuredText support is added
|
||||
Reset {
|
||||
/// The lines of code that we collected but should be printed back to
|
||||
/// the docstring as-is and not formatted.
|
||||
code: Vec<CodeExampleLine<'src>>,
|
||||
/// The line that was added and triggered this reset to occur. It
|
||||
/// should be written back to the docstring as-is after the code lines.
|
||||
original: InputDocstringLine<'src>,
|
||||
original: Option<DocstringLine<'src>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Looks for a valid doctest PS1 prompt in the line given.
|
||||
///
|
||||
/// If one was found, then the indentation prior to the prompt is returned
|
||||
/// along with the code portion of the line.
|
||||
fn doctest_find_ps1_prompt(line: &str) -> Option<(&str, &str)> {
|
||||
let trim_start = line.trim_start();
|
||||
// Prompts must be followed by an ASCII space character[1].
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L809-L812
|
||||
let code = trim_start.strip_prefix(">>> ")?;
|
||||
let indent_len = line
|
||||
.len()
|
||||
.checked_sub(trim_start.len())
|
||||
.expect("suffix is <= original");
|
||||
let indent = &line[..indent_len];
|
||||
Some((indent, code))
|
||||
}
|
||||
|
||||
/// Looks for a valid doctest PS2 prompt in the line given.
|
||||
///
|
||||
/// If one is found, then the code portion of the line following the PS2 prompt
|
||||
/// is returned.
|
||||
///
|
||||
/// Callers must provide a string containing the original indentation of the
|
||||
/// PS1 prompt that started the doctest containing the potential PS2 prompt
|
||||
/// in the line given. If the line contains a PS2 prompt, its indentation must
|
||||
/// match the indentation used for the corresponding PS1 prompt (otherwise
|
||||
/// `None` will be returned).
|
||||
fn doctest_find_ps2_prompt<'src>(ps1_indent: &str, line: &'src str) -> Option<&'src str> {
|
||||
let (ps2_indent, ps2_after) = line.split_once("...")?;
|
||||
// PS2 prompts must have the same indentation as their
|
||||
// corresponding PS1 prompt.[1] While the 'doctest' Python
|
||||
// module will error in this case, we just treat this line as a
|
||||
// non-doctest line.
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L733
|
||||
if ps1_indent != ps2_indent {
|
||||
return None;
|
||||
}
|
||||
// PS2 prompts must be followed by an ASCII space character unless
|
||||
// it's an otherwise empty line[1].
|
||||
//
|
||||
// [1]: https://github.com/python/cpython/blob/0ff6368519ed7542ad8b443de01108690102420a/Lib/doctest.py#L809-L812
|
||||
match ps2_after.strip_prefix(' ') {
|
||||
None if ps2_after.is_empty() => Some(""),
|
||||
None => None,
|
||||
Some(code) => Some(code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the given source code using the given options.
|
||||
///
|
||||
/// The given quote style should correspond to the style used by the docstring
|
||||
|
||||
@@ -32,7 +32,6 @@ mod options;
|
||||
pub(crate) mod other;
|
||||
pub(crate) mod pattern;
|
||||
mod prelude;
|
||||
mod preview;
|
||||
mod shared_traits;
|
||||
pub(crate) mod statement;
|
||||
pub(crate) mod type_param;
|
||||
|
||||
@@ -119,7 +119,7 @@ impl PyFormatOptions {
|
||||
self.docstring_code
|
||||
}
|
||||
|
||||
pub const fn preview(&self) -> PreviewMode {
|
||||
pub fn preview(&self) -> PreviewMode {
|
||||
self.preview
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::expression::is_expression_huggable;
|
||||
use crate::expression::parentheses::{empty_parenthesized, parenthesized, Parentheses};
|
||||
use crate::other::commas;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatArguments;
|
||||
@@ -178,7 +177,8 @@ fn is_single_argument_parenthesized(argument: &Expr, call_end: TextSize, source:
|
||||
/// Hugging should only be applied to single-argument collections, like lists, or starred versions
|
||||
/// of those collections.
|
||||
fn is_argument_huggable(item: &Arguments, context: &PyFormatContext) -> bool {
|
||||
if !is_hug_parens_with_braces_and_square_brackets_enabled(context) {
|
||||
let options = context.options();
|
||||
if !options.preview().is_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ fn is_argument_huggable(item: &Arguments, context: &PyFormatContext) -> bool {
|
||||
};
|
||||
|
||||
// If the expression itself isn't huggable, then we can't hug it.
|
||||
if !is_expression_huggable(arg, context) {
|
||||
if !is_expression_huggable(arg, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -202,8 +202,6 @@ fn is_argument_huggable(item: &Arguments, context: &PyFormatContext) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let options = context.options();
|
||||
|
||||
// If the expression has a trailing comma, then we can't hug it.
|
||||
if options.magic_trailing_comma().is_respect()
|
||||
&& commas::has_magic_trailing_comma(TextRange::new(arg.end(), item.end()), options, context)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
//! Helpers to test if a specific preview style is enabled or not.
|
||||
//!
|
||||
//! The motivation for these functions isn't to avoid code duplication but to ease promoting preview styles
|
||||
//! to stable. The challenge with directly using [`is_preview`](PyFormatContext::is_preview) is that it is unclear
|
||||
//! for which specific feature this preview check is for. Having named functions simplifies the promotion:
|
||||
//! Simply delete the function and let Rust tell you which checks you have to remove.
|
||||
use crate::PyFormatContext;
|
||||
|
||||
/// Returns `true` if the [`fix_power_op_line_length`](https://github.com/astral-sh/ruff/issues/8938) preview style is enabled.
|
||||
pub(crate) const fn is_fix_power_op_line_length_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`hug_parens_with_braces_and_square_brackets`](https://github.com/astral-sh/ruff/issues/8279) preview style is enabled.
|
||||
pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled(
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
@@ -2,8 +2,10 @@ use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAnnAssign;
|
||||
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
use crate::statement::trailing_semicolon;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -31,7 +33,7 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
space(),
|
||||
token("="),
|
||||
space(),
|
||||
FormatStatementsLastExpression::new(value, item)
|
||||
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
|
||||
]
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use ruff_formatter::{format_args, write, FormatError};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, StmtAssign};
|
||||
use ruff_python_ast::{Expr, StmtAssign};
|
||||
|
||||
use crate::comments::{trailing_comments, SourceComment, SuppressionKind};
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::parentheses::{
|
||||
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
|
||||
};
|
||||
use crate::expression::parentheses::{Parentheses, Parenthesize};
|
||||
use crate::expression::{has_own_parentheses, maybe_parenthesize_expression};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::trailing_semicolon;
|
||||
@@ -36,7 +34,14 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
|
||||
]
|
||||
)?;
|
||||
|
||||
FormatStatementsLastExpression::new(value, item).fmt(f)?;
|
||||
write!(
|
||||
f,
|
||||
[maybe_parenthesize_expression(
|
||||
value,
|
||||
item,
|
||||
Parenthesize::IfBreaks
|
||||
)]
|
||||
)?;
|
||||
|
||||
if f.options().source_type().is_ipynb()
|
||||
&& f.context().node_level().is_last_top_level_statement()
|
||||
@@ -128,188 +133,3 @@ enum ParenthesizeTarget {
|
||||
Never,
|
||||
IfBreaks,
|
||||
}
|
||||
|
||||
/// Formats the last expression in statements that start with a keyword (like `return`) or after an operator (assignments).
|
||||
///
|
||||
/// It avoids parenthesizing unsplittable values (like `None`, `True`, `False`, Names, a subset of strings) just to make
|
||||
/// the trailing comment fit and inlines a trailing comment if the value itself exceeds the configured line width:
|
||||
///
|
||||
/// The implementation formats the statement's and value's trailing end of line comments:
|
||||
/// * after the expression if the expression needs no parentheses (necessary or the `expand_parent` makes the group never fit).
|
||||
/// * inside the parentheses if the expression exceeds the line-width.
|
||||
///
|
||||
/// ```python
|
||||
/// a = loooooooooooooooooooooooooooong # with_comment
|
||||
/// b = (
|
||||
/// short # with_comment
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Which gets formatted to:
|
||||
///
|
||||
/// ```python
|
||||
/// # formatted
|
||||
/// a = (
|
||||
/// loooooooooooooooooooooooooooong # with comment
|
||||
/// )
|
||||
/// b = short # with comment
|
||||
/// ```
|
||||
///
|
||||
/// The long name gets parenthesized because it exceeds the configured line width and the trailing comma of the
|
||||
/// statement gets formatted inside (instead of outside) the parentheses.
|
||||
///
|
||||
/// The `short` name gets unparenthesized because it fits into the configured line length, regardless of whether
|
||||
/// the comment exceeds the line width or not.
|
||||
///
|
||||
/// This logic isn't implemented in [`place_comment`] by associating trailing statement comments to the expression because
|
||||
/// doing so breaks the suite empty lines formatting that relies on trailing comments to be stored on the statement.
|
||||
pub(super) struct FormatStatementsLastExpression<'a> {
|
||||
expression: &'a Expr,
|
||||
parent: AnyNodeRef<'a>,
|
||||
}
|
||||
|
||||
impl<'a> FormatStatementsLastExpression<'a> {
|
||||
pub(super) fn new<P: Into<AnyNodeRef<'a>>>(expression: &'a Expr, parent: P) -> Self {
|
||||
Self {
|
||||
expression,
|
||||
parent: parent.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
let can_inline_comment = match self.expression {
|
||||
Expr::Name(_)
|
||||
| Expr::NoneLiteral(_)
|
||||
| Expr::NumberLiteral(_)
|
||||
| Expr::BooleanLiteral(_) => true,
|
||||
Expr::StringLiteral(string) => {
|
||||
string.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
|
||||
}
|
||||
Expr::BytesLiteral(bytes) => {
|
||||
bytes.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
|
||||
}
|
||||
Expr::FString(fstring) => {
|
||||
fstring.needs_parentheses(self.parent, f.context()) == OptionalParentheses::BestFit
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !can_inline_comment {
|
||||
return maybe_parenthesize_expression(
|
||||
self.expression,
|
||||
self.parent,
|
||||
Parenthesize::IfBreaks,
|
||||
)
|
||||
.fmt(f);
|
||||
}
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
let expression_comments = comments.leading_dangling_trailing(self.expression);
|
||||
|
||||
if expression_comments.has_leading() {
|
||||
// Preserve the parentheses if the expression has any leading comments,
|
||||
// same as `maybe_parenthesize_expression`
|
||||
return self
|
||||
.expression
|
||||
.format()
|
||||
.with_options(Parentheses::Always)
|
||||
.fmt(f);
|
||||
}
|
||||
|
||||
let statement_trailing_comments = comments.trailing(self.parent);
|
||||
let after_end_of_line = statement_trailing_comments
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
let (stmt_inline_comments, _) = statement_trailing_comments.split_at(after_end_of_line);
|
||||
|
||||
let after_end_of_line = expression_comments
|
||||
.trailing
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
|
||||
let (expression_inline_comments, expression_trailing_comments) =
|
||||
expression_comments.trailing.split_at(after_end_of_line);
|
||||
|
||||
if expression_trailing_comments.is_empty() {
|
||||
let inline_comments = OptionalParenthesesInlinedComments {
|
||||
expression: expression_inline_comments,
|
||||
statement: stmt_inline_comments,
|
||||
};
|
||||
|
||||
let group_id = f.group_id("optional_parentheses");
|
||||
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
|
||||
|
||||
best_fit_parenthesize(&format_with(|f| {
|
||||
inline_comments.mark_formatted();
|
||||
|
||||
self.expression
|
||||
.format()
|
||||
.with_options(Parentheses::Never)
|
||||
.fmt(f)?;
|
||||
|
||||
if !inline_comments.is_empty() {
|
||||
// If the expressions exceeds the line width, format the comments in the parentheses
|
||||
if_group_breaks(&inline_comments)
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
|
||||
if !inline_comments.is_empty() {
|
||||
// If the line fits into the line width, format the comments after the parenthesized expression
|
||||
if_group_fits_on_line(&inline_comments)
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
self.expression
|
||||
.format()
|
||||
.with_options(Parentheses::Always)
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OptionalParenthesesInlinedComments<'a> {
|
||||
expression: &'a [SourceComment],
|
||||
statement: &'a [SourceComment],
|
||||
}
|
||||
|
||||
impl<'a> OptionalParenthesesInlinedComments<'a> {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.expression.is_empty() && self.statement.is_empty()
|
||||
}
|
||||
|
||||
fn iter_comments(&self) -> impl Iterator<Item = &'a SourceComment> {
|
||||
self.expression.iter().chain(self.statement)
|
||||
}
|
||||
|
||||
fn mark_formatted(&self) {
|
||||
for comment in self.expression {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for OptionalParenthesesInlinedComments<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
for comment in self.iter_comments() {
|
||||
comment.mark_unformatted();
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
trailing_comments(self.expression),
|
||||
trailing_comments(self.statement)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAugAssign;
|
||||
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{AsFormat, FormatNodeRule};
|
||||
|
||||
@@ -26,7 +28,7 @@ impl FormatNodeRule<StmtAugAssign> for FormatStmtAugAssign {
|
||||
op.format(),
|
||||
token("="),
|
||||
space(),
|
||||
FormatStatementsLastExpression::new(value, item)
|
||||
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
|
||||
]
|
||||
)?;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use ruff_python_ast::{Expr, StmtReturn};
|
||||
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
use crate::expression::expr_tuple::TupleParentheses;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtReturn;
|
||||
@@ -30,7 +31,10 @@ impl FormatNodeRule<StmtReturn> for FormatStmtReturn {
|
||||
Some(value) => {
|
||||
write!(
|
||||
f,
|
||||
[space(), FormatStatementsLastExpression::new(value, item)]
|
||||
[
|
||||
space(),
|
||||
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
|
||||
]
|
||||
)
|
||||
}
|
||||
None => Ok(()),
|
||||
|
||||
@@ -2,8 +2,9 @@ use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtTypeAlias;
|
||||
|
||||
use crate::comments::{SourceComment, SuppressionKind};
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtTypeAlias;
|
||||
@@ -29,7 +30,7 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
|
||||
space(),
|
||||
token("="),
|
||||
space(),
|
||||
FormatStatementsLastExpression::new(value, item)
|
||||
maybe_parenthesize_expression(value, item, Parenthesize::IfBreaks)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -511,9 +511,7 @@ pub(crate) fn contains_only_an_ellipsis(body: &[Stmt], comments: &Comments) -> b
|
||||
let [node] = body else {
|
||||
return false;
|
||||
};
|
||||
value.is_ellipsis_literal_expr()
|
||||
&& !comments.has_leading(node)
|
||||
&& !comments.has_trailing_own_line(node)
|
||||
value.is_ellipsis_literal_expr() && !comments.has_leading(node)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user