Compare commits
128 Commits
charlie/do
...
v0.0.276
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3992c47c00 | ||
|
|
8de5a3d29d | ||
|
|
ed1dd09d02 | ||
|
|
ca497fabbd | ||
|
|
00fbbe4223 | ||
|
|
dadad0e9ed | ||
|
|
d2450c25ab | ||
|
|
1e4b88969c | ||
|
|
dc072537e5 | ||
|
|
7ac9e0252e | ||
|
|
ca6ff72404 | ||
|
|
94ac2c4e1b | ||
|
|
93b2bd7184 | ||
|
|
0bff4ed4d3 | ||
|
|
df13e69c3c | ||
|
|
c8b9a46e2b | ||
|
|
6cc04d64e4 | ||
|
|
d0b2fffb87 | ||
|
|
b32d1e8d78 | ||
|
|
af7051b976 | ||
|
|
f0ec9ecd67 | ||
|
|
f9129e435a | ||
|
|
dc65007fe9 | ||
|
|
9c2a75284b | ||
|
|
ae25638b0b | ||
|
|
f7969cf23c | ||
|
|
955e9ef821 | ||
|
|
38189ed913 | ||
|
|
ca5e10b5ea | ||
|
|
a973019358 | ||
|
|
aa887d5a1d | ||
|
|
72f7f11bac | ||
|
|
5aa2a90e17 | ||
|
|
0e89c94947 | ||
|
|
139a9f757b | ||
|
|
c5e20505f8 | ||
|
|
69c4b7fa11 | ||
|
|
0e12eb3071 | ||
|
|
864f50a3a4 | ||
|
|
4d90a5a9bc | ||
|
|
1d2d015bc5 | ||
|
|
ea7bb199bc | ||
|
|
979049b2a6 | ||
|
|
6587fb844a | ||
|
|
a68a86e18b | ||
|
|
b42d76494c | ||
|
|
c7adb9117f | ||
|
|
1979103ec0 | ||
|
|
9e2fd0c620 | ||
|
|
366edc5a3f | ||
|
|
2aecaf5060 | ||
|
|
d19324df69 | ||
|
|
2c99b268c6 | ||
|
|
56f73de0cb | ||
|
|
a0a93a636f | ||
|
|
035f8993f4 | ||
|
|
032b967b05 | ||
|
|
962479d943 | ||
|
|
ff0d0ab7a0 | ||
|
|
0585e14d3b | ||
|
|
1ed227a1e0 | ||
|
|
502e15585d | ||
|
|
520f4f33c3 | ||
|
|
7f6cb9dfb5 | ||
|
|
50a7769d69 | ||
|
|
190bed124f | ||
|
|
d53b986fd4 | ||
|
|
8a1bb7a5af | ||
|
|
2fc38d81e6 | ||
|
|
fa1b85b3da | ||
|
|
baa7264ca4 | ||
|
|
fde3f09370 | ||
|
|
d00559e42a | ||
|
|
49cabca3e7 | ||
|
|
313711aaf9 | ||
|
|
f18a1f70de | ||
|
|
dd0d1afb66 | ||
|
|
a52cd47c7f | ||
|
|
8879927b9a | ||
|
|
dce6a046b0 | ||
|
|
18c73c1f9b | ||
|
|
19c221a2d2 | ||
|
|
fd0c3faa70 | ||
|
|
1fe4073b56 | ||
|
|
b233763156 | ||
|
|
1ef4eee089 | ||
|
|
0ce38b650e | ||
|
|
e0a507e48e | ||
|
|
adf5cb5ff7 | ||
|
|
d3d69a031e | ||
|
|
6ba9d5d5a4 | ||
|
|
f45d1c2b84 | ||
|
|
4b65446de6 | ||
|
|
cb580f960f | ||
|
|
f85eb709e2 | ||
|
|
2f03159c8b | ||
|
|
1c638264b2 | ||
|
|
2dfa6ff58d | ||
|
|
930f03de98 | ||
|
|
c52aa8f065 | ||
|
|
3e12bdff45 | ||
|
|
2142bf6141 | ||
|
|
1cf307c34c | ||
|
|
7819b95d7f | ||
|
|
4a81cfc51a | ||
|
|
38e618cd18 | ||
|
|
50f0edd2cb | ||
|
|
e0e1d13d9f | ||
|
|
8bc7378002 | ||
|
|
cdbd0bd5cd | ||
|
|
5f88ff8a96 | ||
|
|
1c2be54b4a | ||
|
|
5dd00b19e6 | ||
|
|
c0f93fcf3e | ||
|
|
3238a6ef1f | ||
|
|
96ecfae1c5 | ||
|
|
03694ef649 | ||
|
|
f9f0cf7524 | ||
|
|
eaa10ad2d9 | ||
|
|
84259f5440 | ||
|
|
e8ebe0a425 | ||
|
|
d407165aa7 | ||
|
|
f7e1cf4b51 | ||
|
|
7d4f8e59da | ||
|
|
2c63f8cdea | ||
|
|
1c0a3a467f | ||
|
|
6b8b318d6b | ||
|
|
c0c59b82ec |
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -491,16 +491,16 @@ jobs:
|
||||
- name: "Publish to GitHub"
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
draft: false
|
||||
draft: true
|
||||
files: binaries/*
|
||||
tag_name: v${{ inputs.tag }}
|
||||
|
||||
# After the release has been published, we update downstream repositories
|
||||
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
|
||||
update-dependents:
|
||||
name: Release
|
||||
name: Update dependents
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
needs: publish-release
|
||||
steps:
|
||||
- name: "Update pre-commit mirror"
|
||||
uses: actions/github-script@v6
|
||||
|
||||
@@ -6,7 +6,9 @@ exclude: |
|
||||
crates/ruff/src/rules/.*/snapshots/.*|
|
||||
crates/ruff_cli/resources/.*|
|
||||
crates/ruff_python_formatter/resources/.*|
|
||||
crates/ruff_python_formatter/src/snapshots/.*
|
||||
crates/ruff_python_formatter/tests/snapshots/.*|
|
||||
crates/ruff_python_resolver/resources/.*|
|
||||
crates/ruff_python_resolver/tests/snapshots/.*
|
||||
)$
|
||||
|
||||
repos:
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.276
|
||||
|
||||
### The `keep-runtime-typing` setting has been reinstated ([#5470](https://github.com/astral-sh/ruff/pull/5470))
|
||||
|
||||
The `keep-runtime-typing` setting has been reinstated with revised semantics. This setting was
|
||||
removed in [#4427](https://github.com/astral-sh/ruff/pull/4427), as it was equivalent to ignoring
|
||||
the `UP006` and `UP007` rules via Ruff's standard `ignore` mechanism.
|
||||
|
||||
Taking `UP006` (rewrite `List[int]` to `list[int]`) as an example, the setting now behaves as
|
||||
follows:
|
||||
|
||||
- On Python 3.7 and Python 3.8, setting `keep-runtime-typing = true` will cause Ruff to ignore
|
||||
`UP006` violations, even if `from __future__ import annotations` is present in the file.
|
||||
While such annotations are valid in Python 3.7 and Python 3.8 when combined with
|
||||
`from __future__ import annotations`, they aren't supported by libraries like Pydantic and
|
||||
FastAPI, which rely on runtime type checking.
|
||||
- On Python 3.9 and above, the setting has no effect, as `list[int]` is a valid type annotation,
|
||||
and libraries like Pydantic and FastAPI support it without issue.
|
||||
|
||||
In short: `keep-runtime-typing` can be used to ensure that Ruff doesn't introduce type annotations
|
||||
that are not supported at runtime by the current Python version, which are unsupported by libraries
|
||||
like Pydantic and FastAPI.
|
||||
|
||||
Note that this is not a breaking change, but is included here to complement the previous removal
|
||||
of `keep-runtime-typing`.
|
||||
|
||||
## 0.0.268
|
||||
|
||||
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/astral-sh/ruff/pull/4427))
|
||||
|
||||
@@ -327,22 +327,18 @@ git clone --branch 3.10 https://github.com/python/cpython.git crates/ruff/resour
|
||||
To benchmark the release build:
|
||||
|
||||
```shell
|
||||
cargo build --release && hyperfine --ignore-failure --warmup 10 \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache" \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/"
|
||||
cargo build --release && hyperfine --warmup 10 \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e" \
|
||||
"./target/release/ruff ./crates/ruff/resources/test/cpython/ -e"
|
||||
|
||||
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
|
||||
Time (mean ± σ): 293.8 ms ± 3.2 ms [User: 2384.6 ms, System: 90.3 ms]
|
||||
Range (min … max): 289.9 ms … 301.6 ms 10 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Benchmark 2: ./target/release/ruff ./crates/ruff/resources/test/cpython/
|
||||
Time (mean ± σ): 48.0 ms ± 3.1 ms [User: 65.2 ms, System: 124.7 ms]
|
||||
Range (min … max): 45.0 ms … 66.7 ms 62 runs
|
||||
|
||||
Warning: Ignoring non-zero exit code.
|
||||
|
||||
Summary
|
||||
'./target/release/ruff ./crates/ruff/resources/test/cpython/' ran
|
||||
6.12 ± 0.41 times faster than './target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache'
|
||||
@@ -503,7 +499,7 @@ examples.
|
||||
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
|
||||
|
||||
```shell
|
||||
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
|
||||
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record --call-graph dwarf -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
|
||||
```
|
||||
|
||||
You can also use the `ruff_dev` launcher to run `ruff check` multiple times on a repository to
|
||||
|
||||
435
Cargo.lock
generated
435
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
21
Cargo.toml
@@ -21,7 +21,7 @@ filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.28.0" }
|
||||
insta = { version = "1.30.0" }
|
||||
is-macro = { version = "0.2.2" }
|
||||
itertools = { version = "0.10.5" }
|
||||
log = { version = "0.4.17" }
|
||||
@@ -49,16 +49,15 @@ toml = { version = "0.7.2" }
|
||||
|
||||
# v0.0.1
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
|
||||
# v0.0.3
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" }
|
||||
# v0.0.3
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
|
||||
# v0.0.3
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false, features = ["num-bigint"] }
|
||||
# v0.0.3
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false }
|
||||
# v0.0.3
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
|
||||
|
||||
# Please tag the RustPython version everytime you update its revision here and in fuzz/Cargo.toml
|
||||
# Tagging the version ensures that older ruff versions continue to build from source even when we rebase our RustPython fork.
|
||||
# Current tag: v0.0.7
|
||||
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" }
|
||||
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["num-bigint"]}
|
||||
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false, features = ["num-bigint"] }
|
||||
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0", default-features = false }
|
||||
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "c174bbf1f29527edd43d432326327f16f47ab9e0" , default-features = false, features = ["full-lexer", "num-bigint"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
||||
26
LICENSE
26
LICENSE
@@ -1224,6 +1224,32 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- Pyright, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Pyright - A static type checker for the Python language
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
"""
|
||||
|
||||
- rust-analyzer/text-size, licensed under the MIT license:
|
||||
"""
|
||||
Permission is hereby granted, free of charge, to any
|
||||
|
||||
@@ -139,7 +139,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.274
|
||||
rev: v0.0.276
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -330,9 +330,11 @@ We're grateful to the maintainers of these tools for their work, and for all
|
||||
the value they've provided to the Python community.
|
||||
|
||||
Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
|
||||
and again draws on both the APIs and implementation details of [Rome](https://github.com/rome/tools),
|
||||
and again draws on both API and implementation details from [Rome](https://github.com/rome/tools),
|
||||
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).
|
||||
|
||||
Ruff's import resolver is based on the import resolution algorithm from [Pyright](https://github.com/microsoft/pyright).
|
||||
|
||||
Ruff is also influenced by a number of tools outside the Python ecosystem, like
|
||||
[Clippy](https://github.com/rust-lang/rust-clippy) and [ESLint](https://github.com/eslint/eslint).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.274"
|
||||
version = "0.0.276"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.274"
|
||||
version = "0.0.276"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -42,6 +42,7 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
log = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
natord = { version = "1.0.9" }
|
||||
nohash-hasher = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
@@ -87,4 +88,3 @@ colored = { workspace = true, features = ["no-color"] }
|
||||
[features]
|
||||
default = []
|
||||
schemars = ["dep:schemars"]
|
||||
jupyter_notebook = []
|
||||
|
||||
@@ -23,6 +23,10 @@ class Foobar(unittest.TestCase):
|
||||
with self.assertRaises(Exception):
|
||||
raise Exception("Evil I say!")
|
||||
|
||||
def also_evil_raises(self) -> None:
|
||||
with self.assertRaises(BaseException):
|
||||
raise Exception("Evil I say!")
|
||||
|
||||
def context_manager_raises(self) -> None:
|
||||
with self.assertRaises(Exception) as ex:
|
||||
raise Exception("Context manager is good")
|
||||
@@ -41,6 +45,9 @@ def test_pytest_raises():
|
||||
with pytest.raises(Exception):
|
||||
raise ValueError("Hello")
|
||||
|
||||
with pytest.raises(Exception), pytest.raises(ValueError):
|
||||
raise ValueError("Hello")
|
||||
|
||||
with pytest.raises(Exception, "hello"):
|
||||
raise ValueError("This is fine")
|
||||
|
||||
|
||||
@@ -111,3 +111,19 @@ class PerfectlyFine(models.Model):
|
||||
@property
|
||||
def random_property(self):
|
||||
return "%s" % self
|
||||
|
||||
|
||||
class MultipleConsecutiveFields(models.Model):
|
||||
"""Model that contains multiple out-of-order field definitions in a row."""
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "test"
|
||||
|
||||
first_name = models.CharField(max_length=32)
|
||||
last_name = models.CharField(max_length=32)
|
||||
|
||||
def get_absolute_url(self):
|
||||
pass
|
||||
|
||||
middle_name = models.CharField(max_length=32)
|
||||
|
||||
6
crates/ruff/resources/test/fixtures/flake8_pyi/PYI002.py
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_pyi/PYI002.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys
|
||||
|
||||
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
6
crates/ruff/resources/test/fixtures/flake8_pyi/PYI002.pyi
vendored
Normal file
6
crates/ruff/resources/test/fixtures/flake8_pyi/PYI002.pyi
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys
|
||||
|
||||
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
31
crates/ruff/resources/test/fixtures/flake8_pyi/PYI003.py
vendored
Normal file
31
crates/ruff/resources/test/fixtures/flake8_pyi/PYI003.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
|
||||
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
|
||||
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
|
||||
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info < (3, 5): ...
|
||||
if sys.version_info >= (3, 5): ...
|
||||
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
31
crates/ruff/resources/test/fixtures/flake8_pyi/PYI003.pyi
vendored
Normal file
31
crates/ruff/resources/test/fixtures/flake8_pyi/PYI003.pyi
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
|
||||
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
|
||||
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
|
||||
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
|
||||
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||
if sys.version_info < (3, 5): ...
|
||||
if sys.version_info >= (3, 5): ...
|
||||
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI004.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI004.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
from sys import version_info
|
||||
|
||||
if sys.version_info >= (3, 4, 3): ... # PYI004
|
||||
if sys.version_info < (3, 4, 3): ... # PYI004
|
||||
if sys.version_info == (3, 4, 3): ... # PYI004
|
||||
if sys.version_info != (3, 4, 3): ... # PYI004
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if version_info[0] == 2: ...
|
||||
if sys.version_info < (3, 5): ...
|
||||
if version_info >= (3, 5): ...
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if sys.platform == 'linux': ...
|
||||
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI004.pyi
vendored
Normal file
15
crates/ruff/resources/test/fixtures/flake8_pyi/PYI004.pyi
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
from sys import version_info
|
||||
|
||||
if sys.version_info >= (3, 4, 3): ... # PYI004
|
||||
if sys.version_info < (3, 4, 3): ... # PYI004
|
||||
if sys.version_info == (3, 4, 3): ... # PYI004
|
||||
if sys.version_info != (3, 4, 3): ... # PYI004
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if version_info[0] == 2: ...
|
||||
if sys.version_info < (3, 5): ...
|
||||
if version_info >= (3, 5): ...
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if sys.platform == 'linux': ...
|
||||
14
crates/ruff/resources/test/fixtures/flake8_pyi/PYI005.py
vendored
Normal file
14
crates/ruff/resources/test/fixtures/flake8_pyi/PYI005.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
from sys import platform, version_info
|
||||
|
||||
if sys.version_info[:1] == (2, 7): ... # Y005
|
||||
if sys.version_info[:2] == (2,): ... # Y005
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if version_info[0] == 2: ...
|
||||
if sys.version_info < (3, 5): ...
|
||||
if version_info >= (3, 5): ...
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if platform == 'linux': ...
|
||||
14
crates/ruff/resources/test/fixtures/flake8_pyi/PYI005.pyi
vendored
Normal file
14
crates/ruff/resources/test/fixtures/flake8_pyi/PYI005.pyi
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
from sys import platform, version_info
|
||||
|
||||
if sys.version_info[:1] == (2, 7): ... # Y005
|
||||
if sys.version_info[:2] == (2,): ... # Y005
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: ...
|
||||
if version_info[0] == 2: ...
|
||||
if sys.version_info < (3, 5): ...
|
||||
if version_info >= (3, 5): ...
|
||||
if sys.version_info[:2] == (2, 7): ...
|
||||
if sys.version_info[:1] == (2,): ...
|
||||
if platform == 'linux': ...
|
||||
@@ -91,3 +91,4 @@ field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
field31: typing.Final = field30
|
||||
|
||||
@@ -98,3 +98,4 @@ field27 = list[str]
|
||||
field28 = builtins.str
|
||||
field29 = str
|
||||
field30 = str | bytes | None
|
||||
field31: typing.Final = field30
|
||||
|
||||
@@ -36,3 +36,11 @@ bar: str = "51 character stringgggggggggggggggggggggggggggggggg"
|
||||
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg"
|
||||
|
||||
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff"
|
||||
|
||||
|
||||
class Demo:
|
||||
"""Docstrings are excluded from this rule. Some padding."""
|
||||
|
||||
|
||||
def func() -> None:
|
||||
"""Docstrings are excluded from this rule. Some padding."""
|
||||
|
||||
@@ -28,3 +28,9 @@ bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI05
|
||||
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
|
||||
|
||||
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
|
||||
|
||||
class Demo:
|
||||
"""Docstrings are excluded from this rule. Some padding.""" # OK
|
||||
|
||||
def func() -> None:
|
||||
"""Docstrings are excluded from this rule. Some padding.""" # OK
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# T002 - accepted
|
||||
# TODO (evanrittenhouse): this has an author
|
||||
# TODO(evanrittenhouse): this also has an author
|
||||
# TODO(evanrittenhouse): this has an author
|
||||
# TODO (evanrittenhouse) and more: this has an author
|
||||
# TODO(evanrittenhouse) and more: this has an author
|
||||
# TODO@mayrholu: this has an author
|
||||
# TODO @mayrholu: this has an author
|
||||
# TODO@mayrholu and more: this has an author
|
||||
# TODO @mayrholu and more: this has an author
|
||||
# T002 - errors
|
||||
# TODO: this has no author
|
||||
# FIXME: neither does this
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Do this (new version)
|
||||
from numpy.random import default_rng
|
||||
|
||||
rng = default_rng()
|
||||
vals = rng.standard_normal(10)
|
||||
more_vals = rng.standard_normal(10)
|
||||
@@ -7,11 +8,13 @@ numbers = rng.integers(high, size=5)
|
||||
|
||||
# instead of this (legacy version)
|
||||
from numpy import random
|
||||
|
||||
vals = random.standard_normal(10)
|
||||
more_vals = random.standard_normal(10)
|
||||
numbers = random.integers(high, size=5)
|
||||
|
||||
import numpy
|
||||
|
||||
numpy.random.seed()
|
||||
numpy.random.get_state()
|
||||
numpy.random.set_state()
|
||||
|
||||
15
crates/ruff/resources/test/fixtures/numpy/NPY003.py
vendored
Normal file
15
crates/ruff/resources/test/fixtures/numpy/NPY003.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import numpy as np
|
||||
|
||||
np.round_(np.random.rand(5, 5), 2)
|
||||
np.product(np.random.rand(5, 5))
|
||||
np.cumproduct(np.random.rand(5, 5))
|
||||
np.sometrue(np.random.rand(5, 5))
|
||||
np.alltrue(np.random.rand(5, 5))
|
||||
|
||||
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||
|
||||
round_(np.random.rand(5, 5), 2)
|
||||
product(np.random.rand(5, 5))
|
||||
cumproduct(np.random.rand(5, 5))
|
||||
sometrue(np.random.rand(5, 5))
|
||||
alltrue(np.random.rand(5, 5))
|
||||
@@ -4,7 +4,9 @@ x = pd.DataFrame()
|
||||
|
||||
x.drop(["a"], axis=1, inplace=True)
|
||||
|
||||
x.drop(["a"], axis=1, inplace=True)
|
||||
x.y.drop(["a"], axis=1, inplace=True)
|
||||
|
||||
x["y"].drop(["a"], axis=1, inplace=True)
|
||||
|
||||
x.drop(
|
||||
inplace=True,
|
||||
@@ -23,6 +25,7 @@ x.drop(["a"], axis=1, **kwargs, inplace=True)
|
||||
x.drop(["a"], axis=1, inplace=True, **kwargs)
|
||||
f(x.drop(["a"], axis=1, inplace=True))
|
||||
|
||||
x.apply(lambda x: x.sort_values('a', inplace=True))
|
||||
x.apply(lambda x: x.sort_values("a", inplace=True))
|
||||
import torch
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import ABCMeta
|
||||
import abc
|
||||
|
||||
import pydantic
|
||||
|
||||
@@ -19,6 +19,10 @@ class Class:
|
||||
def class_method(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def abstract_class_method(cls):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def static_method(x):
|
||||
return x
|
||||
@@ -41,7 +45,7 @@ class Class:
|
||||
...
|
||||
|
||||
|
||||
class MetaClass(ABCMeta):
|
||||
class MetaClass(abc.ABCMeta):
|
||||
def bad_method(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import ABCMeta
|
||||
import abc
|
||||
|
||||
import pydantic
|
||||
|
||||
@@ -34,6 +34,23 @@ class Class:
|
||||
def stillBad(cls, my_field: str) -> str:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def badAllowed(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def stillBad(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def badAllowed(cls):
|
||||
pass
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def stillBad(cls):
|
||||
pass
|
||||
|
||||
|
||||
class PosOnlyClass:
|
||||
def badAllowed(this, blah, /, self, something: str):
|
||||
pass
|
||||
|
||||
52
crates/ruff/resources/test/fixtures/perflint/PERF101.py
vendored
Normal file
52
crates/ruff/resources/test/fixtures/perflint/PERF101.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
foo_tuple = (1, 2, 3)
|
||||
foo_list = [1, 2, 3]
|
||||
foo_set = {1, 2, 3}
|
||||
foo_dict = {1: 2, 3: 4}
|
||||
foo_int = 123
|
||||
|
||||
for i in list(foo_tuple): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_list): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_set): # PERF101
|
||||
pass
|
||||
|
||||
for i in list((1, 2, 3)): # PERF101
|
||||
pass
|
||||
|
||||
for i in list([1, 2, 3]): # PERF101
|
||||
pass
|
||||
|
||||
for i in list({1, 2, 3}): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
}
|
||||
):
|
||||
pass
|
||||
|
||||
for i in list( # Comment
|
||||
{1, 2, 3}
|
||||
): # PERF101
|
||||
pass
|
||||
|
||||
for i in list(foo_dict): # Ok
|
||||
pass
|
||||
|
||||
for i in list(1): # Ok
|
||||
pass
|
||||
|
||||
for i in list(foo_int): # Ok
|
||||
pass
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
for i in itertools.product(foo_int): # Ok
|
||||
pass
|
||||
28
crates/ruff/resources/test/fixtures/perflint/PERF203.py
vendored
Normal file
28
crates/ruff/resources/test/fixtures/perflint/PERF203.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
for i in range(10):
|
||||
try: # PERF203
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
try:
|
||||
for i in range(10):
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
i = 0
|
||||
while i < 10: # PERF203
|
||||
try:
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
i += 1
|
||||
|
||||
try:
|
||||
i = 0
|
||||
while i < 10:
|
||||
print(f"{i}")
|
||||
i += 1
|
||||
except:
|
||||
print("error")
|
||||
32
crates/ruff/resources/test/fixtures/perflint/PERF401.py
vendored
Normal file
32
crates/ruff/resources/test/fixtures/perflint/PERF401.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
if i % 2:
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
result.append(i * i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
if i % 2:
|
||||
result.append(i) # PERF401
|
||||
elif i % 2:
|
||||
result.append(i) # PERF401
|
||||
else:
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
result.append(i) # OK
|
||||
19
crates/ruff/resources/test/fixtures/perflint/PERF402.py
vendored
Normal file
19
crates/ruff/resources/test/fixtures/perflint/PERF402.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
result.append(i) # PERF402
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
result.insert(0, i) # PERF402
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
for i in items:
|
||||
result.append(i * i) # OK
|
||||
@@ -1,51 +1,135 @@
|
||||
#: E731
|
||||
f = lambda x: 2 * x
|
||||
#: E731
|
||||
f = lambda x: 2 * x
|
||||
#: E731
|
||||
while False:
|
||||
this = lambda y, z: 2 * x
|
||||
#: E731
|
||||
f = lambda: (yield 1)
|
||||
#: E731
|
||||
f = lambda: (yield from g())
|
||||
#: E731
|
||||
class F:
|
||||
def scope():
|
||||
# E731
|
||||
f = lambda x: 2 * x
|
||||
|
||||
|
||||
f = object()
|
||||
f.method = lambda: "Method"
|
||||
f = {}
|
||||
f["a"] = lambda x: x**2
|
||||
f = []
|
||||
f.append(lambda x: x**2)
|
||||
f = g = lambda x: x**2
|
||||
lambda: "no-op"
|
||||
def scope():
|
||||
# E731
|
||||
f = lambda x: 2 * x
|
||||
|
||||
# Annotated
|
||||
from typing import Callable, ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
def scope():
|
||||
# E731
|
||||
while False:
|
||||
this = lambda y, z: 2 * x
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
f = lambda: (yield 1)
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
f = lambda: (yield from g())
|
||||
|
||||
|
||||
def scope():
|
||||
# OK
|
||||
f = object()
|
||||
f.method = lambda: "Method"
|
||||
|
||||
|
||||
def scope():
|
||||
# OK
|
||||
f = {}
|
||||
f["a"] = lambda x: x**2
|
||||
|
||||
|
||||
def scope():
|
||||
# OK
|
||||
f = []
|
||||
f.append(lambda x: x**2)
|
||||
|
||||
|
||||
def scope():
|
||||
# OK
|
||||
f = g = lambda x: x**2
|
||||
|
||||
|
||||
def scope():
|
||||
# OK
|
||||
lambda: "no-op"
|
||||
|
||||
|
||||
class Scope:
|
||||
# E731
|
||||
f = lambda x: 2 * x
|
||||
|
||||
|
||||
class Scope:
|
||||
from typing import Callable
|
||||
|
||||
# E731
|
||||
f: Callable[[int], int] = lambda x: 2 * x
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
from typing import Callable
|
||||
|
||||
x: Callable[[int], int]
|
||||
if True:
|
||||
x = lambda: 1
|
||||
else:
|
||||
x = lambda: 2
|
||||
return x
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from typing import Callable, ParamSpec
|
||||
|
||||
# ParamSpec cannot be used in this context, so do not preserve the annotation.
|
||||
P = ParamSpec("P")
|
||||
f: Callable[P, int] = lambda *args: len(args)
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from typing import Callable
|
||||
|
||||
f: Callable[[], None] = lambda: None
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from typing import Callable
|
||||
|
||||
f: Callable[..., None] = lambda a, b: None
|
||||
|
||||
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from typing import Callable
|
||||
|
||||
f: Callable[[int], int] = lambda x: 2 * x
|
||||
|
||||
# ParamSpec cannot be used in this context, so do not preserve the annotation.
|
||||
f: Callable[P, int] = lambda *args: len(args)
|
||||
f: Callable[[], None] = lambda: None
|
||||
f: Callable[..., None] = lambda a, b: None
|
||||
f: Callable[[int], int] = lambda x: 2 * x
|
||||
|
||||
# Let's use the `Callable` type from `collections.abc` instead.
|
||||
from collections.abc import Callable
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
f: Callable[[str, int], str] = lambda a, b: a * b
|
||||
f: Callable[[str, int], tuple[str, int]] = lambda a, b: (a, b)
|
||||
f: Callable[[str, int, list[str]], list[str]] = lambda a, b, /, c: [*c, a * b]
|
||||
from collections.abc import Callable
|
||||
|
||||
f: Callable[[str, int], str] = lambda a, b: a * b
|
||||
|
||||
|
||||
# Override `Callable`
|
||||
class Callable:
|
||||
pass
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
f: Callable[[str, int], tuple[str, int]] = lambda a, b: (a, b)
|
||||
|
||||
|
||||
# Do not copy the annotation from here on out.
|
||||
f: Callable[[str, int], str] = lambda a, b: a * b
|
||||
def scope():
|
||||
# E731
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
f: Callable[[str, int, list[str]], list[str]] = lambda a, b, /, c: [*c, a * b]
|
||||
|
||||
@@ -19,6 +19,14 @@ with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
#: W605:1:38
|
||||
value = 'new line\nand invalid escape \_ here'
|
||||
|
||||
|
||||
def f():
|
||||
#: W605:1:11
|
||||
return'\.png$'
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
|
||||
@@ -19,6 +19,11 @@ with \_ somewhere
|
||||
in the middle
|
||||
"""
|
||||
|
||||
|
||||
def f():
|
||||
#: W605:1:11
|
||||
return'\.png$'
|
||||
|
||||
#: Okay
|
||||
regex = r'\.png$'
|
||||
regex = '\\.png$'
|
||||
|
||||
29
crates/ruff/resources/test/fixtures/pydocstyle/D301.py
vendored
Normal file
29
crates/ruff/resources/test/fixtures/pydocstyle/D301.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
def double_quotes_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
def double_quotes_backslash_raw():
|
||||
r"""Sum\mary."""
|
||||
|
||||
|
||||
def double_quotes_backslash_uppercase():
|
||||
R"""Sum\\mary."""
|
||||
|
||||
|
||||
def make_unique_pod_id(pod_id: str) -> str | None:
|
||||
r"""
|
||||
Generate a unique Pod name.
|
||||
|
||||
Kubernetes pod names must consist of one or more lowercase
|
||||
rfc1035/rfc1123 labels separated by '.' with a maximum length of 253
|
||||
characters.
|
||||
|
||||
Name must pass the following regex for validation
|
||||
``^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$``
|
||||
|
||||
For more details, see:
|
||||
https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/design/identifiers.md
|
||||
|
||||
:param pod_id: requested pod name
|
||||
:return: ``str`` valid Pod name of appropriate length
|
||||
"""
|
||||
25
crates/ruff/resources/test/fixtures/pydocstyle/D410.py
vendored
Normal file
25
crates/ruff/resources/test/fixtures/pydocstyle/D410.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
def f(a: int, b: int) -> int:
|
||||
"""Showcase function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : int
|
||||
_description_
|
||||
b : int
|
||||
_description_
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
_description
|
||||
"""
|
||||
return b - a
|
||||
|
||||
|
||||
def f() -> int:
|
||||
"""Showcase function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
Returns
|
||||
-------
|
||||
"""
|
||||
@@ -513,3 +513,19 @@ def implicit_string_concatenation():
|
||||
A value of some sort.
|
||||
|
||||
""""Extra content"
|
||||
|
||||
|
||||
def replace_equals_with_dash():
|
||||
"""Equal length equals should be replaced with dashes.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
"""
|
||||
|
||||
|
||||
def replace_equals_with_dash2():
|
||||
"""Here, the length of equals is not the same.
|
||||
|
||||
Parameters
|
||||
===========
|
||||
"""
|
||||
|
||||
@@ -37,7 +37,10 @@ f"{{test}}"
|
||||
f'{{ 40 }}'
|
||||
f"{{a {{x}}"
|
||||
f"{{{{x}}}}"
|
||||
""f""
|
||||
''f""
|
||||
(""f""r"")
|
||||
|
||||
# To be fixed
|
||||
# Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
# f"\{{x}}"
|
||||
# f"\{{x}}"
|
||||
|
||||
@@ -36,3 +36,6 @@ for item in set(("apples", "lemons", "water")): # set constructor is fine
|
||||
|
||||
for number in {i for i in range(10)}: # set comprehensions are fine
|
||||
print(number)
|
||||
|
||||
for item in {*numbers_set, 4, 5, 6}: # set unpacking is fine
|
||||
print(f"I like {item}.")
|
||||
|
||||
35
crates/ruff/resources/test/fixtures/pylint/single_string_slots.py
vendored
Normal file
35
crates/ruff/resources/test/fixtures/pylint/single_string_slots.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Errors.
|
||||
class Foo:
|
||||
__slots__ = "bar"
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
|
||||
class Foo:
|
||||
__slots__: str = "bar"
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
|
||||
class Foo:
|
||||
__slots__: str = f"bar"
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
|
||||
# Non-errors.
|
||||
class Foo:
|
||||
__slots__ = ("bar",)
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
|
||||
|
||||
class Foo:
|
||||
__slots__: tuple[str, ...] = ("bar",)
|
||||
|
||||
def __init__(self, bar):
|
||||
self.bar = bar
|
||||
@@ -70,3 +70,8 @@ print("foo".encode()) # print(b"foo")
|
||||
"abc"
|
||||
"def"
|
||||
)).encode()
|
||||
|
||||
(f"foo{bar}").encode("utf-8")
|
||||
(f"foo{bar}").encode(encoding="utf-8")
|
||||
("unicode text©").encode("utf-8")
|
||||
("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
@@ -48,3 +48,12 @@ if True: from collections import (
|
||||
|
||||
# OK
|
||||
from a import b
|
||||
|
||||
# Ok: `typing_extensions` contains backported improvements.
|
||||
from typing_extensions import SupportsIndex
|
||||
|
||||
# Ok: `typing_extensions` contains backported improvements.
|
||||
from typing_extensions import NamedTuple
|
||||
|
||||
# Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import typing
|
||||
from typing import ClassVar, Sequence, Final
|
||||
|
||||
KNOWINGLY_MUTABLE_DEFAULT = []
|
||||
|
||||
|
||||
class A:
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: typing.Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: typing.ClassVar[list[int]] = []
|
||||
final_variable: typing.Final[list[int]] = []
|
||||
__slots__ = {
|
||||
"mutable_default": "A mutable default value",
|
||||
}
|
||||
|
||||
|
||||
class B:
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
@@ -30,7 +21,6 @@ class C:
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
@@ -43,7 +33,5 @@ class D(BaseModel):
|
||||
mutable_default: list[int] = []
|
||||
immutable_annotation: Sequence[int] = []
|
||||
without_annotation = []
|
||||
correct_code: list[int] = KNOWINGLY_MUTABLE_DEFAULT
|
||||
perfectly_fine: list[int] = field(default_factory=list)
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
|
||||
@@ -221,3 +221,23 @@ def f(arg: Union["No" "ne", "int"] = None):
|
||||
# Avoid flagging when there's a parse error in the forward reference
|
||||
def f(arg: Union["<>", "int"] = None):
|
||||
pass
|
||||
|
||||
|
||||
# Type aliases
|
||||
|
||||
Text = str | bytes
|
||||
|
||||
|
||||
def f(arg: Text = None):
|
||||
pass
|
||||
|
||||
|
||||
def f(arg: "Text" = None):
|
||||
pass
|
||||
|
||||
|
||||
from custom_typing import MaybeInt
|
||||
|
||||
|
||||
def f(arg: MaybeInt = None):
|
||||
pass
|
||||
|
||||
@@ -36,6 +36,7 @@ use crate::importer::Importer;
|
||||
use crate::noqa::NoqaMapping;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_builtins::helpers::AnyShadowing;
|
||||
|
||||
use crate::rules::{
|
||||
airflow, flake8_2020, flake8_annotations, flake8_async, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
|
||||
@@ -71,7 +72,7 @@ pub(crate) struct Checker<'a> {
|
||||
deferred: Deferred<'a>,
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
// Check-specific state.
|
||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
||||
pub(crate) flake8_bugbear_seen: Vec<&'a ast::ExprName>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -358,11 +359,7 @@ where
|
||||
..
|
||||
}) => {
|
||||
if self.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
self.diagnostics
|
||||
.extend(flake8_django::rules::non_leading_receiver_decorator(
|
||||
decorator_list,
|
||||
|expr| self.semantic.resolve_call_path(expr),
|
||||
));
|
||||
flake8_django::rules::non_leading_receiver_decorator(self, decorator_list);
|
||||
}
|
||||
if self.enabled(Rule::AmbiguousFunctionName) {
|
||||
if let Some(diagnostic) =
|
||||
@@ -504,8 +501,7 @@ where
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::HardcodedPasswordDefault) {
|
||||
self.diagnostics
|
||||
.extend(flake8_bandit::rules::hardcoded_password_default(args));
|
||||
flake8_bandit::rules::hardcoded_password_default(self, args);
|
||||
}
|
||||
if self.enabled(Rule::PropertyWithParameters) {
|
||||
pylint::rules::property_with_parameters(self, stmt, decorator_list, args);
|
||||
@@ -643,10 +639,7 @@ where
|
||||
},
|
||||
) => {
|
||||
if self.enabled(Rule::DjangoNullableModelStringField) {
|
||||
self.diagnostics
|
||||
.extend(flake8_django::rules::nullable_model_string_field(
|
||||
self, body,
|
||||
));
|
||||
flake8_django::rules::nullable_model_string_field(self, body);
|
||||
}
|
||||
if self.enabled(Rule::DjangoExcludeWithModelForm) {
|
||||
if let Some(diagnostic) =
|
||||
@@ -667,21 +660,17 @@ where
|
||||
}
|
||||
if !self.is_stub {
|
||||
if self.enabled(Rule::DjangoModelWithoutDunderStr) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_django::rules::model_without_dunder_str(self, bases, body, stmt)
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
flake8_django::rules::model_without_dunder_str(self, class_def);
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(self, name);
|
||||
}
|
||||
if self.enabled(Rule::UselessObjectInheritance) {
|
||||
pyupgrade::rules::useless_object_inheritance(self, class_def, stmt);
|
||||
pyupgrade::rules::useless_object_inheritance(self, class_def);
|
||||
}
|
||||
if self.enabled(Rule::UnnecessaryClassParentheses) {
|
||||
pyupgrade::rules::unnecessary_class_parentheses(self, class_def, stmt);
|
||||
pyupgrade::rules::unnecessary_class_parentheses(self, class_def);
|
||||
}
|
||||
if self.enabled(Rule::AmbiguousClassName) {
|
||||
if let Some(diagnostic) =
|
||||
@@ -770,6 +759,9 @@ where
|
||||
if self.enabled(Rule::NoSlotsInNamedtupleSubclass) {
|
||||
flake8_slots::rules::no_slots_in_namedtuple_subclass(self, stmt, class_def);
|
||||
}
|
||||
if self.enabled(Rule::SingleStringSlots) {
|
||||
pylint::rules::single_string_slots(self, class_def);
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
if self.enabled(Rule::MultipleImportsOnOneLine) {
|
||||
@@ -1367,6 +1359,51 @@ where
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if self.is_stub {
|
||||
if self.any_enabled(&[
|
||||
Rule::UnrecognizedVersionInfoCheck,
|
||||
Rule::PatchVersionComparison,
|
||||
Rule::WrongTupleLengthVersionComparison,
|
||||
]) {
|
||||
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||
for value in values {
|
||||
flake8_pyi::rules::unrecognized_version_info(self, value);
|
||||
}
|
||||
} else {
|
||||
flake8_pyi::rules::unrecognized_version_info(self, test);
|
||||
}
|
||||
}
|
||||
if self.any_enabled(&[
|
||||
Rule::UnrecognizedPlatformCheck,
|
||||
Rule::UnrecognizedPlatformName,
|
||||
]) {
|
||||
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||
for value in values {
|
||||
flake8_pyi::rules::unrecognized_platform(self, value);
|
||||
}
|
||||
} else {
|
||||
flake8_pyi::rules::unrecognized_platform(self, test);
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::BadVersionInfoComparison) {
|
||||
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||
for value in values {
|
||||
flake8_pyi::rules::bad_version_info_comparison(self, value);
|
||||
}
|
||||
} else {
|
||||
flake8_pyi::rules::bad_version_info_comparison(self, test);
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::ComplexIfStatementInStub) {
|
||||
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||
for value in values {
|
||||
flake8_pyi::rules::complex_if_statement_in_stub(self, value);
|
||||
}
|
||||
} else {
|
||||
flake8_pyi::rules::complex_if_statement_in_stub(self, test);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::Assert(ast::StmtAssert {
|
||||
test,
|
||||
@@ -1406,7 +1443,7 @@ where
|
||||
Stmt::With(ast::StmtWith { items, body, .. })
|
||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
||||
if self.enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception(self, stmt, items);
|
||||
flake8_bugbear::rules::assert_raises_exception(self, items);
|
||||
}
|
||||
if self.enabled(Rule::PytestRaisesWithMultipleStatements) {
|
||||
flake8_pytest_style::rules::complex_raises(self, stmt, items, body);
|
||||
@@ -1430,6 +1467,9 @@ where
|
||||
if self.enabled(Rule::UselessElseOnLoop) {
|
||||
pylint::rules::useless_else_on_loop(self, stmt, body, orelse);
|
||||
}
|
||||
if self.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(self, body);
|
||||
}
|
||||
}
|
||||
Stmt::For(ast::StmtFor {
|
||||
target,
|
||||
@@ -1477,10 +1517,22 @@ where
|
||||
if self.enabled(Rule::InDictKeys) {
|
||||
flake8_simplify::rules::key_in_dict_for(self, target, iter);
|
||||
}
|
||||
if self.enabled(Rule::TryExceptInLoop) {
|
||||
perflint::rules::try_except_in_loop(self, body);
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::IncorrectDictIterator) {
|
||||
perflint::rules::incorrect_dict_iterator(self, target, iter);
|
||||
}
|
||||
if self.enabled(Rule::ManualListComprehension) {
|
||||
perflint::rules::manual_list_comprehension(self, target, body);
|
||||
}
|
||||
if self.enabled(Rule::ManualListCopy) {
|
||||
perflint::rules::manual_list_copy(self, target, body);
|
||||
}
|
||||
if self.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(self, iter);
|
||||
}
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
@@ -1516,9 +1568,7 @@ where
|
||||
pyupgrade::rules::os_error_alias_handlers(self, handlers);
|
||||
}
|
||||
if self.enabled(Rule::PytestAssertInExcept) {
|
||||
self.diagnostics.extend(
|
||||
flake8_pytest_style::rules::assert_in_exception_handler(handlers),
|
||||
);
|
||||
flake8_pytest_style::rules::assert_in_exception_handler(self, handlers);
|
||||
}
|
||||
if self.enabled(Rule::SuppressibleException) {
|
||||
flake8_simplify::rules::suppressible_exception(
|
||||
@@ -1559,11 +1609,7 @@ where
|
||||
flake8_bugbear::rules::assignment_to_os_environ(self, targets);
|
||||
}
|
||||
if self.enabled(Rule::HardcodedPasswordString) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_bandit::rules::assign_hardcoded_password_string(value, targets)
|
||||
{
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
flake8_bandit::rules::assign_hardcoded_password_string(self, value, targets);
|
||||
}
|
||||
if self.enabled(Rule::GlobalStatement) {
|
||||
for target in targets.iter() {
|
||||
@@ -2095,6 +2141,7 @@ where
|
||||
&& self.settings.target_version >= PythonVersion::Py37
|
||||
&& !self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||
self, value,
|
||||
@@ -2105,7 +2152,8 @@ where
|
||||
if self.settings.target_version >= PythonVersion::Py310
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation())
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
pyupgrade::rules::use_pep604_annotation(
|
||||
self, expr, slice, operator,
|
||||
@@ -2181,6 +2229,9 @@ where
|
||||
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
||||
numpy::rules::deprecated_type_alias(self, expr);
|
||||
}
|
||||
if self.enabled(Rule::NumpyDeprecatedFunction) {
|
||||
numpy::rules::deprecated_function(self, expr);
|
||||
}
|
||||
if self.is_stub {
|
||||
if self.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(self, expr);
|
||||
@@ -2200,6 +2251,7 @@ where
|
||||
&& self.settings.target_version >= PythonVersion::Py37
|
||||
&& !self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||
self, expr,
|
||||
@@ -2210,7 +2262,8 @@ where
|
||||
if self.settings.target_version >= PythonVersion::Py39
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation())
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
pyupgrade::rules::use_pep585_annotation(
|
||||
self,
|
||||
@@ -2275,6 +2328,7 @@ where
|
||||
&& self.settings.target_version >= PythonVersion::Py37
|
||||
&& !self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||
self, expr,
|
||||
@@ -2285,7 +2339,8 @@ where
|
||||
if self.settings.target_version >= PythonVersion::Py39
|
||||
|| (self.settings.target_version >= PythonVersion::Py37
|
||||
&& self.semantic.future_annotations()
|
||||
&& self.semantic.in_annotation())
|
||||
&& self.semantic.in_annotation()
|
||||
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||
{
|
||||
pyupgrade::rules::use_pep585_annotation(self, expr, &replacement);
|
||||
}
|
||||
@@ -2303,6 +2358,9 @@ where
|
||||
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
||||
numpy::rules::deprecated_type_alias(self, expr);
|
||||
}
|
||||
if self.enabled(Rule::NumpyDeprecatedFunction) {
|
||||
numpy::rules::deprecated_function(self, expr);
|
||||
}
|
||||
if self.enabled(Rule::DeprecatedMockImport) {
|
||||
pyupgrade::rules::deprecated_mock_attribute(self, expr);
|
||||
}
|
||||
@@ -2546,8 +2604,7 @@ where
|
||||
flake8_bandit::rules::jinja2_autoescape_false(self, func, args, keywords);
|
||||
}
|
||||
if self.enabled(Rule::HardcodedPasswordFuncArg) {
|
||||
self.diagnostics
|
||||
.extend(flake8_bandit::rules::hardcoded_password_func_arg(keywords));
|
||||
flake8_bandit::rules::hardcoded_password_func_arg(self, keywords);
|
||||
}
|
||||
if self.enabled(Rule::HardcodedSQLExpression) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||
@@ -2680,17 +2737,12 @@ where
|
||||
flake8_debugger::rules::debugger_call(self, expr, func);
|
||||
}
|
||||
if self.enabled(Rule::PandasUseOfInplaceArgument) {
|
||||
self.diagnostics.extend(
|
||||
pandas_vet::rules::inplace_argument(self, expr, func, args, keywords)
|
||||
.into_iter(),
|
||||
);
|
||||
pandas_vet::rules::inplace_argument(self, expr, func, args, keywords);
|
||||
}
|
||||
pandas_vet::rules::call(self, func);
|
||||
|
||||
if self.enabled(Rule::PandasUseOfPdMerge) {
|
||||
if let Some(diagnostic) = pandas_vet::rules::use_of_pd_merge(func) {
|
||||
self.diagnostics.push(diagnostic);
|
||||
};
|
||||
pandas_vet::rules::use_of_pd_merge(self, func);
|
||||
}
|
||||
if self.enabled(Rule::CallDatetimeWithoutTzinfo) {
|
||||
flake8_datetimez::rules::call_datetime_without_tzinfo(
|
||||
@@ -2807,16 +2859,13 @@ where
|
||||
&self.settings.flake8_gettext.functions_names,
|
||||
) {
|
||||
if self.enabled(Rule::FStringInGetTextFuncCall) {
|
||||
self.diagnostics
|
||||
.extend(flake8_gettext::rules::f_string_in_gettext_func_call(args));
|
||||
flake8_gettext::rules::f_string_in_gettext_func_call(self, args);
|
||||
}
|
||||
if self.enabled(Rule::FormatInGetTextFuncCall) {
|
||||
self.diagnostics
|
||||
.extend(flake8_gettext::rules::format_in_gettext_func_call(args));
|
||||
flake8_gettext::rules::format_in_gettext_func_call(self, args);
|
||||
}
|
||||
if self.enabled(Rule::PrintfInGetTextFuncCall) {
|
||||
self.diagnostics
|
||||
.extend(flake8_gettext::rules::printf_in_gettext_func_call(args));
|
||||
flake8_gettext::rules::printf_in_gettext_func_call(self, args);
|
||||
}
|
||||
}
|
||||
if self.enabled(Rule::UncapitalizedEnvironmentVariables) {
|
||||
@@ -2857,7 +2906,7 @@ where
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(self, func);
|
||||
}
|
||||
if self.enabled(Rule::NumpyLegacyRandom) {
|
||||
numpy::rules::numpy_legacy_random(self, func);
|
||||
numpy::rules::legacy_random(self, func);
|
||||
}
|
||||
if self.any_enabled(&[
|
||||
Rule::LoggingStringFormat,
|
||||
@@ -3157,11 +3206,10 @@ where
|
||||
flake8_2020::rules::compare(self, left, ops, comparators);
|
||||
}
|
||||
if self.enabled(Rule::HardcodedPasswordString) {
|
||||
self.diagnostics.extend(
|
||||
flake8_bandit::rules::compare_to_hardcoded_password_string(
|
||||
left,
|
||||
comparators,
|
||||
),
|
||||
flake8_bandit::rules::compare_to_hardcoded_password_string(
|
||||
self,
|
||||
left,
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
if self.enabled(Rule::ComparisonWithItself) {
|
||||
@@ -3182,29 +3230,6 @@ where
|
||||
if self.enabled(Rule::YodaConditions) {
|
||||
flake8_simplify::rules::yoda_conditions(self, expr, left, ops, comparators);
|
||||
}
|
||||
if self.is_stub {
|
||||
if self.any_enabled(&[
|
||||
Rule::UnrecognizedPlatformCheck,
|
||||
Rule::UnrecognizedPlatformName,
|
||||
]) {
|
||||
flake8_pyi::rules::unrecognized_platform(
|
||||
self,
|
||||
expr,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
if self.enabled(Rule::BadVersionInfoComparison) {
|
||||
flake8_pyi::rules::bad_version_info_comparison(
|
||||
self,
|
||||
expr,
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||
@@ -4141,88 +4166,6 @@ impl<'a> Checker<'a> {
|
||||
|
||||
// Create the `Binding`.
|
||||
let binding_id = self.semantic.push_binding(range, kind, flags);
|
||||
let binding = self.semantic.binding(binding_id);
|
||||
|
||||
// Determine whether the binding shadows any existing bindings.
|
||||
if let Some((stack_index, shadowed_id)) = self
|
||||
.semantic
|
||||
.scopes
|
||||
.ancestors(self.semantic.scope_id)
|
||||
.enumerate()
|
||||
.find_map(|(stack_index, scope)| {
|
||||
scope.get(name).and_then(|binding_id| {
|
||||
let binding = self.semantic.binding(binding_id);
|
||||
if binding.is_unbound() {
|
||||
None
|
||||
} else {
|
||||
Some((stack_index, binding_id))
|
||||
}
|
||||
})
|
||||
})
|
||||
{
|
||||
let shadowed = self.semantic.binding(shadowed_id);
|
||||
let in_current_scope = stack_index == 0;
|
||||
if !shadowed.kind.is_builtin()
|
||||
&& shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
!branch_detection::different_forks(left, right, &self.semantic.stmts)
|
||||
})
|
||||
})
|
||||
{
|
||||
let shadows_import = matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
);
|
||||
if binding.kind.is_loop_var() && shadows_import {
|
||||
if self.enabled(Rule::ImportShadowedByLoopVar) {
|
||||
#[allow(deprecated)]
|
||||
let line = self.locator.compute_line_index(shadowed.range.start());
|
||||
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportShadowedByLoopVar {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
} else if in_current_scope {
|
||||
if !shadowed.is_used()
|
||||
&& binding.redefines(shadowed)
|
||||
&& (!self.settings.dummy_variable_rgx.is_match(name) || shadows_import)
|
||||
&& !(shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(self.semantic.stmts[shadowed.source.unwrap()]),
|
||||
&self.semantic,
|
||||
))
|
||||
{
|
||||
if self.enabled(Rule::RedefinedWhileUnused) {
|
||||
#[allow(deprecated)]
|
||||
let line = self.locator.compute_line_index(shadowed.range.start());
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::RedefinedWhileUnused {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
);
|
||||
if let Some(range) = binding.parent_range(&self.semantic) {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
self.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
} else if shadows_import && binding.redefines(shadowed) {
|
||||
self.semantic
|
||||
.shadowed_bindings
|
||||
.insert(binding_id, shadowed_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's an existing binding in this scope, copy its references.
|
||||
if let Some(shadowed_id) = self.semantic.scopes[scope_id].get(name) {
|
||||
@@ -4256,6 +4199,21 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.bindings[binding_id].references = references;
|
||||
}
|
||||
} else if let Some(shadowed_id) = self
|
||||
.semantic
|
||||
.scopes
|
||||
.ancestors(scope_id)
|
||||
.skip(1)
|
||||
.find_map(|scope| scope.get(name))
|
||||
{
|
||||
// Otherwise, if there's an existing binding in a parent scope, mark it as shadowed.
|
||||
let binding = self.semantic.binding(binding_id);
|
||||
let shadowed = self.semantic.binding(shadowed_id);
|
||||
if binding.redefines(shadowed) {
|
||||
self.semantic
|
||||
.shadowed_bindings
|
||||
.insert(binding_id, shadowed_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the binding to the scope.
|
||||
@@ -4274,7 +4232,7 @@ impl<'a> Checker<'a> {
|
||||
{
|
||||
// Add the builtin to the scope.
|
||||
let binding_id = self.semantic.push_builtin();
|
||||
let scope = self.semantic.scope_mut();
|
||||
let scope = self.semantic.global_scope_mut();
|
||||
scope.add(builtin, binding_id);
|
||||
}
|
||||
}
|
||||
@@ -4477,7 +4435,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn handle_node_delete(&mut self, expr: &'a Expr) {
|
||||
let Expr::Name(ast::ExprName { id, .. } )= expr else {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -4678,17 +4636,19 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn check_deferred_scopes(&mut self) {
|
||||
if !self.any_enabled(&[
|
||||
Rule::UnusedImport,
|
||||
Rule::GlobalVariableNotAssigned,
|
||||
Rule::UndefinedLocalWithImportStarUsage,
|
||||
Rule::ImportShadowedByLoopVar,
|
||||
Rule::RedefinedWhileUnused,
|
||||
Rule::RuntimeImportInTypeCheckingBlock,
|
||||
Rule::TypingOnlyFirstPartyImport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
Rule::TypingOnlyStandardLibraryImport,
|
||||
Rule::UndefinedExport,
|
||||
Rule::TypingOnlyThirdPartyImport,
|
||||
Rule::UnaliasedCollectionsAbcSetImport,
|
||||
Rule::UnconventionalImportAlias,
|
||||
Rule::UndefinedExport,
|
||||
Rule::UndefinedLocalWithImportStarUsage,
|
||||
Rule::UndefinedLocalWithImportStarUsage,
|
||||
Rule::UnusedImport,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -4755,8 +4715,8 @@ impl<'a> Checker<'a> {
|
||||
};
|
||||
|
||||
let mut diagnostics: Vec<Diagnostic> = vec![];
|
||||
for scope_id in self.deferred.scopes.iter().rev() {
|
||||
let scope = &self.semantic.scopes[*scope_id];
|
||||
for scope_id in self.deferred.scopes.iter().rev().copied() {
|
||||
let scope = &self.semantic.scopes[scope_id];
|
||||
|
||||
if scope.kind.is_module() {
|
||||
// F822
|
||||
@@ -4815,21 +4775,123 @@ impl<'a> Checker<'a> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for any bindings that were redefined in another scope, and remain
|
||||
// unused. Note that we only store references in `shadowed_bindings` if
|
||||
// the bindings are in different scopes.
|
||||
if self.enabled(Rule::RedefinedWhileUnused) {
|
||||
// F402
|
||||
if self.enabled(Rule::ImportShadowedByLoopVar) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
if let Some(shadowed_id) = self.semantic.shadowed_binding(binding_id) {
|
||||
let shadowed = self.semantic.binding(shadowed_id);
|
||||
if shadowed.is_used() {
|
||||
for shadow in self.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
// If the shadowing binding isn't a loop variable, abort.
|
||||
let binding = &self.semantic.bindings[shadow.binding_id()];
|
||||
if !binding.kind.is_loop_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowed binding isn't an import, abort.
|
||||
let shadowed = &self.semantic.bindings[shadow.shadowed_id()];
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(left, right, &self.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = self.locator.compute_line_index(shadowed.range.start());
|
||||
|
||||
let binding = self.semantic.binding(binding_id);
|
||||
self.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::ImportShadowedByLoopVar {
|
||||
name: name.to_string(),
|
||||
line,
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// F811
|
||||
if self.enabled(Rule::RedefinedWhileUnused) {
|
||||
for (name, binding_id) in scope.bindings() {
|
||||
for shadow in self.semantic.shadowed_bindings(scope_id, binding_id) {
|
||||
// If the shadowing binding is a loop variable, abort, to avoid overlap
|
||||
// with F402.
|
||||
let binding = &self.semantic.bindings[shadow.binding_id()];
|
||||
if binding.kind.is_loop_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowed binding is used, abort.
|
||||
let shadowed = &self.semantic.bindings[shadow.shadowed_id()];
|
||||
if shadowed.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the shadowing binding isn't considered a "redefinition" of the
|
||||
// shadowed binding, abort.
|
||||
if !binding.redefines(shadowed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if shadow.same_scope() {
|
||||
// If the symbol is a dummy variable, abort, unless the shadowed
|
||||
// binding is an import.
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) && self.settings.dummy_variable_rgx.is_match(name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is an overloaded function, abort.
|
||||
if shadowed.kind.is_function_definition()
|
||||
&& visibility::is_overload(
|
||||
cast::decorator_list(
|
||||
self.semantic.stmts[shadowed.source.unwrap()],
|
||||
),
|
||||
&self.semantic,
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Only enforce cross-scope shadowing for imports.
|
||||
if !matches!(
|
||||
shadowed.kind,
|
||||
BindingKind::Import(..)
|
||||
| BindingKind::FromImport(..)
|
||||
| BindingKind::SubmoduleImport(..)
|
||||
| BindingKind::FutureImport
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If the bindings are in different forks, abort.
|
||||
if shadowed.source.map_or(true, |left| {
|
||||
binding.source.map_or(true, |right| {
|
||||
branch_detection::different_forks(left, right, &self.semantic.stmts)
|
||||
})
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let line = self.locator.compute_line_index(shadowed.range.start());
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
pyflakes::rules::RedefinedWhileUnused {
|
||||
name: (*name).to_string(),
|
||||
@@ -4851,7 +4913,7 @@ impl<'a> Checker<'a> {
|
||||
} else {
|
||||
self.semantic
|
||||
.scopes
|
||||
.ancestor_ids(*scope_id)
|
||||
.ancestor_ids(scope_id)
|
||||
.flat_map(|scope_id| runtime_imports[scope_id.as_usize()].iter())
|
||||
.copied()
|
||||
.collect()
|
||||
|
||||
@@ -156,6 +156,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyflakes, "901") => (RuleGroup::Unspecified, rules::pyflakes::rules::RaiseNotImplemented),
|
||||
|
||||
// pylint
|
||||
(Pylint, "C0205") => (RuleGroup::Unspecified, rules::pylint::rules::SingleStringSlots),
|
||||
(Pylint, "C0414") => (RuleGroup::Unspecified, rules::pylint::rules::UselessImportAlias),
|
||||
(Pylint, "C1901") => (RuleGroup::Nursery, rules::pylint::rules::CompareToEmptyString),
|
||||
(Pylint, "C3002") => (RuleGroup::Unspecified, rules::pylint::rules::UnnecessaryDirectLambdaCall),
|
||||
@@ -595,6 +596,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// flake8-pyi
|
||||
(Flake8Pyi, "001") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnprefixedTypeParam),
|
||||
(Flake8Pyi, "002") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexIfStatementInStub),
|
||||
(Flake8Pyi, "003") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedVersionInfoCheck),
|
||||
(Flake8Pyi, "004") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::PatchVersionComparison),
|
||||
(Flake8Pyi, "005") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::WrongTupleLengthVersionComparison),
|
||||
(Flake8Pyi, "006") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadVersionInfoComparison),
|
||||
(Flake8Pyi, "007") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformCheck),
|
||||
(Flake8Pyi, "008") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformName),
|
||||
@@ -741,6 +746,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
// numpy
|
||||
(Numpy, "001") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedTypeAlias),
|
||||
(Numpy, "002") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyLegacyRandom),
|
||||
(Numpy, "003") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedFunction),
|
||||
|
||||
// ruff
|
||||
(Ruff, "001") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterString),
|
||||
@@ -784,7 +790,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Airflow, "001") => (RuleGroup::Unspecified, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
|
||||
|
||||
// perflint
|
||||
(Perflint, "101") => (RuleGroup::Unspecified, rules::perflint::rules::UnnecessaryListCast),
|
||||
(Perflint, "102") => (RuleGroup::Unspecified, rules::perflint::rules::IncorrectDictIterator),
|
||||
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
||||
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
|
||||
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
|
||||
|
||||
// flake8-fixme
|
||||
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
||||
|
||||
@@ -18,7 +18,6 @@ pub(crate) struct Docstring<'a> {
|
||||
pub(crate) expr: &'a Expr,
|
||||
/// The content of the docstring, including the leading and trailing quotes.
|
||||
pub(crate) contents: &'a str,
|
||||
|
||||
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
||||
pub(crate) body_range: TextRange,
|
||||
pub(crate) indentation: &'a str,
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_ast::docstrings::{leading_space, leading_words};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use ruff_python_whitespace::{UniversalNewlineIterator, UniversalNewlines};
|
||||
use ruff_python_whitespace::{Line, UniversalNewlineIterator, UniversalNewlines};
|
||||
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::docstrings::{Docstring, DocstringBody};
|
||||
@@ -144,15 +144,13 @@ impl<'a> SectionContexts<'a> {
|
||||
|
||||
let mut contexts = Vec::new();
|
||||
let mut last: Option<SectionContextData> = None;
|
||||
let mut previous_line = None;
|
||||
|
||||
for line in contents.universal_newlines() {
|
||||
if previous_line.is_none() {
|
||||
// skip the first line
|
||||
previous_line = Some(line.as_str());
|
||||
continue;
|
||||
}
|
||||
let mut lines = contents.universal_newlines().peekable();
|
||||
|
||||
// Skip the first line, which is the summary.
|
||||
let mut previous_line = lines.next();
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
if let Some(section_kind) = suspected_as_section(&line, style) {
|
||||
let indent = leading_space(&line);
|
||||
let section_name = leading_words(&line);
|
||||
@@ -162,7 +160,8 @@ impl<'a> SectionContexts<'a> {
|
||||
if is_docstring_section(
|
||||
&line,
|
||||
section_name_range,
|
||||
previous_line.unwrap_or_default(),
|
||||
previous_line.as_ref(),
|
||||
lines.peek(),
|
||||
) {
|
||||
if let Some(mut last) = last.take() {
|
||||
last.range = TextRange::new(last.range.start(), line.start());
|
||||
@@ -178,7 +177,7 @@ impl<'a> SectionContexts<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
previous_line = Some(line.as_str());
|
||||
previous_line = Some(line);
|
||||
}
|
||||
|
||||
if let Some(mut last) = last.take() {
|
||||
@@ -388,7 +387,13 @@ fn suspected_as_section(line: &str, style: SectionStyle) -> Option<SectionKind>
|
||||
}
|
||||
|
||||
/// Check if the suspected context is really a section header.
|
||||
fn is_docstring_section(line: &str, section_name_range: TextRange, previous_lines: &str) -> bool {
|
||||
fn is_docstring_section(
|
||||
line: &Line,
|
||||
section_name_range: TextRange,
|
||||
previous_line: Option<&Line>,
|
||||
next_line: Option<&Line>,
|
||||
) -> bool {
|
||||
// Determine whether the current line looks like a section header, e.g., "Args:".
|
||||
let section_name_suffix = line[usize::from(section_name_range.end())..].trim();
|
||||
let this_looks_like_a_section_name =
|
||||
section_name_suffix == ":" || section_name_suffix.is_empty();
|
||||
@@ -396,13 +401,29 @@ fn is_docstring_section(line: &str, section_name_range: TextRange, previous_line
|
||||
return false;
|
||||
}
|
||||
|
||||
let prev_line = previous_lines.trim();
|
||||
let prev_line_ends_with_punctuation = [',', ';', '.', '-', '\\', '/', ']', '}', ')']
|
||||
.into_iter()
|
||||
.any(|char| prev_line.ends_with(char));
|
||||
let prev_line_looks_like_end_of_paragraph =
|
||||
prev_line_ends_with_punctuation || prev_line.is_empty();
|
||||
if !prev_line_looks_like_end_of_paragraph {
|
||||
// Determine whether the next line is an underline, e.g., "-----".
|
||||
let next_line_is_underline = next_line.map_or(false, |next_line| {
|
||||
let next_line = next_line.trim();
|
||||
if next_line.is_empty() {
|
||||
false
|
||||
} else {
|
||||
let next_line_is_underline = next_line.chars().all(|char| matches!(char, '-' | '='));
|
||||
next_line_is_underline
|
||||
}
|
||||
});
|
||||
if next_line_is_underline {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine whether the previous line looks like the end of a paragraph.
|
||||
let previous_line_looks_like_end_of_paragraph = previous_line.map_or(true, |previous_line| {
|
||||
let previous_line = previous_line.trim();
|
||||
let previous_line_ends_with_punctuation = [',', ';', '.', '-', '\\', '/', ']', '}', ')']
|
||||
.into_iter()
|
||||
.any(|char| previous_line.ends_with(char));
|
||||
previous_line_ends_with_punctuation || previous_line.is_empty()
|
||||
});
|
||||
if !previous_line_looks_like_end_of_paragraph {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,15 +39,6 @@ pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
Ok(String::from_utf8(writer)?)
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Path`] appears to be that of a jupyter notebook file (`.ipynb`).
|
||||
pub fn is_jupyter_notebook(path: &Path) -> bool {
|
||||
path.extension()
|
||||
.map_or(false, |ext| ext == JUPYTER_NOTEBOOK_EXT)
|
||||
// For now this is feature gated here, the long term solution depends on
|
||||
// https://github.com/astral-sh/ruff/issues/3410
|
||||
&& cfg!(feature = "jupyter_notebook")
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// Return the [`SourceValue`] of the cell.
|
||||
fn source(&self) -> &SourceValue {
|
||||
@@ -277,11 +268,12 @@ impl Notebook {
|
||||
.markers()
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|m| m.source <= *offset) else {
|
||||
// There are no markers above the current offset, so we can
|
||||
// stop here.
|
||||
break;
|
||||
};
|
||||
.find(|m| m.source <= *offset)
|
||||
else {
|
||||
// There are no markers above the current offset, so we can
|
||||
// stop here.
|
||||
break;
|
||||
};
|
||||
last_marker = Some(marker);
|
||||
marker
|
||||
}
|
||||
@@ -445,15 +437,13 @@ impl Notebook {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::jupyter::index::JupyterIndex;
|
||||
#[cfg(feature = "jupyter_notebook")]
|
||||
use crate::jupyter::is_jupyter_notebook;
|
||||
use crate::jupyter::schema::Cell;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::registry::Rule;
|
||||
@@ -512,16 +502,6 @@ mod test {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "jupyter_notebook")]
|
||||
fn inclusions() {
|
||||
let path = Path::new("foo/bar/baz");
|
||||
assert!(!is_jupyter_notebook(path));
|
||||
|
||||
let path = Path::new("foo/bar/baz.ipynb");
|
||||
assert!(is_jupyter_notebook(path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concat_notebook() -> Result<()> {
|
||||
let notebook = read_jupyter_notebook(Path::new("valid.ipynb"))?;
|
||||
|
||||
@@ -25,14 +25,12 @@ isort.ipynb:cell 1:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
||||
isort.ipynb:cell 2:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
2 | import random
|
||||
3 | import math
|
||||
4 | / from typing import Any
|
||||
5 | | import collections
|
||||
6 | | # Newline should be added here
|
||||
1 | / from typing import Any
|
||||
2 | | import collections
|
||||
3 | | # Newline should be added here
|
||||
| |_^ I001
|
||||
7 | def foo():
|
||||
8 | pass
|
||||
4 | def foo():
|
||||
5 | pass
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
@@ -9,6 +9,8 @@ pub use ruff_python_ast::source_code::round_trip;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::IOError;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
mod autofix;
|
||||
mod checkers;
|
||||
mod codes;
|
||||
|
||||
@@ -149,7 +149,14 @@ impl Display for DisplayGroupedMessage<'_> {
|
||||
if self.show_source {
|
||||
use std::fmt::Write;
|
||||
let mut padded = PadAdapter::new(f);
|
||||
writeln!(padded, "{}", MessageCodeFrame { message })?;
|
||||
writeln!(
|
||||
padded,
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index: self.jupyter_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -11,7 +11,7 @@ use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
|
||||
|
||||
use crate::fs::relativize_path;
|
||||
use crate::jupyter::Notebook;
|
||||
use crate::jupyter::{JupyterIndex, Notebook};
|
||||
use crate::line_width::{LineWidth, TabSize};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext, Message};
|
||||
@@ -72,13 +72,13 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
let start_location = message.compute_start_location();
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = context
|
||||
let jupyter_index = context
|
||||
.source_kind(message.filename())
|
||||
.and_then(SourceKind::notebook)
|
||||
.map(Notebook::index)
|
||||
{
|
||||
.map(Notebook::index);
|
||||
|
||||
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
|
||||
let diagnostic_location = if let Some(jupyter_index) = jupyter_index {
|
||||
write!(
|
||||
writer,
|
||||
"cell {cell}{sep}",
|
||||
@@ -114,7 +114,14 @@ impl Emitter for TextEmitter {
|
||||
)?;
|
||||
|
||||
if self.flags.contains(EmitterFlags::SHOW_SOURCE) {
|
||||
writeln!(writer, "{}", MessageCodeFrame { message })?;
|
||||
writeln!(
|
||||
writer,
|
||||
"{}",
|
||||
MessageCodeFrame {
|
||||
message,
|
||||
jupyter_index
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.flags.contains(EmitterFlags::SHOW_FIX_DIFF) {
|
||||
@@ -158,6 +165,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
|
||||
pub(super) struct MessageCodeFrame<'a> {
|
||||
pub(crate) message: &'a Message,
|
||||
pub(crate) jupyter_index: Option<&'a JupyterIndex>,
|
||||
}
|
||||
|
||||
impl Display for MessageCodeFrame<'_> {
|
||||
@@ -182,6 +190,20 @@ impl Display for MessageCodeFrame<'_> {
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_start_cell = jupyter_index
|
||||
.cell(content_start_index.get())
|
||||
.unwrap_or_default();
|
||||
while start_index < content_start_index {
|
||||
if jupyter_index.cell(start_index.get()).unwrap_or_default() == content_start_cell {
|
||||
break;
|
||||
}
|
||||
start_index = start_index.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim leading empty lines.
|
||||
while start_index < content_start_index {
|
||||
if !source_code.line_text(start_index).trim().is_empty() {
|
||||
@@ -195,7 +217,21 @@ impl Display for MessageCodeFrame<'_> {
|
||||
.saturating_add(2)
|
||||
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
|
||||
|
||||
// Trim trailing empty lines
|
||||
// If we're working on a jupyter notebook, skip the lines which are
|
||||
// outside of the cell containing the diagnostic.
|
||||
if let Some(jupyter_index) = self.jupyter_index {
|
||||
let content_end_cell = jupyter_index
|
||||
.cell(content_end_index.get())
|
||||
.unwrap_or_default();
|
||||
while end_index > content_end_index {
|
||||
if jupyter_index.cell(end_index.get()).unwrap_or_default() == content_end_cell {
|
||||
break;
|
||||
}
|
||||
end_index = end_index.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim trailing empty lines.
|
||||
while end_index > content_end_index {
|
||||
if !source_code.line_text(end_index).trim().is_empty() {
|
||||
break;
|
||||
@@ -224,7 +260,14 @@ impl Display for MessageCodeFrame<'_> {
|
||||
title: None,
|
||||
slices: vec![Slice {
|
||||
source: &source.text,
|
||||
line_start: start_index.get(),
|
||||
line_start: self.jupyter_index.map_or_else(
|
||||
|| start_index.get(),
|
||||
|jupyter_index| {
|
||||
jupyter_index
|
||||
.cell_row(start_index.get())
|
||||
.unwrap_or_default() as usize
|
||||
},
|
||||
),
|
||||
annotations: vec![SourceAnnotation {
|
||||
label: &label,
|
||||
annotation_type: AnnotationType::Error,
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_macros::CacheKey;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
const RULESET_SIZE: usize = 10;
|
||||
const RULESET_SIZE: usize = 11;
|
||||
|
||||
/// A set of [`Rule`]s.
|
||||
///
|
||||
|
||||
@@ -3,7 +3,7 @@ use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::prelude::Constant;
|
||||
use rustpython_parser::ast::Constant;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
|
||||
@@ -9,6 +9,38 @@ use crate::registry::Rule;
|
||||
|
||||
use super::super::helpers::is_sys;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons that test `sys.version` against string literals,
|
||||
/// such that the comparison will evaluate to `False` on Python 3.10 or later.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Comparing `sys.version` to a string is error-prone and may cause subtle
|
||||
/// bugs, as the comparison will be performed lexicographically, not
|
||||
/// semantically. For example, `sys.version > "3.9"` will evaluate to `False`
|
||||
/// when using Python 3.10, as `"3.10"` is lexicographically "less" than
|
||||
/// `"3.9"`.
|
||||
///
|
||||
/// Instead, use `sys.version_info` to access the current major and minor
|
||||
/// version numbers as a tuple, which can be compared to other tuples
|
||||
/// without issue.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version > "3.9" # `False` on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version_info > (3, 9) # `True` on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionCmpStr3;
|
||||
|
||||
@@ -19,6 +51,43 @@ impl Violation for SysVersionCmpStr3 {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for equality comparisons against the major version returned by
|
||||
/// `sys.version_info` (e.g., `sys.version_info[0] == 3`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `sys.version_info[0] == 3` to verify that the major version is
|
||||
/// Python 3 or greater will fail if the major version number is ever
|
||||
/// incremented (e.g., to Python 4). This is likely unintended, as code
|
||||
/// that uses this comparison is likely intended to be run on Python 2,
|
||||
/// but would now run on Python 4 too.
|
||||
///
|
||||
/// Instead, use `>=` to check if the major version number is 3 or greater,
|
||||
/// to future-proof the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info[0] == 3:
|
||||
/// ...
|
||||
/// else:
|
||||
/// print("Python 2") # This will be printed on Python 4.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info >= (3,):
|
||||
/// ...
|
||||
/// else:
|
||||
/// print("Python 2") # This will not be printed on Python 4.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfo0Eq3;
|
||||
|
||||
@@ -29,6 +98,36 @@ impl Violation for SysVersionInfo0Eq3 {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons that test `sys.version_info[1]` against an integer.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Comparisons based on the current minor version number alone can cause
|
||||
/// subtle bugs and would likely lead to unintended effects if the Python
|
||||
/// major version number were ever incremented (e.g., to Python 4).
|
||||
///
|
||||
/// Instead, compare `sys.version_info` to a tuple, including the major and
|
||||
/// minor version numbers, to future-proof the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info[1] < 7:
|
||||
/// print("Python 3.6 or earlier.") # This will be printed on Python 4.0.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info < (3, 7):
|
||||
/// print("Python 3.6 or earlier.")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfo1CmpInt;
|
||||
|
||||
@@ -42,6 +141,36 @@ impl Violation for SysVersionInfo1CmpInt {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons that test `sys.version_info.minor` against an integer.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Comparisons based on the current minor version number alone can cause
|
||||
/// subtle bugs and would likely lead to unintended effects if the Python
|
||||
/// major version number were ever incremented (e.g., to Python 4).
|
||||
///
|
||||
/// Instead, compare `sys.version_info` to a tuple, including the major and
|
||||
/// minor version numbers, to future-proof the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info.minor < 7:
|
||||
/// print("Python 3.6 or earlier.") # This will be printed on Python 4.0.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.version_info < (3, 7):
|
||||
/// print("Python 3.6 or earlier.")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionInfoMinorCmpInt;
|
||||
|
||||
@@ -55,6 +184,37 @@ impl Violation for SysVersionInfoMinorCmpInt {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for comparisons that test `sys.version` against string literals,
|
||||
/// such that the comparison would fail if the major version number were
|
||||
/// ever incremented to Python 10 or higher.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Comparing `sys.version` to a string is error-prone and may cause subtle
|
||||
/// bugs, as the comparison will be performed lexicographically, not
|
||||
/// semantically.
|
||||
///
|
||||
/// Instead, use `sys.version_info` to access the current major and minor
|
||||
/// version numbers as a tuple, which can be compared to other tuples
|
||||
/// without issue.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version >= "3" # `False` on Python 10.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version_info >= (3,) # `True` on Python 10.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionCmpStr10;
|
||||
|
||||
|
||||
@@ -5,6 +5,35 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `six.PY3`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `six.PY3` will evaluate to `False` on Python 4 and greater. This is likely
|
||||
/// unintended, and may cause code intended to run on Python 2 to run on Python 4
|
||||
/// too.
|
||||
///
|
||||
/// Instead, use `not six.PY2` to validate that the current Python major version is
|
||||
/// _not_ equal to 2, to future-proof the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import six
|
||||
///
|
||||
/// six.PY3 # `False` on Python 4.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import six
|
||||
///
|
||||
/// not six.PY2 # `True` on Python 4.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PyPI: `six`](https://pypi.org/project/six/)
|
||||
/// - [Six documentation: `six.PY2`](https://six.readthedocs.io/#six.PY2)
|
||||
/// - [Six documentation: `six.PY3`](https://six.readthedocs.io/#six.PY3)
|
||||
#[violation]
|
||||
pub struct SixPY3;
|
||||
|
||||
|
||||
@@ -8,6 +8,36 @@ use crate::checkers::ast::Checker;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_2020::helpers::is_sys;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `sys.version[:3]`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If the current major or minor version consists of multiple digits,
|
||||
/// `sys.version[:3]` will truncate the version number (e.g., `"3.10"` would
|
||||
/// become `"3.1"`). This is likely unintended, and can lead to subtle bugs if
|
||||
/// the version string is used to test against a specific Python version.
|
||||
///
|
||||
/// Instead, use `sys.version_info` to access the current major and minor
|
||||
/// version numbers as a tuple, which can be compared to other tuples
|
||||
/// without issue.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version[:3] # Evaluates to "3.1" on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version_info[:2] # Evaluates to (3, 10) on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionSlice3;
|
||||
|
||||
@@ -18,6 +48,36 @@ impl Violation for SysVersionSlice3 {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `sys.version[2]`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If the current major or minor version consists of multiple digits,
|
||||
/// `sys.version[2]` will select the first digit of the minor number only
|
||||
/// (e.g., `"3.10"` would evaluate to `"1"`). This is likely unintended, and
|
||||
/// can lead to subtle bugs if the version is used to test against a minor
|
||||
/// version number.
|
||||
///
|
||||
/// Instead, use `sys.version_info.minor` to access the current minor version
|
||||
/// number.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version[2] # Evaluates to "1" on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// f"{sys.version_info.minor}" # Evaluates to "10" on Python 3.10.
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersion2;
|
||||
|
||||
@@ -28,6 +88,36 @@ impl Violation for SysVersion2 {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `sys.version[0]`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If the current major or minor version consists of multiple digits,
|
||||
/// `sys.version[0]` will select the first digit of the major version number
|
||||
/// only (e.g., `"3.10"` would evaluate to `"1"`). This is likely unintended,
|
||||
/// and can lead to subtle bugs if the version string is used to test against a
|
||||
/// major version number.
|
||||
///
|
||||
/// Instead, use `sys.version_info.major` to access the current major version
|
||||
/// number.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version[0] # If using Python 10, this evaluates to "1".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// f"{sys.version_info.major}" # If using Python 10, this evaluates to "10".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersion0;
|
||||
|
||||
@@ -38,6 +128,36 @@ impl Violation for SysVersion0 {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `sys.version[:1]`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If the major version number consists of more than one digit, this will
|
||||
/// select the first digit of the major version number only (e.g., `"10.0"`
|
||||
/// would evaluate to `"1"`). This is likely unintended, and can lead to subtle
|
||||
/// bugs in future versions of Python if the version string is used to test
|
||||
/// against a specific major version number.
|
||||
///
|
||||
/// Instead, use `sys.version_info.major` to access the current major version
|
||||
/// number.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// sys.version[:1] # If using Python 10, this evaluates to "1".
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import sys
|
||||
///
|
||||
/// f"{sys.version_info.major}" # If using Python 10, this evaluates to "10".
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `sys.version`](https://docs.python.org/3/library/sys.html#sys.version)
|
||||
/// - [Python documentation: `sys.version_info`](https://docs.python.org/3/library/sys.html#sys.version_info)
|
||||
#[violation]
|
||||
pub struct SysVersionSlice1;
|
||||
|
||||
|
||||
@@ -457,11 +457,7 @@ pub(crate) fn definition(
|
||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||
// We could adhere more closely to `flake8-annotations` by defining public
|
||||
// vs. secret vs. protected.
|
||||
let Definition::Member(Member {
|
||||
kind,
|
||||
stmt,
|
||||
..
|
||||
}) = definition else {
|
||||
let Definition::Member(Member { kind, stmt, .. }) = definition else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use rustpython_parser::ast::{Arg, ArgWithDefault, Arguments, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
@@ -36,9 +37,7 @@ fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
|
||||
}
|
||||
|
||||
/// S107
|
||||
pub(crate) fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
pub(crate) fn hardcoded_password_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
default,
|
||||
@@ -53,9 +52,7 @@ pub(crate) fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnosti
|
||||
continue;
|
||||
};
|
||||
if let Some(diagnostic) = check_password_kwarg(def, default) {
|
||||
diagnostics.push(diagnostic);
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use rustpython_parser::ast::{Keyword, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
@@ -22,10 +23,10 @@ impl Violation for HardcodedPasswordFuncArg {
|
||||
}
|
||||
|
||||
/// S106
|
||||
pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnostic> {
|
||||
keywords
|
||||
.iter()
|
||||
.filter_map(|keyword| {
|
||||
pub(crate) fn hardcoded_password_func_arg(checker: &mut Checker, keywords: &[Keyword]) {
|
||||
checker
|
||||
.diagnostics
|
||||
.extend(keywords.iter().filter_map(|keyword| {
|
||||
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
|
||||
let arg = keyword.arg.as_ref()?;
|
||||
if !matches_password_name(arg) {
|
||||
@@ -37,6 +38,5 @@ pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnosti
|
||||
},
|
||||
keyword.range(),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::super::helpers::{matches_password_name, string_literal};
|
||||
|
||||
#[violation]
|
||||
@@ -47,12 +49,13 @@ fn password_target(target: &Expr) -> Option<&str> {
|
||||
|
||||
/// S105
|
||||
pub(crate) fn compare_to_hardcoded_password_string(
|
||||
checker: &mut Checker,
|
||||
left: &Expr,
|
||||
comparators: &[Expr],
|
||||
) -> Vec<Diagnostic> {
|
||||
comparators
|
||||
.iter()
|
||||
.filter_map(|comp| {
|
||||
) {
|
||||
checker
|
||||
.diagnostics
|
||||
.extend(comparators.iter().filter_map(|comp| {
|
||||
string_literal(comp).filter(|string| !string.is_empty())?;
|
||||
let Some(name) = password_target(left) else {
|
||||
return None;
|
||||
@@ -63,29 +66,29 @@ pub(crate) fn compare_to_hardcoded_password_string(
|
||||
},
|
||||
comp.range(),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}));
|
||||
}
|
||||
|
||||
/// S105
|
||||
pub(crate) fn assign_hardcoded_password_string(
|
||||
checker: &mut Checker,
|
||||
value: &Expr,
|
||||
targets: &[Expr],
|
||||
) -> Option<Diagnostic> {
|
||||
) {
|
||||
if string_literal(value)
|
||||
.filter(|string| !string.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
for target in targets {
|
||||
if let Some(name) = password_target(target) {
|
||||
return Some(Diagnostic::new(
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedPasswordString {
|
||||
name: name.to_string(),
|
||||
},
|
||||
value.range(),
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||
return None;
|
||||
};
|
||||
// Only evaluate the full BinOp, not the nested components.
|
||||
let Expr::BinOp(_ )= parent else {
|
||||
let Expr::BinOp(_) = parent else {
|
||||
if any_over_expr(expr, &has_string_literal) {
|
||||
return Some(checker.generator().expr(expr));
|
||||
}
|
||||
|
||||
@@ -21,21 +21,6 @@ impl Violation for RequestWithNoCertValidation {
|
||||
}
|
||||
}
|
||||
|
||||
const REQUESTS_HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
|
||||
const HTTPX_METHODS: [&str; 11] = [
|
||||
"get",
|
||||
"options",
|
||||
"head",
|
||||
"post",
|
||||
"put",
|
||||
"patch",
|
||||
"delete",
|
||||
"request",
|
||||
"stream",
|
||||
"Client",
|
||||
"AsyncClient",
|
||||
];
|
||||
|
||||
/// S501
|
||||
pub(crate) fn request_with_no_cert_validation(
|
||||
checker: &mut Checker,
|
||||
@@ -46,16 +31,13 @@ pub(crate) fn request_with_no_cert_validation(
|
||||
if let Some(target) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.and_then(|call_path| {
|
||||
if call_path.len() == 2 {
|
||||
if call_path[0] == "requests" && REQUESTS_HTTP_VERBS.contains(&call_path[1]) {
|
||||
return Some("requests");
|
||||
}
|
||||
if call_path[0] == "httpx" && HTTPX_METHODS.contains(&call_path[1]) {
|
||||
return Some("httpx");
|
||||
}
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
|
||||
Some("requests")
|
||||
}
|
||||
None
|
||||
["httpx", "get" | "options" | "head" | "post" | "put" | "patch" | "delete" | "request"
|
||||
| "stream" | "Client" | "AsyncClient"] => Some("httpx"),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
||||
use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
||||
use ruff_python_ast::helpers::{is_const_none, SimpleCallArgs};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[violation]
|
||||
pub struct RequestWithoutTimeout {
|
||||
pub timeout: Option<String>,
|
||||
implicit: bool,
|
||||
}
|
||||
|
||||
impl Violation for RequestWithoutTimeout {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let RequestWithoutTimeout { timeout } = self;
|
||||
match timeout {
|
||||
Some(value) => {
|
||||
format!("Probable use of requests call with timeout set to `{value}`")
|
||||
}
|
||||
None => format!("Probable use of requests call without timeout"),
|
||||
let RequestWithoutTimeout { implicit } = self;
|
||||
if *implicit {
|
||||
format!("Probable use of requests call without timeout")
|
||||
} else {
|
||||
format!("Probable use of requests call with timeout set to `None`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
|
||||
|
||||
/// S113
|
||||
pub(crate) fn request_without_timeout(
|
||||
checker: &mut Checker,
|
||||
@@ -37,30 +34,26 @@ pub(crate) fn request_without_timeout(
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
HTTP_VERBS
|
||||
.iter()
|
||||
.any(|func_name| call_path.as_slice() == ["requests", func_name])
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
[
|
||||
"requests",
|
||||
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
|
||||
]
|
||||
)
|
||||
})
|
||||
{
|
||||
let call_args = SimpleCallArgs::new(args, keywords);
|
||||
if let Some(timeout_arg) = call_args.keyword_argument("timeout") {
|
||||
if let Some(timeout) = match timeout_arg {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: value @ Constant::None,
|
||||
..
|
||||
}) => Some(checker.generator().constant(value)),
|
||||
_ => None,
|
||||
} {
|
||||
if let Some(timeout) = call_args.keyword_argument("timeout") {
|
||||
if is_const_none(timeout) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout {
|
||||
timeout: Some(timeout),
|
||||
},
|
||||
timeout_arg.range(),
|
||||
RequestWithoutTimeout { implicit: false },
|
||||
timeout.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { timeout: None },
|
||||
RequestWithoutTimeout { implicit: true },
|
||||
func.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -4,50 +4,57 @@ use ruff_diagnostics::{Diagnostic, DiagnosticKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
pub(super) const FUNC_CALL_NAME_ALLOWLIST: &[&str] = &[
|
||||
"append",
|
||||
"assertEqual",
|
||||
"assertEquals",
|
||||
"assertNotEqual",
|
||||
"assertNotEquals",
|
||||
"bool",
|
||||
"bytes",
|
||||
"count",
|
||||
"failIfEqual",
|
||||
"failUnlessEqual",
|
||||
"float",
|
||||
"fromkeys",
|
||||
"get",
|
||||
"getattr",
|
||||
"getboolean",
|
||||
"getfloat",
|
||||
"getint",
|
||||
"index",
|
||||
"insert",
|
||||
"int",
|
||||
"param",
|
||||
"pop",
|
||||
"remove",
|
||||
"set_blocking",
|
||||
"set_enabled",
|
||||
"setattr",
|
||||
"__setattr__",
|
||||
"setdefault",
|
||||
"str",
|
||||
];
|
||||
/// Returns `true` if a function call is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"append"
|
||||
| "assertEqual"
|
||||
| "assertEquals"
|
||||
| "assertNotEqual"
|
||||
| "assertNotEquals"
|
||||
| "bool"
|
||||
| "bytes"
|
||||
| "count"
|
||||
| "failIfEqual"
|
||||
| "failUnlessEqual"
|
||||
| "float"
|
||||
| "fromkeys"
|
||||
| "get"
|
||||
| "getattr"
|
||||
| "getboolean"
|
||||
| "getfloat"
|
||||
| "getint"
|
||||
| "index"
|
||||
| "insert"
|
||||
| "int"
|
||||
| "param"
|
||||
| "pop"
|
||||
| "remove"
|
||||
| "set_blocking"
|
||||
| "set_enabled"
|
||||
| "setattr"
|
||||
| "__setattr__"
|
||||
| "setdefault"
|
||||
| "str"
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) const FUNC_DEF_NAME_ALLOWLIST: &[&str] = &["__setitem__"];
|
||||
/// Returns `true` if a function definition is allowed to use a boolean trap.
|
||||
pub(super) fn is_allowed_func_def(name: &str) -> bool {
|
||||
matches!(name, "__setitem__")
|
||||
}
|
||||
|
||||
/// Returns `true` if an argument is allowed to use a boolean trap. To return
|
||||
/// `true`, the function name must be explicitly allowed, and the argument must
|
||||
/// be either the first or second argument in the call.
|
||||
pub(super) fn allow_boolean_trap(func: &Expr) -> bool {
|
||||
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func {
|
||||
return FUNC_CALL_NAME_ALLOWLIST.contains(&attr.as_ref());
|
||||
return is_allowed_func_call(attr);
|
||||
}
|
||||
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = func {
|
||||
return FUNC_CALL_NAME_ALLOWLIST.contains(&id.as_ref());
|
||||
return is_allowed_func_call(id);
|
||||
}
|
||||
|
||||
false
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use rustpython_parser::ast::{ArgWithDefault, Arguments, Decorator};
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::add_if_boolean;
|
||||
|
||||
use super::super::helpers::FUNC_DEF_NAME_ALLOWLIST;
|
||||
use crate::rules::flake8_boolean_trap::helpers::{add_if_boolean, is_allowed_func_def};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of booleans as default values in function definitions.
|
||||
@@ -64,7 +61,7 @@ pub(crate) fn check_boolean_default_value_in_function_definition(
|
||||
decorator_list: &[Decorator],
|
||||
arguments: &Arguments,
|
||||
) {
|
||||
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::collect_call_path;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_boolean_trap::helpers::FUNC_DEF_NAME_ALLOWLIST;
|
||||
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for boolean positional arguments in function definitions.
|
||||
@@ -82,7 +82,7 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||
decorator_list: &[Decorator],
|
||||
arguments: &Arguments,
|
||||
) {
|
||||
if FUNC_DEF_NAME_ALLOWLIST.contains(&name) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,19 +161,19 @@ pub(crate) fn abstract_base_class(
|
||||
continue;
|
||||
}
|
||||
|
||||
let (
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
}) | Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
})
|
||||
) = stmt else {
|
||||
let (Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
name: method_name,
|
||||
..
|
||||
})) = stmt
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt, WithItem};
|
||||
use std::fmt;
|
||||
|
||||
use rustpython_parser::ast::{self, Expr, Ranged, WithItem};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum AssertionKind {
|
||||
AssertRaises,
|
||||
PytestRaises,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `self.assertRaises(Exception)` or `pytest.raises(Exception)`.
|
||||
/// Checks for `assertRaises` and `pytest.raises` context managers that catch
|
||||
/// `Exception` or `BaseException`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// These forms catch every `Exception`, which can lead to tests passing even
|
||||
/// if, e.g., the code being tested is never executed due to a typo.
|
||||
/// if, e.g., the code under consideration raises a `SyntaxError` or
|
||||
/// `IndentationError`.
|
||||
///
|
||||
/// Either assert for a more specific exception (builtin or custom), or use
|
||||
/// `assertRaisesRegex` or `pytest.raises(..., match=<REGEX>)` respectively.
|
||||
@@ -32,51 +30,83 @@ pub(crate) enum AssertionKind {
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct AssertRaisesException {
|
||||
kind: AssertionKind,
|
||||
assertion: AssertionKind,
|
||||
exception: ExceptionKind,
|
||||
}
|
||||
|
||||
impl Violation for AssertRaisesException {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
match self.kind {
|
||||
AssertionKind::AssertRaises => {
|
||||
format!("`assertRaises(Exception)` should be considered evil")
|
||||
}
|
||||
AssertionKind::PytestRaises => {
|
||||
format!("`pytest.raises(Exception)` should be considered evil")
|
||||
}
|
||||
let AssertRaisesException {
|
||||
assertion,
|
||||
exception,
|
||||
} = self;
|
||||
format!("`{assertion}({exception})` should be considered evil")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum AssertionKind {
|
||||
AssertRaises,
|
||||
PytestRaises,
|
||||
}
|
||||
|
||||
impl fmt::Display for AssertionKind {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
AssertionKind::AssertRaises => fmt.write_str("assertRaises"),
|
||||
AssertionKind::PytestRaises => fmt.write_str("pytest.raises"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum ExceptionKind {
|
||||
BaseException,
|
||||
Exception,
|
||||
}
|
||||
|
||||
impl fmt::Display for ExceptionKind {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ExceptionKind::BaseException => fmt.write_str("BaseException"),
|
||||
ExceptionKind::Exception => fmt.write_str("Exception"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// B017
|
||||
pub(crate) fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[WithItem]) {
|
||||
let Some(item) = items.first() else {
|
||||
return;
|
||||
};
|
||||
let item_context = &item.context_expr;
|
||||
let Expr::Call(ast::ExprCall { func, args, keywords, range: _ }) = &item_context else {
|
||||
return;
|
||||
};
|
||||
if args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
if item.optional_vars.is_some() {
|
||||
return;
|
||||
}
|
||||
pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) {
|
||||
for item in items {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
}) = &item.context_expr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
if item.optional_vars.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(args.first().unwrap())
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["", "Exception"])
|
||||
})
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(exception) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(args.first().unwrap())
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["", "Exception"] => Some(ExceptionKind::Exception),
|
||||
["", "BaseException"] => Some(ExceptionKind::BaseException),
|
||||
_ => None,
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let kind = {
|
||||
if matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
|
||||
let assertion = if matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
|
||||
{
|
||||
AssertionKind::AssertRaises
|
||||
} else if checker
|
||||
@@ -92,11 +122,14 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items:
|
||||
AssertionKind::PytestRaises
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
AssertRaisesException { kind },
|
||||
stmt.range(),
|
||||
));
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
AssertRaisesException {
|
||||
assertion,
|
||||
exception,
|
||||
},
|
||||
item.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr])
|
||||
if attr != "environ" {
|
||||
return;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. } )= value.as_ref() else {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if id != "os" {
|
||||
|
||||
@@ -166,7 +166,11 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand
|
||||
let mut seen: FxHashSet<CallPath> = FxHashSet::default();
|
||||
let mut duplicates: FxHashMap<CallPath, Vec<&Expr>> = FxHashMap::default();
|
||||
for handler in handlers {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_: Some(type_), .. }) = handler else {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
type_: Some(type_),
|
||||
..
|
||||
}) = handler
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match type_.as_ref() {
|
||||
|
||||
@@ -47,7 +47,7 @@ impl Violation for ExceptWithNonExceptionClasses {
|
||||
/// This should leave any unstarred iterables alone (subsequently raising a
|
||||
/// warning for B029).
|
||||
fn flatten_starred_iterables(expr: &Expr) -> Vec<&Expr> {
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. } )= expr else {
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = expr else {
|
||||
return vec![expr];
|
||||
};
|
||||
let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(elts.len());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{self, Expr, Stmt};
|
||||
use rustpython_parser::ast::{self, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -50,9 +50,9 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
return;
|
||||
};
|
||||
let Expr::JoinedStr ( _) = value.as_ref() else {
|
||||
if !value.is_joined_str_expr() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FStringDocstring, stmt.identifier()));
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_parser::ast::{self, Comprehension, Expr, ExprContext, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::collect_arg_names;
|
||||
use ruff_python_ast::helpers::includes_arg_name;
|
||||
use ruff_python_ast::types::Node;
|
||||
use ruff_python_ast::visitor;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
@@ -58,19 +57,17 @@ impl Violation for FunctionUsesLoopVariable {
|
||||
|
||||
#[derive(Default)]
|
||||
struct LoadedNamesVisitor<'a> {
|
||||
// Tuple of: name, defining expression, and defining range.
|
||||
loaded: Vec<(&'a str, &'a Expr)>,
|
||||
// Tuple of: name, defining expression, and defining range.
|
||||
stored: Vec<(&'a str, &'a Expr)>,
|
||||
loaded: Vec<&'a ast::ExprName>,
|
||||
stored: Vec<&'a ast::ExprName>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, ctx, range: _ }) => match ctx {
|
||||
ExprContext::Load => self.loaded.push((id, expr)),
|
||||
ExprContext::Store => self.stored.push((id, expr)),
|
||||
Expr::Name(name) => match &name.ctx {
|
||||
ExprContext::Load => self.loaded.push(name),
|
||||
ExprContext::Store => self.stored.push(name),
|
||||
ExprContext::Del => {}
|
||||
},
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
@@ -80,7 +77,7 @@ impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
|
||||
|
||||
#[derive(Default)]
|
||||
struct SuspiciousVariablesVisitor<'a> {
|
||||
names: Vec<(&'a str, &'a Expr)>,
|
||||
names: Vec<&'a ast::ExprName>,
|
||||
safe_functions: Vec<&'a Expr>,
|
||||
}
|
||||
|
||||
@@ -95,17 +92,20 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
|
||||
// Collect all argument names.
|
||||
let mut arg_names = collect_arg_names(args);
|
||||
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
|
||||
|
||||
// Treat any non-arguments as "suspicious".
|
||||
self.names.extend(
|
||||
visitor
|
||||
.loaded
|
||||
.into_iter()
|
||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
||||
);
|
||||
self.names
|
||||
.extend(visitor.loaded.into_iter().filter(|loaded| {
|
||||
if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if includes_arg_name(&loaded.id, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
Stmt::Return(ast::StmtReturn {
|
||||
@@ -132,10 +132,9 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
}) => {
|
||||
match func.as_ref() {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
let id = id.as_str();
|
||||
if id == "filter" || id == "reduce" || id == "map" {
|
||||
if matches!(id.as_str(), "filter" | "reduce" | "map") {
|
||||
for arg in args {
|
||||
if matches!(arg, Expr::Lambda(_)) {
|
||||
if arg.is_lambda_expr() {
|
||||
self.safe_functions.push(arg);
|
||||
}
|
||||
}
|
||||
@@ -159,7 +158,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().map_or(false, |arg| arg == "key")
|
||||
&& matches!(keyword.value, Expr::Lambda(_))
|
||||
&& keyword.value.is_lambda_expr()
|
||||
{
|
||||
self.safe_functions.push(&keyword.value);
|
||||
}
|
||||
@@ -175,17 +174,19 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
visitor.visit_expr(body);
|
||||
|
||||
// Collect all argument names.
|
||||
let mut arg_names = collect_arg_names(args);
|
||||
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
|
||||
|
||||
// Treat any non-arguments as "suspicious".
|
||||
self.names.extend(
|
||||
visitor
|
||||
.loaded
|
||||
.iter()
|
||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
||||
);
|
||||
self.names
|
||||
.extend(visitor.loaded.into_iter().filter(|loaded| {
|
||||
if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if includes_arg_name(&loaded.id, args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -198,7 +199,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
|
||||
#[derive(Default)]
|
||||
struct NamesFromAssignmentsVisitor<'a> {
|
||||
names: FxHashSet<&'a str>,
|
||||
names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all names used in an assignment expression.
|
||||
@@ -206,7 +207,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
match expr {
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
self.names.insert(id.as_str());
|
||||
self.names.push(id.as_str());
|
||||
}
|
||||
Expr::Starred(ast::ExprStarred { value, .. }) => {
|
||||
self.visit_expr(value);
|
||||
@@ -223,7 +224,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
|
||||
|
||||
#[derive(Default)]
|
||||
struct AssignedNamesVisitor<'a> {
|
||||
names: FxHashSet<&'a str>,
|
||||
names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
/// `Visitor` to collect all used identifiers in a statement.
|
||||
@@ -257,7 +258,7 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
if matches!(expr, Expr::Lambda(_)) {
|
||||
if expr.is_lambda_expr() {
|
||||
// Don't recurse.
|
||||
return;
|
||||
}
|
||||
@@ -300,15 +301,15 @@ pub(crate) fn function_uses_loop_variable<'a>(checker: &mut Checker<'a>, node: &
|
||||
|
||||
// If a variable was used in a function or lambda body, and assigned in the
|
||||
// loop, flag it.
|
||||
for (name, expr) in suspicious_variables {
|
||||
if reassigned_in_loop.contains(name) {
|
||||
if !checker.flake8_bugbear_seen.contains(&expr) {
|
||||
checker.flake8_bugbear_seen.push(expr);
|
||||
for name in suspicious_variables {
|
||||
if reassigned_in_loop.contains(&name.id.as_str()) {
|
||||
if !checker.flake8_bugbear_seen.contains(&name) {
|
||||
checker.flake8_bugbear_seen.push(name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
FunctionUsesLoopVariable {
|
||||
name: name.to_string(),
|
||||
name: name.id.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
name.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn getattr_with_constant(
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) {
|
||||
let Expr::Name(ast::ExprName { id, .. } )= func else {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if id != "getattr" {
|
||||
@@ -76,7 +76,8 @@ pub(crate) fn getattr_with_constant(
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} )= arg else {
|
||||
}) = arg
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !is_identifier(value) {
|
||||
|
||||
@@ -69,7 +69,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, arguments: &Argume
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
{
|
||||
let Some(default)= default else {
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -59,7 +59,11 @@ pub(crate) fn redundant_tuple_in_exception_handler(
|
||||
handlers: &[ExceptHandler],
|
||||
) {
|
||||
for handler in handlers {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_: Some(type_), .. }) = handler else {
|
||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||
type_: Some(type_),
|
||||
..
|
||||
}) = handler
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else {
|
||||
|
||||
@@ -82,7 +82,8 @@ pub(crate) fn setattr_with_constant(
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(name),
|
||||
..
|
||||
} )= name else {
|
||||
}) = name
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !is_identifier(name) {
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn star_arg_unpacking_after_keyword_arg(
|
||||
return;
|
||||
};
|
||||
for arg in args {
|
||||
let Expr::Starred (_) = arg else {
|
||||
let Expr::Starred(_) = arg else {
|
||||
continue;
|
||||
};
|
||||
if arg.start() <= keyword.start() {
|
||||
|
||||
@@ -62,7 +62,8 @@ pub(crate) fn strip_with_multi_characters(
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} )= &args[0] else {
|
||||
}) = &args[0]
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ pub(crate) fn unary_prefix_increment(
|
||||
if !matches!(op, UnaryOp::UAdd) {
|
||||
return;
|
||||
}
|
||||
let Expr::UnaryOp(ast::ExprUnaryOp { op, .. })= operand else {
|
||||
return;
|
||||
};
|
||||
let Expr::UnaryOp(ast::ExprUnaryOp { op, .. }) = operand else {
|
||||
return;
|
||||
};
|
||||
if !matches!(op, UnaryOp::UAdd) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Str(s),
|
||||
..
|
||||
}) = &args[1] else
|
||||
{
|
||||
}) = &args[1]
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if s != "__call__" {
|
||||
|
||||
@@ -68,7 +68,13 @@ pub(crate) fn zip_without_explicit_strict(
|
||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||
/// `itertools.cycle` or similar).
|
||||
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall { func, args, keywords, .. }) = &arg else {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017.py:23:9: B017 `assertRaises(Exception)` should be considered evil
|
||||
B017.py:23:14: B017 `assertRaises(Exception)` should be considered evil
|
||||
|
|
||||
21 | class Foobar(unittest.TestCase):
|
||||
22 | def evil_raises(self) -> None:
|
||||
23 | with self.assertRaises(Exception):
|
||||
| _________^
|
||||
24 | | raise Exception("Evil I say!")
|
||||
| |__________________________________________^ B017
|
||||
25 |
|
||||
26 | def context_manager_raises(self) -> None:
|
||||
21 | class Foobar(unittest.TestCase):
|
||||
22 | def evil_raises(self) -> None:
|
||||
23 | with self.assertRaises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
24 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017.py:41:5: B017 `pytest.raises(Exception)` should be considered evil
|
||||
B017.py:27:14: B017 `assertRaises(BaseException)` should be considered evil
|
||||
|
|
||||
40 | def test_pytest_raises():
|
||||
41 | with pytest.raises(Exception):
|
||||
| _____^
|
||||
42 | | raise ValueError("Hello")
|
||||
| |_________________________________^ B017
|
||||
43 |
|
||||
44 | with pytest.raises(Exception, "hello"):
|
||||
26 | def also_evil_raises(self) -> None:
|
||||
27 | with self.assertRaises(BaseException):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
28 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017.py:45:10: B017 `pytest.raises(Exception)` should be considered evil
|
||||
|
|
||||
44 | def test_pytest_raises():
|
||||
45 | with pytest.raises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
46 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017.py:48:10: B017 `pytest.raises(Exception)` should be considered evil
|
||||
|
|
||||
46 | raise ValueError("Hello")
|
||||
47 |
|
||||
48 | with pytest.raises(Exception), pytest.raises(ValueError):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||
49 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ pub(crate) fn fix_unnecessary_generator_dict(
|
||||
// Extract the (k, v) from `(k, v) for ...`.
|
||||
let generator_exp = match_generator_exp(&arg.value)?;
|
||||
let tuple = match_tuple(&generator_exp.elt)?;
|
||||
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..] else {
|
||||
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..]
|
||||
else {
|
||||
bail!("Expected tuple to contain two elements");
|
||||
};
|
||||
|
||||
@@ -188,9 +189,10 @@ pub(crate) fn fix_unnecessary_list_comprehension_dict(
|
||||
|
||||
let tuple = match_tuple(&list_comp.elt)?;
|
||||
|
||||
let [Element::Simple {
|
||||
value: key, ..
|
||||
}, Element::Simple { value, .. }] = &tuple.elements[..] else { bail!("Expected tuple with two elements"); };
|
||||
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..]
|
||||
else {
|
||||
bail!("Expected tuple with two elements");
|
||||
};
|
||||
|
||||
tree = Expression::DictComp(Box::new(DictComp {
|
||||
key: Box::new(key.clone()),
|
||||
@@ -982,14 +984,10 @@ pub(crate) fn fix_unnecessary_map(
|
||||
}
|
||||
|
||||
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
||||
bail!(
|
||||
"Expected tuple to contain a key as the first element"
|
||||
);
|
||||
bail!("Expected tuple to contain a key as the first element");
|
||||
};
|
||||
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
||||
bail!(
|
||||
"Expected tuple to contain a key as the second element"
|
||||
);
|
||||
bail!("Expected tuple to contain a key as the second element");
|
||||
};
|
||||
|
||||
(key, value)
|
||||
@@ -1063,9 +1061,7 @@ pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||
let call = match_call_mut(&mut tree)?;
|
||||
|
||||
let Expression::ListComp(list_comp) = &call.args[0].value else {
|
||||
bail!(
|
||||
"Expected Expression::ListComp"
|
||||
);
|
||||
bail!("Expected Expression::ListComp");
|
||||
};
|
||||
|
||||
let mut new_empty_lines = vec![];
|
||||
|
||||
@@ -66,11 +66,13 @@ pub(crate) fn unnecessary_comprehension_any_all(
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Expr::Name(ast::ExprName { id, .. } )= func else {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
if (matches!(id.as_str(), "all" | "any")) && args.len() == 1 {
|
||||
let (Expr::ListComp(ast::ExprListComp { elt, .. } )| Expr::SetComp(ast::ExprSetComp { elt, .. })) = &args[0] else {
|
||||
let (Expr::ListComp(ast::ExprListComp { elt, .. })
|
||||
| Expr::SetComp(ast::ExprSetComp { elt, .. })) = &args[0]
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if contains_await(elt) {
|
||||
|
||||
@@ -84,7 +84,7 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||
let Some(arg) = args.first() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, ..} )= arg else {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = arg else {
|
||||
return;
|
||||
};
|
||||
let Some(inner) = helpers::expr_name(func) else {
|
||||
|
||||
@@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_dict(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Expr::GeneratorExp(ast::ExprGeneratorExp { elt, .. }) = argument {
|
||||
|
||||
@@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_list(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("list", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("list", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("list") {
|
||||
|
||||
@@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_set(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("set") {
|
||||
|
||||
@@ -47,7 +47,9 @@ pub(crate) fn unnecessary_list_comprehension_dict(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("dict") {
|
||||
|
||||
@@ -47,7 +47,9 @@ pub(crate) fn unnecessary_list_comprehension_set(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("set") {
|
||||
|
||||
@@ -54,7 +54,9 @@ pub(crate) fn unnecessary_literal_dict(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("dict") {
|
||||
|
||||
@@ -55,7 +55,9 @@ pub(crate) fn unnecessary_literal_set(
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
||||
let Some(argument) =
|
||||
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if !checker.semantic().is_builtin("set") {
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn unnecessary_map(
|
||||
)
|
||||
}
|
||||
|
||||
let Some(id) = helpers::expr_name(func) else {
|
||||
let Some(id) = helpers::expr_name(func) else {
|
||||
return;
|
||||
};
|
||||
match id {
|
||||
@@ -127,9 +127,11 @@ pub(crate) fn unnecessary_map(
|
||||
if args.len() != 2 {
|
||||
return;
|
||||
}
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else {
|
||||
return;
|
||||
};
|
||||
let Some(argument) =
|
||||
helpers::first_argument_with_matching_function("map", func, args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Expr::Lambda(_) = argument {
|
||||
let mut diagnostic = create_diagnostic(id, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
@@ -155,7 +157,9 @@ pub(crate) fn unnecessary_map(
|
||||
|
||||
if args.len() == 1 {
|
||||
if let Expr::Call(ast::ExprCall { func, args, .. }) = &args[0] {
|
||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else {
|
||||
let Some(argument) =
|
||||
helpers::first_argument_with_matching_function("map", func, args)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Expr::Lambda(ast::ExprLambda { body, .. }) = argument {
|
||||
|
||||
@@ -64,9 +64,15 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||
let Expr::Subscript(ast::ExprSubscript { slice, .. }) = first_arg else {
|
||||
return;
|
||||
};
|
||||
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Slice(ast::ExprSlice {
|
||||
lower,
|
||||
upper,
|
||||
step,
|
||||
range: _,
|
||||
}) = slice.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if lower.is_some() || upper.is_some() {
|
||||
return;
|
||||
}
|
||||
@@ -77,13 +83,15 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||
op: UnaryOp::USub,
|
||||
operand,
|
||||
range: _,
|
||||
}) = step.as_ref() else {
|
||||
}) = step.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(val),
|
||||
..
|
||||
}) = operand.as_ref() else {
|
||||
}) = operand.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if *val != BigInt::from(1) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user