Compare commits
88 Commits
v0.0.281
...
collect_de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f2979ed66 | ||
|
|
fe97a2a302 | ||
|
|
a48d16e025 | ||
|
|
8a5bc93fdd | ||
|
|
6da527170f | ||
|
|
8cddb6c08d | ||
|
|
b8ca220eeb | ||
|
|
1d8759d5df | ||
|
|
d3aa8b4ee0 | ||
|
|
9ae498595c | ||
|
|
5f225b18ab | ||
|
|
8276b26480 | ||
|
|
1705fcef36 | ||
|
|
51ff98f9e9 | ||
|
|
b3f3529499 | ||
|
|
2fa508793f | ||
|
|
718e3945e3 | ||
|
|
c75e8a8dab | ||
|
|
74e734e962 | ||
|
|
0e18abcf95 | ||
|
|
7c8bcede5b | ||
|
|
30c2e9430e | ||
|
|
b6f0316d55 | ||
|
|
9f3567dea6 | ||
|
|
9e2bbf4beb | ||
|
|
d7627c398c | ||
|
|
23e527e386 | ||
|
|
a15b0a9102 | ||
|
|
d40597a266 | ||
|
|
82410524d9 | ||
|
|
5b2e973fa5 | ||
|
|
1a60d1e3c6 | ||
|
|
9425ed72a0 | ||
|
|
9f38dbd06e | ||
|
|
7c5791fb77 | ||
|
|
c362ea7fd4 | ||
|
|
ec8fad5b02 | ||
|
|
bcc41ba062 | ||
|
|
556abf4bd3 | ||
|
|
23b8fc4366 | ||
|
|
fd40864924 | ||
|
|
041946fb64 | ||
|
|
8a0f844642 | ||
|
|
8c40886f87 | ||
|
|
4c53bfe896 | ||
|
|
b095b7204b | ||
|
|
981e64f82b | ||
|
|
0d62ad2480 | ||
|
|
b4f224ecea | ||
|
|
7842c82a0a | ||
|
|
9c708d8fc1 | ||
|
|
adc8bb7821 | ||
|
|
a82eb9544c | ||
|
|
5e41f2fc7d | ||
|
|
1df7e9831b | ||
|
|
2e1754e5fc | ||
|
|
67b88803d8 | ||
|
|
ed45fcb1f7 | ||
|
|
adf227b8a9 | ||
|
|
debfca3a11 | ||
|
|
83fe103d6e | ||
|
|
e08f873077 | ||
|
|
928ab63a64 | ||
|
|
b68f76f0d9 | ||
|
|
1a85953129 | ||
|
|
743118ae9a | ||
|
|
0753017cf1 | ||
|
|
29fb655e04 | ||
|
|
f45e8645d7 | ||
|
|
7c7231db2e | ||
|
|
4ad5903ef6 | ||
|
|
c6986ac95d | ||
|
|
ecfdd8d58b | ||
|
|
07468f8be9 | ||
|
|
ba990b676f | ||
|
|
44a8d1c644 | ||
|
|
88b984e885 | ||
|
|
bf584c6d74 | ||
|
|
6ea3c178fd | ||
|
|
764d35667f | ||
|
|
ff9ebbaa5f | ||
|
|
38b5726948 | ||
|
|
615337a54d | ||
|
|
6ee5cb37c0 | ||
|
|
0fddb31235 | ||
|
|
a7aa3caaae | ||
|
|
e52b636da0 | ||
|
|
9063f4524d |
1
.github/release.yml
vendored
1
.github/release.yml
vendored
@@ -4,6 +4,7 @@ changelog:
|
||||
labels:
|
||||
- internal
|
||||
- documentation
|
||||
- formatter
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
|
||||
14
.github/workflows/benchmark.yaml
vendored
14
.github/workflows/benchmark.yaml
vendored
@@ -3,12 +3,12 @@ name: Benchmark
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'rust-toolchain'
|
||||
- 'crates/**'
|
||||
- '!crates/ruff_dev'
|
||||
- '!crates/ruff_shrinking'
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "rust-toolchain"
|
||||
- "crates/**"
|
||||
- "!crates/ruff_dev"
|
||||
- "!crates/ruff_shrinking"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
name: "Run | ${{ matrix.os }}"
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
|
||||
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@@ -2,7 +2,7 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -16,7 +16,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.11" # to build abi3 wheels
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -42,6 +42,7 @@ jobs:
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/check_ecosystem.py
|
||||
|
||||
formatter:
|
||||
- Cargo.toml
|
||||
@@ -54,7 +55,7 @@ jobs:
|
||||
- crates/ruff_python_index/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
|
||||
- crates/ruff_dev/**
|
||||
|
||||
cargo-fmt:
|
||||
name: "cargo fmt"
|
||||
@@ -83,7 +84,7 @@ jobs:
|
||||
cargo-test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "cargo test | ${{ matrix.os }}"
|
||||
steps:
|
||||
@@ -235,7 +236,6 @@ jobs:
|
||||
- name: "Run cargo-udeps"
|
||||
run: cargo +nightly-2023-06-08 udeps
|
||||
|
||||
|
||||
python-package:
|
||||
name: "python package"
|
||||
runs-on: ubuntu-latest
|
||||
@@ -335,9 +335,9 @@ jobs:
|
||||
- name: "Cache rust"
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: "Formatter progress"
|
||||
run: scripts/formatter_progress.sh
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: grep "similarity index" target/progress_projects_report.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
|
||||
# CPython is not black formatted, so we run only the stability check
|
||||
- name: "Clone CPython 3.10"
|
||||
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
|
||||
|
||||
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -3,7 +3,7 @@ name: mkdocs
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
mkdocs:
|
||||
|
||||
6
.github/workflows/flake8-to-ruff.yaml
vendored
6
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x64, x86 ]
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ x86_64, i686 ]
|
||||
target: [x86_64, i686]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ aarch64, armv7, s390x, ppc64le, ppc64 ]
|
||||
target: [aarch64, armv7, s390x, ppc64le, ppc64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -3,7 +3,7 @@ name: "[Playground] Release"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ published ]
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
4
.github/workflows/pr-comment.yaml
vendored
4
.github/workflows/pr-comment.yaml
vendored
@@ -2,8 +2,8 @@ name: PR Check Comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [ CI, Benchmark ]
|
||||
types: [ completed ]
|
||||
workflows: [CI, Benchmark]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_run_id:
|
||||
|
||||
@@ -42,13 +42,13 @@ repos:
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --
|
||||
language: system
|
||||
types: [ rust ]
|
||||
types: [rust]
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
- id: ruff
|
||||
name: ruff
|
||||
entry: cargo run --bin ruff -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
|
||||
language: system
|
||||
types_or: [ python, pyi ]
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
exclude: |
|
||||
(?x)^(
|
||||
@@ -62,5 +62,12 @@ repos:
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.0.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
ci:
|
||||
skip: [ cargo-fmt, dev-generate-all ]
|
||||
skip: [cargo-fmt, dev-generate-all]
|
||||
|
||||
@@ -69,6 +69,13 @@ and pre-commit to run some validation checks:
|
||||
pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
|
||||
```
|
||||
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run Ruff locally from the repository root with:
|
||||
|
||||
63
Cargo.lock
generated
63
Cargo.lock
generated
@@ -133,6 +133,12 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "3.0.0"
|
||||
@@ -794,7 +800,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1027,6 +1033,7 @@ dependencies = [
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2035,7 +2042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2134,7 +2141,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.281"
|
||||
version = "0.0.282"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2194,7 +2201,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pretty_assertions",
|
||||
"rayon",
|
||||
@@ -2218,6 +2224,9 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2282,6 +2291,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3115,6 +3125,18 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-indicatif"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b38ed3722d27705c3bd7ca0ccf29acc3d8e1c717b4cd87f97891a2c1834ea1af"
|
||||
dependencies = [
|
||||
"indicatif",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
@@ -3313,6 +3335,39 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vt100"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"log",
|
||||
"unicode-width",
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
homepage = "https://beta.ruff.rs/docs"
|
||||
documentation = "https://beta.ruff.rs/docs"
|
||||
repository = "https://github.com/astral-sh/ruff"
|
||||
@@ -46,6 +46,9 @@ syn = { version = "2.0.15" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
wsl = { version = "0.1.0" }
|
||||
|
||||
# v1.0.1
|
||||
|
||||
@@ -140,7 +140,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.281
|
||||
rev: v0.0.282
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.282"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.282"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -67,7 +67,8 @@ cfg.getboolean("hello", True)
|
||||
os.set_blocking(0, False)
|
||||
g_action.set_enabled(True)
|
||||
settings.set_enable_developer_extras(True)
|
||||
|
||||
foo.is_(True)
|
||||
bar.is_not(False)
|
||||
|
||||
class Registry:
|
||||
def __init__(self) -> None:
|
||||
|
||||
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.py
vendored
Normal file
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import TypeVar, Self, Type
|
||||
|
||||
_S = TypeVar("_S", bound=BadClass)
|
||||
_S2 = TypeVar("_S2", BadClass, GoodClass)
|
||||
|
||||
class BadClass:
|
||||
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
class GoodClass:
|
||||
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
|
||||
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
|
||||
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
|
||||
@classmethod
|
||||
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
|
||||
@classmethod
|
||||
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
|
||||
@staticmethod
|
||||
def static_method(arg1: _S) -> _S: ...
|
||||
|
||||
|
||||
# Python > 3.12
|
||||
class PEP695BadDunderNew[T]:
|
||||
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
|
||||
|
||||
def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.pyi
vendored
Normal file
42
crates/ruff/resources/test/fixtures/flake8_pyi/PYI019.pyi
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import TypeVar, Self, Type
|
||||
|
||||
_S = TypeVar("_S", bound=BadClass)
|
||||
_S2 = TypeVar("_S2", BadClass, GoodClass)
|
||||
|
||||
class BadClass:
|
||||
def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
def bad_instance_method(self: _S, arg: bytes) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def bad_class_method(cls: type[_S], arg: int) -> _S: ... # PYI019
|
||||
|
||||
|
||||
@classmethod
|
||||
def excluded_edge_case(cls: Type[_S], arg: int) -> _S: ... # Ok
|
||||
|
||||
|
||||
class GoodClass:
|
||||
def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ...
|
||||
def good_instance_method_1(self: Self, arg: bytes) -> Self: ...
|
||||
def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ...
|
||||
@classmethod
|
||||
def good_cls_method_1(cls: type[Self], arg: int) -> Self: ...
|
||||
@classmethod
|
||||
def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ...
|
||||
@staticmethod
|
||||
def static_method(arg1: _S) -> _S: ...
|
||||
|
||||
|
||||
# Python > 3.12
|
||||
class PEP695BadDunderNew[T]:
|
||||
def __new__[S](cls: type[S], *args: Any, ** kwargs: Any) -> S: ... # PYI019
|
||||
|
||||
|
||||
def generic_instance_method[S](self: S) -> S: ... # PYI019
|
||||
|
||||
|
||||
class PEP695GoodDunderNew[T]:
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
|
||||
@@ -1,9 +1,11 @@
|
||||
import collections
|
||||
|
||||
person: collections.namedtuple # OK
|
||||
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
person: namedtuple # OK
|
||||
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
person = namedtuple("Person", ["name", "age"]) # OK
|
||||
person = namedtuple(
|
||||
"Person", ["name", "age"]
|
||||
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
import typing
|
||||
import typing_extensions
|
||||
from typing import Literal
|
||||
# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi.
|
||||
# Note that this rule could be applied here in the future.
|
||||
|
||||
# Shouldn't affect non-union field types.
|
||||
field1: Literal[1] # OK
|
||||
field2: Literal[1] | Literal[2] # OK
|
||||
|
||||
def func1(arg1: Literal[1] | Literal[2]): # OK
|
||||
# Should emit for duplicate field types.
|
||||
field2: Literal[1] | Literal[2] # Error
|
||||
|
||||
# Should emit for union types in arguments.
|
||||
def func1(arg1: Literal[1] | Literal[2]): # Error
|
||||
print(arg1)
|
||||
|
||||
|
||||
def func2() -> Literal[1] | Literal[2]: # OK
|
||||
# Should emit for unions in return types.
|
||||
def func2() -> Literal[1] | Literal[2]: # Error
|
||||
return "my Literal[1]ing"
|
||||
|
||||
|
||||
field3: Literal[1] | Literal[2] | str # OK
|
||||
field4: str | Literal[1] | Literal[2] # OK
|
||||
field5: Literal[1] | str | Literal[2] # OK
|
||||
field6: Literal[1] | bool | Literal[2] | str # OK
|
||||
field7 = Literal[1] | Literal[2] # OK
|
||||
field8: Literal[1] | (Literal[2] | str) # OK
|
||||
field9: Literal[1] | (Literal[2] | str) # OK
|
||||
field10: (Literal[1] | str) | Literal[2] # OK
|
||||
field11: dict[Literal[1] | Literal[2], str] # OK
|
||||
# Should emit in longer unions, even if not directly adjacent.
|
||||
field3: Literal[1] | Literal[2] | str # Error
|
||||
field4: str | Literal[1] | Literal[2] # Error
|
||||
field5: Literal[1] | str | Literal[2] # Error
|
||||
field6: Literal[1] | bool | Literal[2] | str # Error
|
||||
|
||||
# Should emit for non-type unions.
|
||||
field7 = Literal[1] | Literal[2] # Error
|
||||
|
||||
# Should emit for parenthesized unions.
|
||||
field8: Literal[1] | (Literal[2] | str) # Error
|
||||
|
||||
# Should handle user parentheses when fixing.
|
||||
field9: Literal[1] | (Literal[2] | str) # Error
|
||||
field10: (Literal[1] | str) | Literal[2] # Error
|
||||
|
||||
# Should emit for union in generic parent type.
|
||||
field11: dict[Literal[1] | Literal[2], str] # Error
|
||||
|
||||
@@ -3,8 +3,8 @@ import typing
|
||||
|
||||
|
||||
class Bad:
|
||||
def __eq__(self, other: Any) -> bool: ... # Fine because not a stub file
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Fine because not a stub file
|
||||
def __eq__(self, other: Any) -> bool: ... # Y032
|
||||
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
|
||||
|
||||
|
||||
class Good:
|
||||
|
||||
@@ -9,16 +9,16 @@ from typing import (
|
||||
|
||||
just_literals_pipe_union: TypeAlias = (
|
||||
Literal[True] | Literal["idk"]
|
||||
) # not PYI042 (not a stubfile)
|
||||
) # PYI042, since not camel case
|
||||
PublicAliasT: TypeAlias = str | int
|
||||
PublicAliasT2: TypeAlias = Union[str, bytes]
|
||||
_ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any
|
||||
_PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"]
|
||||
_PrivateAliasS2: TypeAlias = Annotated[str, "also okay"]
|
||||
|
||||
snake_case_alias1: TypeAlias = str | int # not PYI042 (not a stubfile)
|
||||
_snake_case_alias2: TypeAlias = Literal["whatever"] # not PYI042 (not a stubfile)
|
||||
Snake_case_alias: TypeAlias = int | float # not PYI042 (not a stubfile)
|
||||
snake_case_alias1: TypeAlias = str | int # PYI042, since not camel case
|
||||
_snake_case_alias2: TypeAlias = Literal["whatever"] # PYI042, since not camel case
|
||||
Snake_case_alias: TypeAlias = int | float # PYI042, since not camel case
|
||||
|
||||
# check that this edge case doesn't crash
|
||||
_: TypeAlias = str | int
|
||||
|
||||
@@ -7,11 +7,11 @@ from typing import (
|
||||
Literal,
|
||||
)
|
||||
|
||||
_PrivateAliasT: TypeAlias = str | int # not PYI043 (not a stubfile)
|
||||
_PrivateAliasT2: TypeAlias = typing.Any # not PYI043 (not a stubfile)
|
||||
_PrivateAliasT: TypeAlias = str | int # PYI043, since this ends in a T
|
||||
_PrivateAliasT2: TypeAlias = typing.Any # PYI043, since this ends in a T
|
||||
_PrivateAliasT3: TypeAlias = Literal[
|
||||
"not", "a", "chance"
|
||||
] # not PYI043 (not a stubfile)
|
||||
] # PYI043, since this ends in a T
|
||||
just_literals_pipe_union: TypeAlias = Literal[True] | Literal["idk"]
|
||||
PublicAliasT: TypeAlias = str | int
|
||||
PublicAliasT2: TypeAlias = Union[str, bytes]
|
||||
|
||||
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.py
vendored
Normal file
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import typing
|
||||
from typing import Literal, TypeAlias, Union
|
||||
|
||||
A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
# OK
|
||||
A: Literal["foo"]
|
||||
B: TypeAlias = Literal[b"bar", b"foo"]
|
||||
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
|
||||
D: TypeAlias = Literal[b"str_bytes", 42]
|
||||
|
||||
def func(x: Literal[1J], y: Literal[3.14]): ...
|
||||
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.pyi
vendored
Normal file
17
crates/ruff/resources/test/fixtures/flake8_pyi/PYI051.pyi
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import typing
|
||||
from typing import Literal, TypeAlias, Union
|
||||
|
||||
A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
# OK
|
||||
A: Literal["foo"]
|
||||
B: TypeAlias = Literal[b"bar", b"foo"]
|
||||
C: TypeAlias = typing.Union[Literal[5], Literal["foo"]]
|
||||
D: TypeAlias = Literal[b"str_bytes", 42]
|
||||
|
||||
def func(x: Literal[1J], y: Literal[3.14]): ...
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.py
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
# OK
|
||||
x: type[int, str, float]
|
||||
y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.pyi
vendored
Normal file
20
crates/ruff/resources/test/fixtures/flake8_pyi/PYI055.pyi
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
# OK
|
||||
x: type[int, str, float]
|
||||
y: builtins.type[int, str, complex]
|
||||
z: Union[float, complex]
|
||||
|
||||
|
||||
def func(arg: type[int, float] | str) -> None: ...
|
||||
10
crates/ruff/resources/test/fixtures/flake8_type_checking/snapshot.py
vendored
Normal file
10
crates/ruff/resources/test/fixtures/flake8_type_checking/snapshot.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Regression test: ensure that we don't treat the export entry as a typing-only reference."""
|
||||
from __future__ import annotations
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
__all__ = ("getLogger",)
|
||||
|
||||
|
||||
def foo() -> None:
|
||||
pass
|
||||
@@ -18,3 +18,5 @@ file_name.split(os.sep)
|
||||
|
||||
# OK
|
||||
"foo/bar/".split("/")
|
||||
"foo/bar/".split(os.sep, 1)
|
||||
"foo/bar/".split(1, sep=os.sep)
|
||||
|
||||
@@ -45,3 +45,18 @@ def f():
|
||||
for i in items:
|
||||
if i not in result:
|
||||
result.append(i) # OK
|
||||
|
||||
|
||||
def f():
|
||||
fibonacci = [0, 1]
|
||||
for i in range(20):
|
||||
fibonacci.append(sum(fibonacci[-2:])) # OK
|
||||
print(fibonacci)
|
||||
|
||||
|
||||
def f():
|
||||
foo = object()
|
||||
foo.fibonacci = [0, 1]
|
||||
for i in range(20):
|
||||
foo.fibonacci.append(sum(foo.fibonacci[-2:])) # OK
|
||||
print(foo.fibonacci)
|
||||
|
||||
@@ -66,3 +66,6 @@ while 1:
|
||||
#: E703:2:1
|
||||
0\
|
||||
;
|
||||
#: E701:2:3
|
||||
a = \
|
||||
5;
|
||||
|
||||
@@ -58,3 +58,6 @@ assert type(res) == type(None)
|
||||
types = StrEnum
|
||||
if x == types.X:
|
||||
pass
|
||||
|
||||
#: E721
|
||||
assert type(res) is int
|
||||
|
||||
@@ -147,3 +147,10 @@ def f() -> None:
|
||||
global CONSTANT
|
||||
CONSTANT = 1
|
||||
CONSTANT = 2
|
||||
|
||||
|
||||
def f() -> None:
|
||||
try:
|
||||
print("hello")
|
||||
except A as e :
|
||||
print("oh no!")
|
||||
|
||||
29
crates/ruff/resources/test/fixtures/pylint/bad_string_format_character.py
vendored
Normal file
29
crates/ruff/resources/test/fixtures/pylint/bad_string_format_character.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# pylint: disable=missing-docstring,consider-using-f-string, pointless-statement
|
||||
|
||||
## Old style formatting
|
||||
|
||||
"%s %z" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"%s" "%z" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"""%s %z""" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
"""%s""" """%z""" % ("hello", "world") # [bad-format-character]
|
||||
|
||||
## New style formatting
|
||||
|
||||
"{:s} {:y}".format("hello", "world") # [bad-format-character]
|
||||
|
||||
"{:*^30s}".format("centered")
|
||||
|
||||
## f-strings
|
||||
|
||||
H, W = "hello", "world"
|
||||
f"{H} {W}"
|
||||
f"{H:s} {W:z}" # [bad-format-character]
|
||||
|
||||
f"{1:z}" # [bad-format-character]
|
||||
|
||||
## False negatives
|
||||
|
||||
print(("%" "z") % 1)
|
||||
@@ -40,3 +40,4 @@ __all__ = __all__ + ["Hello"]
|
||||
|
||||
__all__ = __all__ + multiprocessing.__all__
|
||||
|
||||
__all__ = list[str](["Hello", "world"])
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
|
||||
"{1} {0}".format(a, b)
|
||||
|
||||
"{0} {1} {0}".format(a, b)
|
||||
|
||||
"{x.y}".format(x=z)
|
||||
|
||||
"{x} {y} {x}".format(x=a, y=b)
|
||||
|
||||
"{.x} {.y}".format(a, b)
|
||||
|
||||
"{} {}".format(a.b, c.d)
|
||||
@@ -72,6 +76,58 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
111111
|
||||
)
|
||||
|
||||
"{a}" "{b}".format(a=1, b=1)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
"{b}"
|
||||
).format(a=1, b=1)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
""
|
||||
"{b}"
|
||||
""
|
||||
).format(a=1, b=1)
|
||||
|
||||
(
|
||||
(
|
||||
# comment
|
||||
"{a}"
|
||||
# comment
|
||||
"{b}"
|
||||
)
|
||||
# comment
|
||||
.format(a=1, b=1)
|
||||
)
|
||||
|
||||
(
|
||||
"{a}"
|
||||
"b"
|
||||
).format(a=1)
|
||||
|
||||
|
||||
def d(osname, version, release):
|
||||
return"{}-{}.{}".format(osname, version, release)
|
||||
|
||||
|
||||
def e():
|
||||
yield"{}".format(1)
|
||||
|
||||
|
||||
assert"{}".format(1)
|
||||
|
||||
|
||||
async def c():
|
||||
return "{}".format(await 3)
|
||||
|
||||
|
||||
async def c():
|
||||
return "{}".format(1 + await 3)
|
||||
|
||||
|
||||
"{}".format(1 * 2)
|
||||
|
||||
###
|
||||
# Non-errors
|
||||
###
|
||||
@@ -85,8 +141,6 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
|
||||
"{} {}".format(*a)
|
||||
|
||||
"{0} {0}".format(arg)
|
||||
|
||||
"{x} {x}".format(arg)
|
||||
|
||||
"{x.y} {x.z}".format(arg)
|
||||
@@ -103,8 +157,6 @@ b"{} {}".format(a, b)
|
||||
|
||||
r'"\N{snowman} {}".format(a)'
|
||||
|
||||
"{a}" "{b}".format(a=1, b=1)
|
||||
|
||||
"123456789 {}".format(
|
||||
11111111111111111111111111111111111111111111111111111111111111111111111111,
|
||||
)
|
||||
@@ -140,20 +192,9 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
]
|
||||
)
|
||||
|
||||
async def c():
|
||||
return "{}".format(await 3)
|
||||
(
|
||||
"{a}"
|
||||
"{1 + 2}"
|
||||
).format(a=1)
|
||||
|
||||
|
||||
async def c():
|
||||
return "{}".format(1 + await 3)
|
||||
|
||||
|
||||
def d(osname, version, release):
|
||||
return"{}-{}.{}".format(osname, version, release)
|
||||
|
||||
|
||||
def e():
|
||||
yield"{}".format(1)
|
||||
|
||||
|
||||
assert"{}".format(1)
|
||||
"{}".format(**c)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"""A mirror of UP037_1.py, with `from __future__ import annotations`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
@@ -1,108 +0,0 @@
|
||||
"""A mirror of UP037_0.py, without `from __future__ import annotations`."""
|
||||
|
||||
from typing import (
|
||||
Annotated,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
TypedDict,
|
||||
cast,
|
||||
)
|
||||
|
||||
from mypy_extensions import Arg, DefaultArg, DefaultNamedArg, NamedArg, VarArg
|
||||
|
||||
|
||||
def foo(var: "MyClass") -> "MyClass":
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
def foo(*, inplace: "bool"):
|
||||
pass
|
||||
|
||||
|
||||
def foo(*args: "str", **kwargs: "int"):
|
||||
pass
|
||||
|
||||
|
||||
x: Tuple["MyClass"]
|
||||
|
||||
x: Callable[["MyClass"], None]
|
||||
|
||||
|
||||
class Foo(NamedTuple):
|
||||
x: "MyClass"
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", foo="int", total=False)
|
||||
|
||||
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E", {"foo": "int"})
|
||||
|
||||
|
||||
x: Annotated["str", "metadata"]
|
||||
|
||||
x: Arg("str", "name")
|
||||
|
||||
x: DefaultArg("str", "name")
|
||||
|
||||
x: NamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", "name")
|
||||
|
||||
x: DefaultNamedArg("str", name="name")
|
||||
|
||||
x: VarArg("str")
|
||||
|
||||
x: List[List[List["MyClass"]]]
|
||||
|
||||
x: NamedTuple("X", [("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple("X", fields=[("foo", "int"), ("bar", "str")])
|
||||
|
||||
x: NamedTuple(typename="X", fields=[("foo", "int")])
|
||||
|
||||
X: MyCallable("X")
|
||||
|
||||
|
||||
# OK
|
||||
class D(TypedDict):
|
||||
E: TypedDict("E")
|
||||
|
||||
|
||||
x: Annotated[()]
|
||||
|
||||
x: DefaultNamedArg(name="name", quox="str")
|
||||
|
||||
x: DefaultNamedArg(name="name")
|
||||
|
||||
x: NamedTuple("X", [("foo",), ("bar",)])
|
||||
|
||||
x: NamedTuple("X", ["foo", "bar"])
|
||||
|
||||
x: NamedTuple()
|
||||
|
||||
x: Literal["foo", "bar"]
|
||||
|
||||
x = cast(x, "str")
|
||||
|
||||
|
||||
def foo(x, *args, **kwargs):
|
||||
...
|
||||
|
||||
|
||||
def foo(*, inplace):
|
||||
...
|
||||
|
||||
|
||||
x: Annotated[1:2] = ...
|
||||
|
||||
x = TypeVar("x", "str", "int")
|
||||
|
||||
x = cast("str", x)
|
||||
|
||||
X = List["MyClass"]
|
||||
16
crates/ruff/resources/test/fixtures/pyupgrade/UP040.py
vendored
Normal file
16
crates/ruff/resources/test/fixtures/pyupgrade/UP040.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import typing
|
||||
from typing import TypeAlias
|
||||
|
||||
# UP040
|
||||
x: typing.TypeAlias = int
|
||||
x: TypeAlias = int
|
||||
|
||||
|
||||
# UP040 with generics (todo)
|
||||
T = typing.TypeVar["T"]
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
# OK
|
||||
x: TypeAlias
|
||||
x: int = 1
|
||||
@@ -11,6 +11,8 @@ class A:
|
||||
without_annotation = []
|
||||
class_variable: ClassVar[list[int]] = []
|
||||
final_variable: Final[list[int]] = []
|
||||
class_variable_without_subscript: ClassVar = []
|
||||
final_variable_without_subscript: Final = []
|
||||
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
//! Interface for generating autofix edits from higher-level actions (e.g., "remove an argument").
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use ruff_python_ast::{self as ast, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::{lexer, Mode};
|
||||
use ruff_python_trivia::{has_leading_content, is_python_whitespace, PythonWhitespace};
|
||||
use ruff_source_file::{Locator, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::autofix::codemods;
|
||||
|
||||
@@ -68,105 +69,101 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Parentheses {
|
||||
/// Remove parentheses, if the removed argument is the only argument left.
|
||||
Remove,
|
||||
/// Preserve parentheses, even if the removed argument is the only argument
|
||||
Preserve,
|
||||
}
|
||||
|
||||
/// Generic function to remove arguments or keyword arguments in function
|
||||
/// calls and class definitions. (For classes `args` should be considered
|
||||
/// `bases`)
|
||||
///
|
||||
/// Supports the removal of parentheses when this is the only (kw)arg left.
|
||||
/// For this behavior, set `remove_parentheses` to `true`.
|
||||
pub(crate) fn remove_argument(
|
||||
pub(crate) fn remove_argument<T: Ranged>(
|
||||
argument: &T,
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
locator: &Locator,
|
||||
call_at: TextSize,
|
||||
expr_range: TextRange,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
remove_parentheses: bool,
|
||||
) -> Result<Edit> {
|
||||
// TODO(sbrugman): Preserve trailing comments.
|
||||
let contents = locator.after(call_at);
|
||||
if arguments.keywords.len() + arguments.args.len() > 1 {
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let mut fix_start = None;
|
||||
let mut fix_end = None;
|
||||
|
||||
let n_arguments = keywords.len() + args.len();
|
||||
if n_arguments == 0 {
|
||||
bail!("No arguments or keywords to remove");
|
||||
}
|
||||
|
||||
if n_arguments == 1 {
|
||||
// Case 1: there is only one argument.
|
||||
let mut count = 0u32;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if tok.is_lpar() {
|
||||
if count == 0 {
|
||||
fix_start = Some(if remove_parentheses {
|
||||
range.start()
|
||||
} else {
|
||||
range.start() + TextSize::from(1)
|
||||
});
|
||||
}
|
||||
count = count.saturating_add(1);
|
||||
}
|
||||
|
||||
if tok.is_rpar() {
|
||||
count = count.saturating_sub(1);
|
||||
if count == 0 {
|
||||
fix_end = Some(if remove_parentheses {
|
||||
if arguments
|
||||
.args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(arguments.keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > argument.start())
|
||||
{
|
||||
// Case 1: argument or keyword is _not_ the last node, so delete from the start of the
|
||||
// argument to the end of the subsequent comma.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if seen_comma {
|
||||
if tok.is_non_logical_newline() {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if tok.is_newline() {
|
||||
range.end()
|
||||
} else {
|
||||
range.end() - TextSize::from(1)
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == argument.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && tok.is_comma() {
|
||||
seen_comma = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 2: argument or keyword is the last node, so delete from the start of the
|
||||
// previous comma to the end of the argument.
|
||||
for (tok, range) in lexer::lex_starts_at(
|
||||
locator.slice(arguments.range()),
|
||||
Mode::Module,
|
||||
arguments.start(),
|
||||
)
|
||||
.flatten()
|
||||
{
|
||||
if range.start() == argument.start() {
|
||||
fix_end = Some(argument.end());
|
||||
break;
|
||||
}
|
||||
if tok.is_comma() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if args
|
||||
.iter()
|
||||
.map(Expr::start)
|
||||
.chain(keywords.iter().map(Keyword::start))
|
||||
.any(|location| location > expr_range.start())
|
||||
{
|
||||
// Case 2: argument or keyword is _not_ the last node.
|
||||
let mut seen_comma = false;
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if seen_comma {
|
||||
if tok.is_non_logical_newline() {
|
||||
// Also delete any non-logical newlines after the comma.
|
||||
continue;
|
||||
}
|
||||
fix_end = Some(if tok.is_newline() {
|
||||
range.end()
|
||||
} else {
|
||||
range.start()
|
||||
});
|
||||
break;
|
||||
}
|
||||
if range.start() == expr_range.start() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
if fix_start.is_some() && tok.is_comma() {
|
||||
seen_comma = true;
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case 3: argument or keyword is the last node, so we have to find the last
|
||||
// comma in the stmt.
|
||||
for (tok, range) in lexer::lex_starts_at(contents, Mode::Module, call_at).flatten() {
|
||||
if range.start() == expr_range.start() {
|
||||
fix_end = Some(expr_range.end());
|
||||
break;
|
||||
// Only one argument; remove it (but preserve parentheses, if needed).
|
||||
Ok(match parentheses {
|
||||
Parentheses::Remove => Edit::deletion(arguments.start(), arguments.end()),
|
||||
Parentheses::Preserve => {
|
||||
Edit::replacement("()".to_string(), arguments.start(), arguments.end())
|
||||
}
|
||||
if tok.is_comma() {
|
||||
fix_start = Some(range.start());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (fix_start, fix_end) {
|
||||
(Some(start), Some(end)) => Ok(Edit::deletion(start, end)),
|
||||
_ => {
|
||||
bail!("No fix could be constructed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,24 +291,24 @@ fn next_stmt_break(semicolon: TextSize, locator: &Locator) -> TextSize {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_python_ast::{Ranged, Suite};
|
||||
use ruff_python_parser::Parse;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::autofix::edits::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
let contents = "x = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(trailing_semicolon(stmt.end(), &locator), None);
|
||||
|
||||
let contents = "x = 1; y = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
@@ -320,7 +317,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let contents = "x = 1 ; y = 1";
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
@@ -333,7 +330,7 @@ x = 1 \
|
||||
; y = 1
|
||||
"
|
||||
.trim();
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let stmt = program.first().unwrap();
|
||||
let locator = Locator::new(contents);
|
||||
assert_eq!(
|
||||
|
||||
@@ -76,7 +76,7 @@ fn apply_fixes<'a>(
|
||||
}
|
||||
|
||||
// If this fix overlaps with a fix we've already applied, skip it.
|
||||
if last_pos.map_or(false, |last_pos| last_pos >= first.start()) {
|
||||
if last_pos.is_some_and(|last_pos| last_pos >= first.start()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +56,11 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
if checker.enabled(Rule::UnaliasedCollectionsAbcSetImport) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pyi::rules::unaliased_collections_abc_set_import(checker, binding)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,19 +218,17 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
|
||||
@@ -30,8 +30,8 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::MissingTypeKwargs,
|
||||
Rule::MissingTypeSelf,
|
||||
]);
|
||||
let enforce_stubs = checker.is_stub
|
||||
&& checker.any_enabled(&[Rule::DocstringInStub, Rule::IterMethodReturnIterable]);
|
||||
let enforce_stubs = checker.is_stub && checker.enabled(Rule::DocstringInStub);
|
||||
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
|
||||
let enforce_docstrings = checker.any_enabled(&[
|
||||
Rule::BlankLineAfterLastSection,
|
||||
Rule::BlankLineAfterSummary,
|
||||
@@ -81,7 +81,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::UndocumentedPublicPackage,
|
||||
]);
|
||||
|
||||
if !enforce_annotations && !enforce_docstrings && !enforce_stubs {
|
||||
if !enforce_annotations && !enforce_docstrings && !enforce_stubs && !enforce_stubs_and_runtime {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
// interfaces, without any AST nodes in between. Right now, we
|
||||
// only error when traversing definition boundaries (functions,
|
||||
// classes, etc.).
|
||||
if !overloaded_name.map_or(false, |overloaded_name| {
|
||||
if !overloaded_name.is_some_and(|overloaded_name| {
|
||||
flake8_annotations::helpers::is_overload_impl(
|
||||
definition,
|
||||
&overloaded_name,
|
||||
@@ -141,6 +141,8 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DocstringInStub) {
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, docstring);
|
||||
}
|
||||
}
|
||||
if enforce_stubs_and_runtime {
|
||||
if checker.enabled(Rule::IterMethodReturnIterable) {
|
||||
flake8_pyi::rules::iter_method_return_iterable(checker, definition);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ExprContext, Operator, Ranged};
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, ExprContext, Operator, Ranged};
|
||||
use ruff_python_literal::cformat::{CFormatError, CFormatErrorType};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
@@ -74,9 +74,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
|
||||
// Ex) Union[...]
|
||||
if checker.any_enabled(&[Rule::UnnecessaryLiteralUnion, Rule::DuplicateUnionMember]) {
|
||||
// Determine if the current expression is an union
|
||||
// Avoid duplicate checks if the parent is an `Union[...]` since these rules traverse nested unions
|
||||
if checker.any_enabled(&[
|
||||
Rule::UnnecessaryLiteralUnion,
|
||||
Rule::DuplicateUnionMember,
|
||||
Rule::RedundantLiteralUnion,
|
||||
Rule::UnnecessaryTypeUnion,
|
||||
]) {
|
||||
// Avoid duplicate checks if the parent is an `Union[...]` since these rules
|
||||
// traverse nested unions.
|
||||
let is_unchecked_union = checker
|
||||
.semantic
|
||||
.expr_grandparent()
|
||||
@@ -92,6 +97,12 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DuplicateUnionMember) {
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,10 +163,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NumpyDeprecatedFunction) {
|
||||
numpy::rules::deprecated_function(checker, expr);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
|
||||
// Ex) List[...]
|
||||
@@ -174,9 +183,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
&& checker.semantic.in_annotation()
|
||||
&& !checker.settings.pyupgrade.keep_runtime_typing
|
||||
{
|
||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||
checker, expr,
|
||||
);
|
||||
flake8_future_annotations::rules::future_rewritable_type_annotation(checker, expr);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP585Annotation) {
|
||||
@@ -201,14 +208,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
|
||||
if checker.semantic.scope().kind.is_any_function() {
|
||||
// Ignore globals.
|
||||
if !checker
|
||||
.semantic
|
||||
.scope()
|
||||
.get(id)
|
||||
.map_or(false, |binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
})
|
||||
{
|
||||
if !checker.semantic.scope().get(id).is_some_and(|binding_id| {
|
||||
checker.semantic.binding(binding_id).is_global()
|
||||
}) {
|
||||
pep8_naming::rules::non_lowercase_variable_in_function(
|
||||
checker, expr, id,
|
||||
);
|
||||
@@ -216,11 +218,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
|
||||
if let ScopeKind::Class(ast::StmtClassDef { bases, .. }) =
|
||||
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
|
||||
&checker.semantic.scope().kind
|
||||
{
|
||||
pep8_naming::rules::mixed_case_variable_in_class_scope(
|
||||
checker, expr, id, bases,
|
||||
checker,
|
||||
expr,
|
||||
id,
|
||||
arguments.as_deref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -318,18 +323,20 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::PrivateMemberAccess) {
|
||||
flake8_self::rules::private_member_access(checker, expr);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attr, value, expr);
|
||||
}
|
||||
Expr::Call(
|
||||
call @ ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
@@ -382,9 +389,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
.enabled(Rule::StringDotFormatExtraPositionalArguments)
|
||||
{
|
||||
pyflakes::rules::string_dot_format_extra_positional_arguments(
|
||||
checker,
|
||||
&summary, args, location,
|
||||
);
|
||||
checker, &summary, args, location,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::StringDotFormatMissingArguments) {
|
||||
pyflakes::rules::string_dot_format_missing_argument(
|
||||
@@ -410,6 +416,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::call(
|
||||
checker,
|
||||
val.as_str(),
|
||||
location,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,10 +438,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyupgrade::rules::super_call_with_parameters(checker, expr, func, args);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryEncodeUTF8) {
|
||||
pyupgrade::rules::unnecessary_encode_utf8(checker, expr, func, args, keywords);
|
||||
pyupgrade::rules::unnecessary_encode_utf8(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantOpenModes) {
|
||||
pyupgrade::rules::redundant_open_modes(checker, expr);
|
||||
pyupgrade::rules::redundant_open_modes(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::NativeLiterals) {
|
||||
pyupgrade::rules::native_literals(checker, expr, func, args, keywords);
|
||||
@@ -436,10 +450,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyupgrade::rules::open_alias(checker, expr, func);
|
||||
}
|
||||
if checker.enabled(Rule::ReplaceUniversalNewlines) {
|
||||
pyupgrade::rules::replace_universal_newlines(checker, func, keywords);
|
||||
pyupgrade::rules::replace_universal_newlines(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ReplaceStdoutStderr) {
|
||||
pyupgrade::rules::replace_stdout_stderr(checker, expr, func, args, keywords);
|
||||
pyupgrade::rules::replace_stdout_stderr(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::OSErrorAlias) {
|
||||
pyupgrade::rules::os_error_alias_call(checker, func);
|
||||
@@ -459,7 +473,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_async::rules::blocking_os_call(checker, expr);
|
||||
}
|
||||
if checker.any_enabled(&[Rule::Print, Rule::PPrint]) {
|
||||
flake8_print::rules::print_call(checker, func, keywords);
|
||||
flake8_print::rules::print_call(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::SuspiciousPickleUsage,
|
||||
@@ -511,13 +525,11 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
if checker.enabled(Rule::ZipWithoutExplicitStrict) {
|
||||
if checker.settings.target_version >= PythonVersion::Py310 {
|
||||
flake8_bugbear::rules::zip_without_explicit_strict(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
flake8_bugbear::rules::zip_without_explicit_strict(checker, call);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::NoExplicitStacklevel) {
|
||||
flake8_bugbear::rules::no_explicit_stacklevel(checker, func, keywords);
|
||||
flake8_bugbear::rules::no_explicit_stacklevel(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryDictKwargs) {
|
||||
flake8_pie::rules::unnecessary_dict_kwargs(checker, expr, keywords);
|
||||
@@ -526,22 +538,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_bandit::rules::exec_used(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::BadFilePermissions) {
|
||||
flake8_bandit::rules::bad_file_permissions(checker, func, args, keywords);
|
||||
flake8_bandit::rules::bad_file_permissions(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::RequestWithNoCertValidation) {
|
||||
flake8_bandit::rules::request_with_no_cert_validation(checker, func, keywords);
|
||||
flake8_bandit::rules::request_with_no_cert_validation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnsafeYAMLLoad) {
|
||||
flake8_bandit::rules::unsafe_yaml_load(checker, func, args, keywords);
|
||||
flake8_bandit::rules::unsafe_yaml_load(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::SnmpInsecureVersion) {
|
||||
flake8_bandit::rules::snmp_insecure_version(checker, func, keywords);
|
||||
flake8_bandit::rules::snmp_insecure_version(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::SnmpWeakCryptography) {
|
||||
flake8_bandit::rules::snmp_weak_cryptography(checker, func, args, keywords);
|
||||
flake8_bandit::rules::snmp_weak_cryptography(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::Jinja2AutoescapeFalse) {
|
||||
flake8_bandit::rules::jinja2_autoescape_false(checker, func, keywords);
|
||||
flake8_bandit::rules::jinja2_autoescape_false(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedPasswordFuncArg) {
|
||||
flake8_bandit::rules::hardcoded_password_func_arg(checker, keywords);
|
||||
@@ -550,18 +562,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_bandit::rules::hardcoded_sql_expression(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::HashlibInsecureHashFunction) {
|
||||
flake8_bandit::rules::hashlib_insecure_hash_functions(
|
||||
checker, func, args, keywords,
|
||||
);
|
||||
flake8_bandit::rules::hashlib_insecure_hash_functions(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::RequestWithoutTimeout) {
|
||||
flake8_bandit::rules::request_without_timeout(checker, func, keywords);
|
||||
flake8_bandit::rules::request_without_timeout(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::ParamikoCall) {
|
||||
flake8_bandit::rules::paramiko_call(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::LoggingConfigInsecureListen) {
|
||||
flake8_bandit::rules::logging_config_insecure_listen(checker, func, keywords);
|
||||
flake8_bandit::rules::logging_config_insecure_listen(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::SubprocessWithoutShellEqualsTrue,
|
||||
@@ -572,7 +582,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::StartProcessWithPartialPath,
|
||||
Rule::UnixCommandWildcardInjection,
|
||||
]) {
|
||||
flake8_bandit::rules::shell_injection(checker, func, args, keywords);
|
||||
flake8_bandit::rules::shell_injection(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryGeneratorList) {
|
||||
flake8_comprehensions::rules::unnecessary_generator_list(
|
||||
@@ -675,23 +685,17 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_debugger::rules::debugger_call(checker, expr, func);
|
||||
}
|
||||
if checker.enabled(Rule::PandasUseOfInplaceArgument) {
|
||||
pandas_vet::rules::inplace_argument(checker, expr, func, args, keywords);
|
||||
pandas_vet::rules::inplace_argument(checker, call);
|
||||
}
|
||||
pandas_vet::rules::call(checker, func);
|
||||
if checker.enabled(Rule::PandasUseOfDotReadTable) {
|
||||
pandas_vet::rules::use_of_read_table(checker, func, keywords);
|
||||
pandas_vet::rules::use_of_read_table(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PandasUseOfPdMerge) {
|
||||
pandas_vet::rules::use_of_pd_merge(checker, func);
|
||||
}
|
||||
if checker.enabled(Rule::CallDatetimeWithoutTzinfo) {
|
||||
flake8_datetimez::rules::call_datetime_without_tzinfo(
|
||||
checker,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
expr.range(),
|
||||
);
|
||||
flake8_datetimez::rules::call_datetime_without_tzinfo(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::CallDatetimeToday) {
|
||||
flake8_datetimez::rules::call_datetime_today(checker, func, expr.range());
|
||||
@@ -707,30 +711,13 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::CallDatetimeNowWithoutTzinfo) {
|
||||
flake8_datetimez::rules::call_datetime_now_without_tzinfo(
|
||||
checker,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
expr.range(),
|
||||
);
|
||||
flake8_datetimez::rules::call_datetime_now_without_tzinfo(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::CallDatetimeFromtimestamp) {
|
||||
flake8_datetimez::rules::call_datetime_fromtimestamp(
|
||||
checker,
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
expr.range(),
|
||||
);
|
||||
flake8_datetimez::rules::call_datetime_fromtimestamp(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::CallDatetimeStrptimeWithoutZone) {
|
||||
flake8_datetimez::rules::call_datetime_strptime_without_zone(
|
||||
checker,
|
||||
func,
|
||||
args,
|
||||
expr.range(),
|
||||
);
|
||||
flake8_datetimez::rules::call_datetime_strptime_without_zone(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::CallDateToday) {
|
||||
flake8_datetimez::rules::call_date_today(checker, func, expr.range());
|
||||
@@ -754,18 +741,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pylint::rules::bad_str_strip_call(checker, func, args);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidEnvvarDefault) {
|
||||
pylint::rules::invalid_envvar_default(checker, func, args, keywords);
|
||||
pylint::rules::invalid_envvar_default(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::InvalidEnvvarValue) {
|
||||
pylint::rules::invalid_envvar_value(checker, func, args, keywords);
|
||||
pylint::rules::invalid_envvar_value(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::NestedMinMax) {
|
||||
pylint::rules::nested_min_max(checker, expr, func, args, keywords);
|
||||
}
|
||||
if checker.enabled(Rule::PytestPatchWithLambda) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pytest_style::rules::patch_with_lambda(func, args, keywords)
|
||||
{
|
||||
if let Some(diagnostic) = flake8_pytest_style::rules::patch_with_lambda(call) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -777,16 +762,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::SubprocessPopenPreexecFn) {
|
||||
pylint::rules::subprocess_popen_preexec_fn(checker, func, keywords);
|
||||
pylint::rules::subprocess_popen_preexec_fn(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::PytestRaisesWithoutException,
|
||||
Rule::PytestRaisesTooBroad,
|
||||
]) {
|
||||
flake8_pytest_style::rules::raises_call(checker, func, args, keywords);
|
||||
flake8_pytest_style::rules::raises_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PytestFailWithoutMessage) {
|
||||
flake8_pytest_style::rules::fail_call(checker, func, args, keywords);
|
||||
flake8_pytest_style::rules::fail_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PairwiseOverZipped) {
|
||||
if checker.settings.target_version >= PythonVersion::Py310 {
|
||||
@@ -857,7 +842,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, expr, func);
|
||||
}
|
||||
if checker.enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, func, args, keywords);
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::NumpyLegacyRandom) {
|
||||
numpy::rules::legacy_random(checker, func);
|
||||
@@ -872,15 +857,15 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::LoggingExcInfo,
|
||||
Rule::LoggingRedundantExcInfo,
|
||||
]) {
|
||||
flake8_logging_format::rules::logging_call(checker, func, args, keywords);
|
||||
flake8_logging_format::rules::logging_call(checker, call);
|
||||
}
|
||||
if checker.any_enabled(&[Rule::LoggingTooFewArgs, Rule::LoggingTooManyArgs]) {
|
||||
pylint::rules::logging_call(checker, func, args, keywords);
|
||||
pylint::rules::logging_call(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::DjangoLocalsInRenderFunction) {
|
||||
flake8_django::rules::locals_in_render_function(checker, func, args, keywords);
|
||||
flake8_django::rules::locals_in_render_function(checker, call);
|
||||
}
|
||||
if checker.is_stub && checker.enabled(Rule::UnsupportedMethodCallOnAll) {
|
||||
if checker.enabled(Rule::UnsupportedMethodCallOnAll) {
|
||||
flake8_pyi::rules::unsupported_method_call_on_all(checker, func);
|
||||
}
|
||||
}
|
||||
@@ -1045,6 +1030,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
checker.locator,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BadStringFormatCharacter) {
|
||||
pylint::rules::bad_string_format_character::percent(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::BadStringFormatType) {
|
||||
pylint::rules::bad_string_format_type(checker, expr, right);
|
||||
}
|
||||
@@ -1088,26 +1076,30 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
// Avoid duplicate checks if the parent is an `|`
|
||||
&& !matches!(
|
||||
checker.semantic.expr_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
|
||||
)
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion)
|
||||
// Avoid duplicate checks if the parent is an `|`
|
||||
&& !matches!(
|
||||
checker.semantic.expr_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp { op: Operator::BitOr, ..}))
|
||||
)
|
||||
{
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
|
||||
// Avoid duplicate checks if the parent is an `|` since these rules
|
||||
// traverse nested unions.
|
||||
let is_unchecked_union = !matches!(
|
||||
checker.semantic.expr_parent(),
|
||||
Some(Expr::BinOp(ast::ExprBinOp {
|
||||
op: Operator::BitOr,
|
||||
..
|
||||
}))
|
||||
);
|
||||
if checker.enabled(Rule::DuplicateUnionMember)
|
||||
&& checker.semantic.in_type_definition()
|
||||
&& is_unchecked_union
|
||||
{
|
||||
flake8_pyi::rules::duplicate_union_member(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantLiteralUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::redundant_literal_union(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryTypeUnion) && is_unchecked_union {
|
||||
flake8_pyi::rules::unnecessary_type_union(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp(ast::ExprUnaryOp {
|
||||
@@ -1142,12 +1134,14 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_simplify::rules::double_negation(checker, expr, *op, operand);
|
||||
}
|
||||
}
|
||||
Expr::Compare(ast::ExprCompare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
range: _,
|
||||
}) => {
|
||||
Expr::Compare(
|
||||
compare @ ast::ExprCompare {
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
let check_none_comparisons = checker.enabled(Rule::NoneComparison);
|
||||
let check_true_false_comparisons = checker.enabled(Rule::TrueFalseComparison);
|
||||
if check_none_comparisons || check_true_false_comparisons {
|
||||
@@ -1165,7 +1159,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
pyflakes::rules::invalid_literal_comparison(checker, left, ops, comparators, expr);
|
||||
}
|
||||
if checker.enabled(Rule::TypeComparison) {
|
||||
pycodestyle::rules::type_comparison(checker, expr, ops, comparators);
|
||||
pycodestyle::rules::type_comparison(checker, compare);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::SysVersionCmpStr3,
|
||||
@@ -1261,7 +1255,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
Expr::Lambda(
|
||||
lambda @ ast::ExprLambda {
|
||||
args: _,
|
||||
parameters: _,
|
||||
body: _,
|
||||
range: _,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
pub(super) use argument::argument;
|
||||
pub(super) use arguments::arguments;
|
||||
pub(super) use bindings::bindings;
|
||||
pub(super) use comprehension::comprehension;
|
||||
pub(super) use deferred_for_loops::deferred_for_loops;
|
||||
@@ -8,12 +6,12 @@ pub(super) use definitions::definitions;
|
||||
pub(super) use except_handler::except_handler;
|
||||
pub(super) use expression::expression;
|
||||
pub(super) use module::module;
|
||||
pub(super) use parameter::parameter;
|
||||
pub(super) use parameters::parameters;
|
||||
pub(super) use statement::statement;
|
||||
pub(super) use suite::suite;
|
||||
pub(super) use unresolved_references::unresolved_references;
|
||||
|
||||
mod argument;
|
||||
mod arguments;
|
||||
mod bindings;
|
||||
mod comprehension;
|
||||
mod deferred_for_loops;
|
||||
@@ -22,6 +20,8 @@ mod definitions;
|
||||
mod except_handler;
|
||||
mod expression;
|
||||
mod module;
|
||||
mod parameter;
|
||||
mod parameters;
|
||||
mod statement;
|
||||
mod suite;
|
||||
mod unresolved_references;
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
use ruff_python_ast::{Arg, Ranged};
|
||||
use ruff_python_ast::{Parameter, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_builtins, pep8_naming, pycodestyle};
|
||||
|
||||
/// Run lint rules over an [`Arg`] syntax node.
|
||||
pub(crate) fn argument(arg: &Arg, checker: &mut Checker) {
|
||||
/// Run lint rules over a [`Parameter`] syntax node.
|
||||
pub(crate) fn parameter(parameter: &Parameter, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::AmbiguousVariableName) {
|
||||
if let Some(diagnostic) = pycodestyle::rules::ambiguous_variable_name(&arg.arg, arg.range())
|
||||
if let Some(diagnostic) =
|
||||
pycodestyle::rules::ambiguous_variable_name(¶meter.name, parameter.range())
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::InvalidArgumentName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::invalid_argument_name(
|
||||
&arg.arg,
|
||||
arg,
|
||||
¶meter.name,
|
||||
parameter,
|
||||
&checker.settings.pep8_naming.ignore_names,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BuiltinArgumentShadowing) {
|
||||
flake8_builtins::rules::builtin_argument_shadowing(checker, arg);
|
||||
flake8_builtins::rules::builtin_argument_shadowing(checker, parameter);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
use ruff_python_ast::Arguments;
|
||||
use ruff_python_ast::Parameters;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_bugbear, flake8_pyi, ruff};
|
||||
|
||||
/// Run lint rules over a [`Arguments`] syntax node.
|
||||
pub(crate) fn arguments(arguments: &Arguments, checker: &mut Checker) {
|
||||
/// Run lint rules over a [`Parameters`] syntax node.
|
||||
pub(crate) fn parameters(parameters: &Parameters, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::MutableArgumentDefault) {
|
||||
flake8_bugbear::rules::mutable_argument_default(checker, arguments);
|
||||
flake8_bugbear::rules::mutable_argument_default(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::FunctionCallInDefaultArgument) {
|
||||
flake8_bugbear::rules::function_call_in_argument_default(checker, arguments);
|
||||
flake8_bugbear::rules::function_call_in_argument_default(checker, parameters);
|
||||
}
|
||||
if checker.settings.rules.enabled(Rule::ImplicitOptional) {
|
||||
ruff::rules::implicit_optional(checker, arguments);
|
||||
ruff::rules::implicit_optional(checker, parameters);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::TypedArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::typed_argument_simple_defaults(checker, arguments);
|
||||
flake8_pyi::rules::typed_argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::ArgumentDefaultInStub) {
|
||||
flake8_pyi::rules::argument_simple_defaults(checker, arguments);
|
||||
flake8_pyi::rules::argument_simple_defaults(checker, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,16 +73,18 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
decorator_list,
|
||||
returns,
|
||||
args,
|
||||
parameters,
|
||||
body,
|
||||
type_params,
|
||||
..
|
||||
}) => {
|
||||
if checker.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||
@@ -114,7 +116,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
parameters,
|
||||
)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
@@ -126,7 +128,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker.semantic.scope(),
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
parameters,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -141,38 +143,52 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::StubBodyMultipleStatements) {
|
||||
flake8_pyi::rules::stub_body_multiple_statements(checker, stmt, body);
|
||||
}
|
||||
if checker.enabled(Rule::AnyEqNeAnnotation) {
|
||||
flake8_pyi::rules::any_eq_ne_annotation(checker, name, args);
|
||||
}
|
||||
if checker.enabled(Rule::NonSelfReturnType) {
|
||||
flake8_pyi::rules::non_self_return_type(
|
||||
checker,
|
||||
stmt,
|
||||
name,
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
args,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::AnyEqNeAnnotation) {
|
||||
flake8_pyi::rules::any_eq_ne_annotation(checker, name, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::NonSelfReturnType) {
|
||||
flake8_pyi::rules::non_self_return_type(
|
||||
checker,
|
||||
stmt,
|
||||
name,
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
parameters,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::CustomTypeVarReturnType) {
|
||||
flake8_pyi::rules::custom_type_var_return_type(
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
parameters,
|
||||
type_params.as_ref(),
|
||||
);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
|
||||
}
|
||||
}
|
||||
if checker.is_stub || checker.settings.target_version >= PythonVersion::Py311 {
|
||||
if checker.enabled(Rule::NoReturnArgumentAnnotationInStub) {
|
||||
flake8_pyi::rules::no_return_argument_annotation(checker, args);
|
||||
}
|
||||
if checker.enabled(Rule::BadExitAnnotation) {
|
||||
flake8_pyi::rules::bad_exit_annotation(
|
||||
checker,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
name,
|
||||
args,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||
flake8_pyi::rules::redundant_numeric_union(checker, args);
|
||||
flake8_pyi::rules::no_return_argument_annotation(checker, parameters);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::BadExitAnnotation) {
|
||||
flake8_pyi::rules::bad_exit_annotation(
|
||||
checker,
|
||||
stmt.is_async_function_def_stmt(),
|
||||
name,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::RedundantNumericUnion) {
|
||||
flake8_pyi::rules::redundant_numeric_union(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::DunderFunctionName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
|
||||
checker.semantic.scope(),
|
||||
@@ -230,13 +246,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedPasswordDefault) {
|
||||
flake8_bandit::rules::hardcoded_password_default(checker, args);
|
||||
flake8_bandit::rules::hardcoded_password_default(checker, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::PropertyWithParameters) {
|
||||
pylint::rules::property_with_parameters(checker, stmt, decorator_list, args);
|
||||
pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyArguments) {
|
||||
pylint::rules::too_many_arguments(checker, args, stmt);
|
||||
pylint::rules::too_many_arguments(checker, parameters, stmt);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyReturnStatements) {
|
||||
if let Some(diagnostic) = pylint::rules::too_many_return_statements(
|
||||
@@ -282,7 +298,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker,
|
||||
stmt,
|
||||
name,
|
||||
args,
|
||||
parameters,
|
||||
decorator_list,
|
||||
body,
|
||||
);
|
||||
@@ -304,7 +320,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::BooleanDefaultValueInFunctionDefinition) {
|
||||
@@ -312,7 +328,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
checker,
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::UnexpectedSpecialMethodSignature) {
|
||||
@@ -321,7 +337,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
stmt,
|
||||
name,
|
||||
decorator_list,
|
||||
args,
|
||||
parameters,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::FStringDocstring) {
|
||||
@@ -363,8 +379,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
Stmt::ClassDef(
|
||||
class_def @ ast::StmtClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
arguments,
|
||||
type_params: _,
|
||||
decorator_list,
|
||||
body,
|
||||
@@ -375,21 +390,27 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_django::rules::nullable_model_string_field(checker, body);
|
||||
}
|
||||
if checker.enabled(Rule::DjangoExcludeWithModelForm) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_django::rules::exclude_with_model_form(checker, bases, body)
|
||||
{
|
||||
if let Some(diagnostic) = flake8_django::rules::exclude_with_model_form(
|
||||
checker,
|
||||
arguments.as_deref(),
|
||||
body,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::DjangoAllWithModelForm) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_django::rules::all_with_model_form(checker, bases, body)
|
||||
flake8_django::rules::all_with_model_form(checker, arguments.as_deref(), body)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::DjangoUnorderedBodyContentInModel) {
|
||||
flake8_django::rules::unordered_body_content_in_model(checker, bases, body);
|
||||
flake8_django::rules::unordered_body_content_in_model(
|
||||
checker,
|
||||
arguments.as_deref(),
|
||||
body,
|
||||
);
|
||||
}
|
||||
if !checker.is_stub {
|
||||
if checker.enabled(Rule::DjangoModelWithoutDunderStr) {
|
||||
@@ -425,7 +446,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ErrorSuffixOnExceptionName) {
|
||||
if let Some(diagnostic) = pep8_naming::rules::error_suffix_on_exception_name(
|
||||
stmt,
|
||||
bases,
|
||||
arguments.as_deref(),
|
||||
name,
|
||||
&checker.settings.pep8_naming.ignore_names,
|
||||
) {
|
||||
@@ -438,7 +459,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
Rule::EmptyMethodWithoutAbstractDecorator,
|
||||
]) {
|
||||
flake8_bugbear::rules::abstract_base_class(
|
||||
checker, stmt, name, bases, keywords, body,
|
||||
checker,
|
||||
stmt,
|
||||
name,
|
||||
arguments.as_deref(),
|
||||
body,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -449,9 +474,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::PassInClassBody) {
|
||||
flake8_pyi::rules::pass_in_class_body(checker, stmt, body);
|
||||
}
|
||||
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
|
||||
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::EllipsisInNonEmptyClassBody) {
|
||||
flake8_pyi::rules::ellipsis_in_non_empty_class_body(checker, stmt, body);
|
||||
}
|
||||
if checker.enabled(Rule::PytestIncorrectMarkParenthesesStyle) {
|
||||
flake8_pytest_style::rules::marks(checker, decorator_list);
|
||||
@@ -478,7 +503,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
|
||||
}
|
||||
if checker.enabled(Rule::DuplicateBases) {
|
||||
pylint::rules::duplicate_bases(checker, name, bases);
|
||||
pylint::rules::duplicate_bases(checker, name, arguments.as_deref());
|
||||
}
|
||||
if checker.enabled(Rule::NoSlotsInStrSubclass) {
|
||||
flake8_slots::rules::no_slots_in_str_subclass(checker, stmt, class_def);
|
||||
@@ -1337,12 +1362,14 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
target,
|
||||
value,
|
||||
annotation,
|
||||
..
|
||||
}) => {
|
||||
Stmt::AnnAssign(
|
||||
assign_stmt @ ast::StmtAnnAssign {
|
||||
target,
|
||||
value,
|
||||
annotation,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
if let Some(value) = value {
|
||||
if checker.enabled(Rule::LambdaAssignment) {
|
||||
pycodestyle::rules::lambda_assignment(
|
||||
@@ -1365,6 +1392,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
stmt,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::NonPEP695TypeAlias) {
|
||||
pyupgrade::rules::non_pep695_type_alias(checker, assign_stmt);
|
||||
}
|
||||
if checker.is_stub {
|
||||
if let Some(value) = value {
|
||||
if checker.enabled(Rule::AssignmentDefaultInStub) {
|
||||
@@ -1386,13 +1416,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||
if checker.enabled(Rule::SnakeCaseTypeAlias) {
|
||||
flake8_pyi::rules::snake_case_type_alias(checker, target);
|
||||
}
|
||||
if checker.enabled(Rule::TSuffixedTypeAlias) {
|
||||
flake8_pyi::rules::t_suffixed_type_alias(checker, target);
|
||||
}
|
||||
}
|
||||
if checker.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||
if checker.enabled(Rule::SnakeCaseTypeAlias) {
|
||||
flake8_pyi::rules::snake_case_type_alias(checker, target);
|
||||
}
|
||||
if checker.enabled(Rule::TSuffixedTypeAlias) {
|
||||
flake8_pyi::rules::t_suffixed_type_alias(checker, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Arg, ArgWithDefault, Arguments, Comprehension, Constant, ElifElseClause,
|
||||
ExceptHandler, Expr, ExprContext, Keyword, Pattern, Ranged, Stmt, Suite, UnaryOp,
|
||||
self as ast, Arguments, Comprehension, Constant, ElifElseClause, ExceptHandler, Expr,
|
||||
ExprContext, Keyword, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt,
|
||||
Suite, UnaryOp,
|
||||
};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
@@ -232,6 +233,11 @@ impl<'a> Checker<'a> {
|
||||
&self.semantic
|
||||
}
|
||||
|
||||
/// Return `true` if the current file is a stub file (`.pyi`).
|
||||
pub(crate) const fn is_stub(&self) -> bool {
|
||||
self.is_stub
|
||||
}
|
||||
|
||||
/// The [`Path`] to the file under analysis.
|
||||
pub(crate) const fn path(&self) -> &'a Path {
|
||||
self.path
|
||||
@@ -334,7 +340,7 @@ where
|
||||
if alias
|
||||
.asname
|
||||
.as_ref()
|
||||
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
|
||||
.is_some_and(|asname| asname.as_str() == alias.name.as_str())
|
||||
{
|
||||
flags |= BindingFlags::EXPLICIT_EXPORT;
|
||||
}
|
||||
@@ -379,7 +385,7 @@ where
|
||||
if alias
|
||||
.asname
|
||||
.as_ref()
|
||||
.map_or(false, |asname| asname.as_str() == alias.name.as_str())
|
||||
.is_some_and(|asname| asname.as_str() == alias.name.as_str())
|
||||
{
|
||||
flags |= BindingFlags::EXPLICIT_EXPORT;
|
||||
}
|
||||
@@ -450,7 +456,7 @@ where
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body,
|
||||
args,
|
||||
parameters,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_params,
|
||||
@@ -458,7 +464,7 @@ where
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body,
|
||||
args,
|
||||
parameters,
|
||||
decorator_list,
|
||||
type_params,
|
||||
returns,
|
||||
@@ -476,28 +482,28 @@ where
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
|
||||
for arg_with_default in args
|
||||
for parameter_with_default in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&args.args)
|
||||
.chain(&args.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = &arg_with_default.def.annotation {
|
||||
if let Some(expr) = ¶meter_with_default.parameter.annotation {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_annotation(expr);
|
||||
} else {
|
||||
self.visit_annotation(expr);
|
||||
};
|
||||
}
|
||||
if let Some(expr) = &arg_with_default.default {
|
||||
if let Some(expr) = ¶meter_with_default.default {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
if let Some(arg) = &args.vararg {
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_annotation(expr);
|
||||
@@ -506,7 +512,7 @@ where
|
||||
};
|
||||
}
|
||||
}
|
||||
if let Some(arg) = &args.kwarg {
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
if let Some(expr) = &arg.annotation {
|
||||
if runtime_annotation {
|
||||
self.visit_runtime_annotation(expr);
|
||||
@@ -547,8 +553,7 @@ where
|
||||
Stmt::ClassDef(
|
||||
class_def @ ast::StmtClassDef {
|
||||
body,
|
||||
bases,
|
||||
keywords,
|
||||
arguments,
|
||||
decorator_list,
|
||||
type_params,
|
||||
..
|
||||
@@ -560,14 +565,12 @@ where
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
for expr in bases {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
for keyword in keywords {
|
||||
self.visit_keyword(keyword);
|
||||
|
||||
if let Some(arguments) = arguments {
|
||||
self.visit_arguments(arguments);
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
@@ -587,14 +590,14 @@ where
|
||||
self.visit_body(body);
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
range: _range,
|
||||
range: _,
|
||||
name,
|
||||
type_params,
|
||||
value,
|
||||
}) => {
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
if let Some(type_params) = type_params {
|
||||
self.visit_type_params(type_params);
|
||||
}
|
||||
self.visit_expr(value);
|
||||
self.semantic.pop_scope();
|
||||
@@ -832,8 +835,7 @@ where
|
||||
match expr {
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args: _,
|
||||
keywords: _,
|
||||
arguments: _,
|
||||
range: _,
|
||||
}) => {
|
||||
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
|
||||
@@ -883,21 +885,21 @@ where
|
||||
}
|
||||
Expr::Lambda(
|
||||
lambda @ ast::ExprLambda {
|
||||
args,
|
||||
parameters,
|
||||
body: _,
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
// Visit the default arguments, but avoid the body, which will be deferred.
|
||||
for ArgWithDefault {
|
||||
for ParameterWithDefault {
|
||||
default,
|
||||
def: _,
|
||||
parameter: _,
|
||||
range: _,
|
||||
} in args
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&args.args)
|
||||
.chain(&args.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = &default {
|
||||
self.visit_expr(expr);
|
||||
@@ -919,8 +921,12 @@ where
|
||||
}
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
range: _,
|
||||
}) => {
|
||||
self.visit_expr(func);
|
||||
@@ -1098,7 +1104,7 @@ where
|
||||
arg,
|
||||
range: _,
|
||||
} = keyword;
|
||||
if arg.as_ref().map_or(false, |arg| arg == "type") {
|
||||
if arg.as_ref().is_some_and(|arg| arg == "type") {
|
||||
self.visit_type_definition(value);
|
||||
} else {
|
||||
self.visit_non_type_definition(value);
|
||||
@@ -1288,43 +1294,43 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &'b Arguments) {
|
||||
fn visit_parameters(&mut self, parameters: &'b Parameters) {
|
||||
// Step 1: Binding.
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them
|
||||
// upstream.
|
||||
for arg_with_default in &arguments.posonlyargs {
|
||||
self.visit_arg(&arg_with_default.def);
|
||||
for parameter_with_default in ¶meters.posonlyargs {
|
||||
self.visit_parameter(¶meter_with_default.parameter);
|
||||
}
|
||||
for arg_with_default in &arguments.args {
|
||||
self.visit_arg(&arg_with_default.def);
|
||||
for parameter_with_default in ¶meters.args {
|
||||
self.visit_parameter(¶meter_with_default.parameter);
|
||||
}
|
||||
if let Some(arg) = &arguments.vararg {
|
||||
self.visit_arg(arg);
|
||||
if let Some(arg) = ¶meters.vararg {
|
||||
self.visit_parameter(arg);
|
||||
}
|
||||
for arg_with_default in &arguments.kwonlyargs {
|
||||
self.visit_arg(&arg_with_default.def);
|
||||
for parameter_with_default in ¶meters.kwonlyargs {
|
||||
self.visit_parameter(¶meter_with_default.parameter);
|
||||
}
|
||||
if let Some(arg) = &arguments.kwarg {
|
||||
self.visit_arg(arg);
|
||||
if let Some(arg) = ¶meters.kwarg {
|
||||
self.visit_parameter(arg);
|
||||
}
|
||||
|
||||
// Step 4: Analysis
|
||||
analyze::arguments(arguments, self);
|
||||
analyze::parameters(parameters, self);
|
||||
}
|
||||
|
||||
fn visit_arg(&mut self, arg: &'b Arg) {
|
||||
fn visit_parameter(&mut self, parameter: &'b Parameter) {
|
||||
// Step 1: Binding.
|
||||
// Bind, but intentionally avoid walking the annotation, as we handle it
|
||||
// upstream.
|
||||
self.add_binding(
|
||||
&arg.arg,
|
||||
arg.identifier(),
|
||||
¶meter.name,
|
||||
parameter.identifier(),
|
||||
BindingKind::Argument,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
|
||||
// Step 4: Analysis
|
||||
analyze::argument(arg, self);
|
||||
analyze::parameter(parameter, self);
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, pattern: &'b Pattern) {
|
||||
@@ -1731,6 +1737,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn visit_deferred_future_type_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.deferred.future_type_definitions.is_empty() {
|
||||
let type_definitions = std::mem::take(&mut self.deferred.future_type_definitions);
|
||||
for (expr, snapshot) in type_definitions {
|
||||
@@ -1741,9 +1748,11 @@ impl<'a> Checker<'a> {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
fn visit_deferred_type_param_definitions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.deferred.type_param_definitions.is_empty() {
|
||||
let type_params = std::mem::take(&mut self.deferred.type_param_definitions);
|
||||
for (type_param, snapshot) in type_params {
|
||||
@@ -1757,9 +1766,11 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.deferred.string_type_definitions.is_empty() {
|
||||
let type_definitions = std::mem::take(&mut self.deferred.string_type_definitions);
|
||||
for (range, value, snapshot) in type_definitions {
|
||||
@@ -1770,7 +1781,7 @@ impl<'a> Checker<'a> {
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if self.semantic.in_typing_only_annotation() {
|
||||
if self.semantic.in_annotation() && self.semantic.future_annotations() {
|
||||
if self.enabled(Rule::QuotedAnnotation) {
|
||||
pyupgrade::rules::quoted_annotation(self, value, range);
|
||||
}
|
||||
@@ -1803,18 +1814,24 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
fn visit_deferred_functions(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.deferred.functions.is_empty() {
|
||||
let deferred_functions = std::mem::take(&mut self.deferred.functions);
|
||||
for snapshot in deferred_functions {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
match &self.semantic.stmt() {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { body, args, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, args, .. }) => {
|
||||
self.visit_arguments(args);
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
body, parameters, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body, parameters, ..
|
||||
}) => {
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_body(body);
|
||||
}
|
||||
_ => {
|
||||
@@ -1823,31 +1840,36 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
fn visit_deferred_lambdas(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
while !self.deferred.lambdas.is_empty() {
|
||||
let lambdas = std::mem::take(&mut self.deferred.lambdas);
|
||||
for (expr, snapshot) in lambdas {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let Expr::Lambda(ast::ExprLambda {
|
||||
args,
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
}) = expr
|
||||
{
|
||||
self.visit_arguments(args);
|
||||
self.visit_parameters(parameters);
|
||||
self.visit_expr(body);
|
||||
} else {
|
||||
unreachable!("Expected Expr::Lambda");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
|
||||
/// Run any lint rules that operate over the module exports (i.e., members of `__all__`).
|
||||
fn visit_exports(&mut self) {
|
||||
let snapshot = self.semantic.snapshot();
|
||||
|
||||
let exports: Vec<(&str, TextRange)> = self
|
||||
.semantic
|
||||
.global_scope()
|
||||
@@ -1890,6 +1912,8 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.semantic.restore(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "E1142") => (RuleGroup::Unspecified, rules::pylint::rules::AwaitOutsideAsync),
|
||||
(Pylint, "E1205") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooManyArgs),
|
||||
(Pylint, "E1206") => (RuleGroup::Unspecified, rules::pylint::rules::LoggingTooFewArgs),
|
||||
(Pylint, "E1300") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatCharacter),
|
||||
(Pylint, "E1307") => (RuleGroup::Unspecified, rules::pylint::rules::BadStringFormatType),
|
||||
(Pylint, "E1310") => (RuleGroup::Unspecified, rules::pylint::rules::BadStrStripCall),
|
||||
(Pylint, "E1507") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarValue),
|
||||
@@ -438,6 +439,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "037") => (RuleGroup::Unspecified, rules::pyupgrade::rules::QuotedAnnotation),
|
||||
(Pyupgrade, "038") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP604Isinstance),
|
||||
(Pyupgrade, "039") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UnnecessaryClassParentheses),
|
||||
(Pyupgrade, "040") => (RuleGroup::Unspecified, rules::pyupgrade::rules::NonPEP695TypeAlias),
|
||||
|
||||
// pydocstyle
|
||||
(Pydocstyle, "100") => (RuleGroup::Unspecified, rules::pydocstyle::rules::UndocumentedPublicModule),
|
||||
@@ -636,6 +638,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "016") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DuplicateUnionMember),
|
||||
(Flake8Pyi, "017") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexAssignmentInStub),
|
||||
(Flake8Pyi, "018") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeVar),
|
||||
(Flake8Pyi, "019") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CustomTypeVarReturnType),
|
||||
(Flake8Pyi, "020") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::QuotedAnnotationInStub),
|
||||
(Flake8Pyi, "021") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::DocstringInStub),
|
||||
(Flake8Pyi, "024") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::CollectionsNamedTuple),
|
||||
@@ -658,9 +661,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
|
||||
(Flake8Pyi, "049") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypedDict),
|
||||
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
|
||||
(Flake8Pyi, "051") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::RedundantLiteralUnion),
|
||||
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
|
||||
(Flake8Pyi, "054") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NumericLiteralTooLong),
|
||||
(Flake8Pyi, "053") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StringOrBytesTooLong),
|
||||
(Flake8Pyi, "055") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnnecessaryTypeUnion),
|
||||
(Flake8Pyi, "056") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
|
||||
|
||||
// flake8-pytest-style
|
||||
|
||||
@@ -402,7 +402,7 @@ fn is_docstring_section(
|
||||
}
|
||||
|
||||
// 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_is_underline = next_line.is_some_and(|next_line| {
|
||||
let next_line = next_line.trim();
|
||||
if next_line.is_empty() {
|
||||
false
|
||||
|
||||
@@ -299,12 +299,12 @@ fn match_leading_semicolon(s: &str) -> Option<TextSize> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use ruff_python_ast::Suite;
|
||||
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::Parse;
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use super::Insertion;
|
||||
@@ -312,7 +312,7 @@ mod tests {
|
||||
#[test]
|
||||
fn start_of_file() -> Result<()> {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
let program = Suite::parse(contents, "<filename>")?;
|
||||
let program = parse_suite(contents, "<filename>")?;
|
||||
let tokens: Vec<LexResult> = ruff_python_parser::tokenize(contents);
|
||||
let locator = Locator::new(contents);
|
||||
let stylist = Stylist::from_tokens(&tokens, &locator);
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'a> Importer<'a> {
|
||||
}) = stmt
|
||||
{
|
||||
if level.map_or(true, |level| level.to_u32() == 0)
|
||||
&& name.as_ref().map_or(false, |name| name == module)
|
||||
&& name.as_ref().is_some_and(|name| name == module)
|
||||
{
|
||||
import_from = Some(*stmt);
|
||||
}
|
||||
|
||||
@@ -376,7 +376,7 @@ impl Notebook {
|
||||
1
|
||||
} else {
|
||||
let trailing_newline =
|
||||
usize::from(string_array.last().map_or(false, |s| s.ends_with('\n')));
|
||||
usize::from(string_array.last().is_some_and(|s| s.ends_with('\n')));
|
||||
u32::try_from(string_array.len() + trailing_newline).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ impl<'a> EmitterContext<'a> {
|
||||
pub fn is_jupyter_notebook(&self, name: &str) -> bool {
|
||||
self.source_kind
|
||||
.get(name)
|
||||
.map_or(false, SourceKind::is_jupyter)
|
||||
.is_some_and(SourceKind::is_jupyter)
|
||||
}
|
||||
|
||||
pub fn source_kind(&self, name: &str) -> Option<&SourceKind> {
|
||||
|
||||
@@ -60,7 +60,7 @@ impl<'a> Directive<'a> {
|
||||
if text[..comment_start]
|
||||
.chars()
|
||||
.last()
|
||||
.map_or(false, |c| c != '#')
|
||||
.is_some_and(|c| c != '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ pub fn python_files_in_path(
|
||||
if let Ok(entry) = &result {
|
||||
if entry
|
||||
.file_type()
|
||||
.map_or(false, |file_type| file_type.is_dir())
|
||||
.is_some_and(|file_type| file_type.is_dir())
|
||||
{
|
||||
match settings_toml(entry.path()) {
|
||||
Ok(Some(pyproject)) => match resolve_scoped_settings(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Constant;
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -60,7 +58,10 @@ pub(crate) fn variable_name_task_id(
|
||||
};
|
||||
|
||||
// If the value is not a call, we can't do anything.
|
||||
let Expr::Call(ast::ExprCall { func, keywords, .. }) = value else {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func, arguments, ..
|
||||
}) = value
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -68,13 +69,13 @@ pub(crate) fn variable_name_task_id(
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| matches!(call_path[0], "airflow"))
|
||||
.is_some_and(|call_path| matches!(call_path[0], "airflow"))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the call doesn't have a `task_id` keyword argument, we can't do anything.
|
||||
let keyword = find_keyword(keywords, "task_id")?;
|
||||
let keyword = arguments.find_keyword("task_id")?;
|
||||
|
||||
// If the keyword argument is not a string, we can't do anything.
|
||||
let task_id = match &keyword.value {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/// See: [eradicate.py](https://github.com/myint/eradicate/blob/98f199940979c94447a461d50d27862b118b282d/eradicate.py)
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff_python_ast::Suite;
|
||||
use ruff_python_parser::Parse;
|
||||
|
||||
use ruff_python_parser::parse_suite;
|
||||
|
||||
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
@@ -79,7 +79,7 @@ pub(crate) fn comment_contains_code(line: &str, task_tags: &[String]) -> bool {
|
||||
}
|
||||
|
||||
// Finally, compile the source code.
|
||||
Suite::parse(&line, "<filename>").is_ok()
|
||||
parse_suite(&line, "<filename>").is_ok()
|
||||
}
|
||||
|
||||
/// Returns `true` if a line is probably part of some multiline code.
|
||||
|
||||
@@ -5,5 +5,5 @@ use ruff_python_semantic::SemanticModel;
|
||||
pub(super) fn is_sys(expr: &Expr, target: &str, semantic: &SemanticModel) -> bool {
|
||||
semantic
|
||||
.resolve_call_path(expr)
|
||||
.map_or(false, |call_path| call_path.as_slice() == ["sys", target])
|
||||
.is_some_and(|call_path| call_path.as_slice() == ["sys", target])
|
||||
}
|
||||
|
||||
@@ -49,9 +49,7 @@ pub(crate) fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(expr)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["six", "PY3"])
|
||||
})
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["six", "PY3"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Parameters, Stmt};
|
||||
|
||||
use ruff_python_ast::cast;
|
||||
use ruff_python_semantic::analyze::visibility;
|
||||
@@ -6,11 +6,11 @@ use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
||||
|
||||
pub(super) fn match_function_def(
|
||||
stmt: &Stmt,
|
||||
) -> (&str, &Arguments, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
|
||||
) -> (&str, &Parameters, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
name,
|
||||
args,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
decorator_list,
|
||||
@@ -18,14 +18,14 @@ pub(super) fn match_function_def(
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
parameters,
|
||||
returns,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
}) => (
|
||||
name,
|
||||
args,
|
||||
parameters,
|
||||
returns.as_ref().map(AsRef::as_ref),
|
||||
body,
|
||||
decorator_list,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, ArgWithDefault, Constant, Expr, Ranged, Stmt};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -524,8 +524,8 @@ pub(crate) fn definition(
|
||||
let is_overridden = visibility::is_override(decorator_list, checker.semantic());
|
||||
|
||||
// ANN001, ANN401
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in arguments
|
||||
@@ -542,26 +542,29 @@ pub(crate) fn definition(
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed arguments
|
||||
if let Some(annotation) = &def.annotation {
|
||||
if let Some(annotation) = ¶meter.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
annotation,
|
||||
|| def.arg.to_string(),
|
||||
|| parameter.name.to_string(),
|
||||
&mut diagnostics,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&def.arg))
|
||||
&& checker
|
||||
.settings
|
||||
.dummy_variable_rgx
|
||||
.is_match(¶meter.name))
|
||||
{
|
||||
if checker.enabled(Rule::MissingTypeFunctionArgument) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeFunctionArgument {
|
||||
name: def.arg.to_string(),
|
||||
name: parameter.name.to_string(),
|
||||
},
|
||||
def.range(),
|
||||
parameter.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -574,18 +577,18 @@ pub(crate) fn definition(
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
let name = &arg.arg;
|
||||
let name = &arg.name;
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.arg))
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.name))
|
||||
{
|
||||
if checker.enabled(Rule::MissingTypeArgs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeArgs {
|
||||
name: arg.arg.to_string(),
|
||||
name: arg.name.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
@@ -600,7 +603,7 @@ pub(crate) fn definition(
|
||||
has_any_typed_arg = true;
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||
let name = &arg.arg;
|
||||
let name = &arg.name;
|
||||
check_dynamically_typed(
|
||||
checker,
|
||||
expr,
|
||||
@@ -611,12 +614,12 @@ pub(crate) fn definition(
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.arg))
|
||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.name))
|
||||
{
|
||||
if checker.enabled(Rule::MissingTypeKwargs) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeKwargs {
|
||||
name: arg.arg.to_string(),
|
||||
name: arg.name.to_string(),
|
||||
},
|
||||
arg.range(),
|
||||
));
|
||||
@@ -627,8 +630,8 @@ pub(crate) fn definition(
|
||||
|
||||
// ANN101, ANN102
|
||||
if is_method && !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if let Some(ArgWithDefault {
|
||||
def,
|
||||
if let Some(ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
}) = arguments
|
||||
@@ -636,23 +639,23 @@ pub(crate) fn definition(
|
||||
.first()
|
||||
.or_else(|| arguments.args.first())
|
||||
{
|
||||
if def.annotation.is_none() {
|
||||
if parameter.annotation.is_none() {
|
||||
if visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
||||
if checker.enabled(Rule::MissingTypeCls) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeCls {
|
||||
name: def.arg.to_string(),
|
||||
name: parameter.name.to_string(),
|
||||
},
|
||||
def.range(),
|
||||
parameter.range(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if checker.enabled(Rule::MissingTypeSelf) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
MissingTypeSelf {
|
||||
name: def.arg.to_string(),
|
||||
name: parameter.name.to_string(),
|
||||
},
|
||||
def.range(),
|
||||
parameter.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn blocking_http_call(checker: &mut Checker, expr: &Expr) {
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.as_ref()
|
||||
.map_or(false, is_blocking_http_call)
|
||||
.is_some_and(is_blocking_http_call)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BlockingHttpCallInAsyncFunction,
|
||||
|
||||
@@ -48,7 +48,7 @@ pub(crate) fn blocking_os_call(checker: &mut Checker, expr: &Expr) {
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.as_ref()
|
||||
.map_or(false, is_unsafe_os_method)
|
||||
.is_some_and(is_unsafe_os_method)
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -48,7 +48,7 @@ pub(crate) fn open_sleep_or_subprocess_call(checker: &mut Checker, expr: &Expr)
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.as_ref()
|
||||
.map_or(false, is_open_sleep_or_subprocess_call)
|
||||
.is_some_and(is_open_sleep_or_subprocess_call)
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
OpenSleepOrSubprocessInAsyncFunction,
|
||||
|
||||
@@ -26,18 +26,14 @@ pub(super) fn is_untyped_exception(type_: Option<&Expr>, semantic: &SemanticMode
|
||||
type_.map_or(true, |type_| {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = &type_ {
|
||||
elts.iter().any(|type_| {
|
||||
semantic
|
||||
.resolve_call_path(type_)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["", "Exception" | "BaseException"])
|
||||
})
|
||||
})
|
||||
} else {
|
||||
semantic
|
||||
.resolve_call_path(type_)
|
||||
.map_or(false, |call_path| {
|
||||
semantic.resolve_call_path(type_).is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["", "Exception" | "BaseException"])
|
||||
})
|
||||
})
|
||||
} else {
|
||||
semantic.resolve_call_path(type_).is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["", "Exception" | "BaseException"])
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use num_traits::ToPrimitive;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Operator, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::call_path::CallPath;
|
||||
use ruff_python_ast::helpers::CallArguments;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Operator, Ranged};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -48,21 +47,13 @@ impl Violation for BadFilePermissions {
|
||||
}
|
||||
|
||||
/// S103
|
||||
pub(crate) fn bad_file_permissions(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn bad_file_permissions(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["os", "chmod"])
|
||||
})
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["os", "chmod"]))
|
||||
{
|
||||
let call_args = CallArguments::new(args, keywords);
|
||||
if let Some(mode_arg) = call_args.argument("mode", 1) {
|
||||
if let Some(mode_arg) = call.arguments.find_argument("mode", 1) {
|
||||
if let Some(int_value) = int_value(mode_arg, checker.semantic()) {
|
||||
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
|
||||
@@ -35,9 +35,7 @@ pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["" | "builtin", "exec"])
|
||||
})
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["" | "builtin", "exec"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{Arg, ArgWithDefault, Arguments, Expr, Ranged};
|
||||
use ruff_python_ast::{Expr, Parameter, ParameterWithDefault, Parameters, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -53,9 +53,9 @@ impl Violation for HardcodedPasswordDefault {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
|
||||
fn check_password_kwarg(parameter: &Parameter, default: &Expr) -> Option<Diagnostic> {
|
||||
string_literal(default).filter(|string| !string.is_empty())?;
|
||||
let kwarg_name = &arg.arg;
|
||||
let kwarg_name = ¶meter.name;
|
||||
if !matches_password_name(kwarg_name) {
|
||||
return None;
|
||||
}
|
||||
@@ -68,21 +68,21 @@ fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
|
||||
}
|
||||
|
||||
/// S107
|
||||
pub(crate) fn hardcoded_password_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
pub(crate) fn hardcoded_password_default(checker: &mut Checker, parameters: &Parameters) {
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default,
|
||||
range: _,
|
||||
} in arguments
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
};
|
||||
if let Some(diagnostic) = check_password_kwarg(def, default) {
|
||||
if let Some(diagnostic) = check_password_kwarg(parameter, default) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{find_keyword, is_const_false, CallArguments};
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
use ruff_python_ast::{self as ast, Arguments, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -60,30 +59,26 @@ impl Violation for HashlibInsecureHashFunction {
|
||||
}
|
||||
|
||||
/// S324
|
||||
pub(crate) fn hashlib_insecure_hash_functions(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if let Some(hashlib_call) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["hashlib", "new"] => Some(HashlibCall::New),
|
||||
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
|
||||
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),
|
||||
["hashlib", "sha"] => Some(HashlibCall::WeakHash("sha")),
|
||||
["hashlib", "sha1"] => Some(HashlibCall::WeakHash("sha1")),
|
||||
_ => None,
|
||||
})
|
||||
pub(crate) fn hashlib_insecure_hash_functions(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if let Some(hashlib_call) =
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_call_path(&call.func)
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["hashlib", "new"] => Some(HashlibCall::New),
|
||||
["hashlib", "md4"] => Some(HashlibCall::WeakHash("md4")),
|
||||
["hashlib", "md5"] => Some(HashlibCall::WeakHash("md5")),
|
||||
["hashlib", "sha"] => Some(HashlibCall::WeakHash("sha")),
|
||||
["hashlib", "sha1"] => Some(HashlibCall::WeakHash("sha1")),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
if !is_used_for_security(keywords) {
|
||||
if !is_used_for_security(&call.arguments) {
|
||||
return;
|
||||
}
|
||||
match hashlib_call {
|
||||
HashlibCall::New => {
|
||||
if let Some(name_arg) = CallArguments::new(args, keywords).argument("name", 0) {
|
||||
if let Some(name_arg) = call.arguments.find_argument("name", 0) {
|
||||
if let Some(hash_func_name) = string_literal(name_arg) {
|
||||
// `hashlib.new` accepts both lowercase and uppercase names for hash
|
||||
// functions.
|
||||
@@ -106,15 +101,16 @@ pub(crate) fn hashlib_insecure_hash_functions(
|
||||
HashlibInsecureHashFunction {
|
||||
string: (*func_name).to_string(),
|
||||
},
|
||||
func.range(),
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_used_for_security(keywords: &[Keyword]) -> bool {
|
||||
find_keyword(keywords, "usedforsecurity")
|
||||
fn is_used_for_security(arguments: &Arguments) -> bool {
|
||||
arguments
|
||||
.find_keyword("usedforsecurity")
|
||||
.map_or(true, |keyword| !is_const_false(&keyword.value))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -59,15 +57,13 @@ impl Violation for Jinja2AutoescapeFalse {
|
||||
}
|
||||
|
||||
/// S701
|
||||
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["jinja2", "Environment"])
|
||||
})
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["jinja2", "Environment"]))
|
||||
{
|
||||
if let Some(keyword) = find_keyword(keywords, "autoescape") {
|
||||
if let Some(keyword) = call.arguments.find_keyword("autoescape") {
|
||||
match &keyword.value {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(true),
|
||||
@@ -91,7 +87,7 @@ pub(crate) fn jinja2_autoescape_false(checker: &mut Checker, func: &Expr, keywor
|
||||
} else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Jinja2AutoescapeFalse { value: false },
|
||||
func.range(),
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::{self as ast, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -35,24 +33,19 @@ impl Violation for LoggingConfigInsecureListen {
|
||||
}
|
||||
|
||||
/// S612
|
||||
pub(crate) fn logging_config_insecure_listen(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn logging_config_insecure_listen(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["logging", "config", "listen"])
|
||||
})
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "config", "listen"]))
|
||||
{
|
||||
if find_keyword(keywords, "verify").is_some() {
|
||||
if call.arguments.find_keyword("verify").is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(LoggingConfigInsecureListen, func.range()));
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
LoggingConfigInsecureListen,
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["paramiko", "exec_command"])
|
||||
})
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["paramiko", "exec_command"]))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{find_keyword, is_const_false};
|
||||
use ruff_python_ast::helpers::is_const_false;
|
||||
use ruff_python_ast::{self as ast, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -46,14 +45,10 @@ impl Violation for RequestWithNoCertValidation {
|
||||
}
|
||||
|
||||
/// S501
|
||||
pub(crate) fn request_with_no_cert_validation(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn request_with_no_cert_validation(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if let Some(target) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.resolve_call_path(&call.func)
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
|
||||
Some("requests")
|
||||
@@ -63,7 +58,7 @@ pub(crate) fn request_with_no_cert_validation(
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
if let Some(keyword) = find_keyword(keywords, "verify") {
|
||||
if let Some(keyword) = call.arguments.find_keyword("verify") {
|
||||
if is_const_false(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithNoCertValidation {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{find_keyword, is_const_none};
|
||||
use ruff_python_ast::helpers::is_const_none;
|
||||
use ruff_python_ast::{self as ast, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -49,11 +48,11 @@ impl Violation for RequestWithoutTimeout {
|
||||
}
|
||||
|
||||
/// S113
|
||||
pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
pub(crate) fn request_without_timeout(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
[
|
||||
@@ -63,7 +62,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywor
|
||||
)
|
||||
})
|
||||
{
|
||||
if let Some(keyword) = find_keyword(keywords, "timeout") {
|
||||
if let Some(keyword) = call.arguments.find_keyword("timeout") {
|
||||
if is_const_none(&keyword.value) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { implicit: false },
|
||||
@@ -73,7 +72,7 @@ pub(crate) fn request_without_timeout(checker: &mut Checker, func: &Expr, keywor
|
||||
} else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RequestWithoutTimeout { implicit: true },
|
||||
func.range(),
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
//! Checks relating to shell injection.
|
||||
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{find_keyword, Truthiness};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::{
|
||||
@@ -144,17 +143,12 @@ impl Violation for UnixCommandWildcardInjection {
|
||||
}
|
||||
|
||||
/// S602, S603, S604, S605, S606, S607, S609
|
||||
pub(crate) fn shell_injection(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let call_kind = get_call_kind(func, checker.semantic());
|
||||
let shell_keyword = find_shell_keyword(keywords, checker.semantic());
|
||||
pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
let call_kind = get_call_kind(&call.func, checker.semantic());
|
||||
let shell_keyword = find_shell_keyword(&call.arguments, checker.semantic());
|
||||
|
||||
if matches!(call_kind, Some(CallKind::Subprocess)) {
|
||||
if let Some(arg) = args.first() {
|
||||
if let Some(arg) = call.arguments.args.first() {
|
||||
match shell_keyword {
|
||||
// S602
|
||||
Some(ShellKeyword {
|
||||
@@ -209,7 +203,7 @@ pub(crate) fn shell_injection(
|
||||
// S605
|
||||
if checker.enabled(Rule::StartProcessWithAShell) {
|
||||
if matches!(call_kind, Some(CallKind::Shell)) {
|
||||
if let Some(arg) = args.first() {
|
||||
if let Some(arg) = call.arguments.args.first() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
StartProcessWithAShell {
|
||||
seems_safe: shell_call_seems_safe(arg),
|
||||
@@ -225,14 +219,14 @@ pub(crate) fn shell_injection(
|
||||
if matches!(call_kind, Some(CallKind::NoShell)) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(StartProcessWithNoShell, func.range()));
|
||||
.push(Diagnostic::new(StartProcessWithNoShell, call.func.range()));
|
||||
}
|
||||
}
|
||||
|
||||
// S607
|
||||
if checker.enabled(Rule::StartProcessWithPartialPath) {
|
||||
if call_kind.is_some() {
|
||||
if let Some(arg) = args.first() {
|
||||
if let Some(arg) = call.arguments.args.first() {
|
||||
if is_partial_path(arg) {
|
||||
checker
|
||||
.diagnostics
|
||||
@@ -256,11 +250,12 @@ pub(crate) fn shell_injection(
|
||||
)
|
||||
)
|
||||
{
|
||||
if let Some(arg) = args.first() {
|
||||
if let Some(arg) = call.arguments.args.first() {
|
||||
if is_wildcard_command(arg) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(UnixCommandWildcardInjection, func.range()));
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnixCommandWildcardInjection,
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,10 +312,10 @@ struct ShellKeyword<'a> {
|
||||
|
||||
/// Return the `shell` keyword argument to the given function call, if any.
|
||||
fn find_shell_keyword<'a>(
|
||||
keywords: &'a [Keyword],
|
||||
arguments: &'a Arguments,
|
||||
semantic: &SemanticModel,
|
||||
) -> Option<ShellKeyword<'a>> {
|
||||
find_keyword(keywords, "shell").map(|keyword| ShellKeyword {
|
||||
arguments.find_keyword("shell").map(|keyword| ShellKeyword {
|
||||
truthiness: Truthiness::from_expr(&keyword.value, |id| semantic.is_builtin(id)),
|
||||
keyword,
|
||||
})
|
||||
@@ -379,7 +374,7 @@ fn is_partial_path(expr: &Expr) -> bool {
|
||||
Expr::List(ast::ExprList { elts, .. }) => elts.first().and_then(string_literal),
|
||||
_ => string_literal(expr),
|
||||
};
|
||||
string_literal.map_or(false, |text| !is_full_path(text))
|
||||
string_literal.is_some_and(|text| !is_full_path(text))
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] is a wildcard command.
|
||||
@@ -410,7 +405,7 @@ fn is_wildcard_command(expr: &Expr) -> bool {
|
||||
has_star && has_command
|
||||
} else {
|
||||
let string_literal = string_literal(expr);
|
||||
string_literal.map_or(false, |text| {
|
||||
string_literal.is_some_and(|text| {
|
||||
text.contains('*')
|
||||
&& (text.contains("chown")
|
||||
|| text.contains("chmod")
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use num_traits::{One, Zero};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -43,15 +42,15 @@ impl Violation for SnmpInsecureVersion {
|
||||
}
|
||||
|
||||
/// S508
|
||||
pub(crate) fn snmp_insecure_version(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
pub(crate) fn snmp_insecure_version(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "CommunityData"])
|
||||
})
|
||||
{
|
||||
if let Some(keyword) = find_keyword(keywords, "mpModel") {
|
||||
if let Some(keyword) = call.arguments.find_keyword("mpModel") {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::CallArguments;
|
||||
use ruff_python_ast::{self as ast, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -42,23 +40,18 @@ impl Violation for SnmpWeakCryptography {
|
||||
}
|
||||
|
||||
/// S509
|
||||
pub(crate) fn snmp_weak_cryptography(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "UsmUserData"])
|
||||
})
|
||||
{
|
||||
if CallArguments::new(args, keywords).len() < 3 {
|
||||
pub(crate) fn snmp_weak_cryptography(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if call.arguments.len() < 3 {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["pysnmp", "hlapi", "UsmUserData"])
|
||||
})
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(SnmpWeakCryptography, func.range()));
|
||||
.push(Diagnostic::new(SnmpWeakCryptography, call.func.range()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::CallArguments;
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -60,25 +58,17 @@ impl Violation for UnsafeYAMLLoad {
|
||||
}
|
||||
|
||||
/// S506
|
||||
pub(crate) fn unsafe_yaml_load(
|
||||
checker: &mut Checker,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
pub(crate) fn unsafe_yaml_load(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["yaml", "load"])
|
||||
})
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["yaml", "load"]))
|
||||
{
|
||||
let call_args = CallArguments::new(args, keywords);
|
||||
if let Some(loader_arg) = call_args.argument("Loader", 1) {
|
||||
if let Some(loader_arg) = call.arguments.find_argument("Loader", 1) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(loader_arg)
|
||||
.map_or(false, |call_path| {
|
||||
.is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["yaml", "SafeLoader" | "CSafeLoader"])
|
||||
})
|
||||
{
|
||||
@@ -95,7 +85,7 @@ pub(crate) fn unsafe_yaml_load(
|
||||
} else {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
UnsafeYAMLLoad { loader: None },
|
||||
func.range(),
|
||||
call.func.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::{find_keyword, is_const_true};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt};
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -77,7 +76,7 @@ pub(crate) fn blind_except(
|
||||
if let Stmt::Raise(ast::StmtRaise { exc, .. }) = stmt {
|
||||
if let Some(exc) = exc {
|
||||
if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() {
|
||||
name.map_or(false, |name| id == name)
|
||||
name.is_some_and(|name| id == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -94,7 +93,10 @@ pub(crate) fn blind_except(
|
||||
// If the exception is logged, don't flag an error.
|
||||
if body.iter().any(|stmt| {
|
||||
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
|
||||
if let Expr::Call(ast::ExprCall { func, keywords, .. }) = value.as_ref() {
|
||||
if let Expr::Call(ast::ExprCall {
|
||||
func, arguments, ..
|
||||
}) = value.as_ref()
|
||||
{
|
||||
if logging::is_logger_candidate(
|
||||
func,
|
||||
checker.semantic(),
|
||||
@@ -106,7 +108,7 @@ pub(crate) fn blind_except(
|
||||
return true;
|
||||
}
|
||||
if attr == "error" {
|
||||
if let Some(keyword) = find_keyword(keywords, "exc_info") {
|
||||
if let Some(keyword) = arguments.find_keyword("exc_info") {
|
||||
if is_const_true(&keyword.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ pub(super) fn is_allowed_func_call(name: &str) -> bool {
|
||||
| "index"
|
||||
| "insert"
|
||||
| "int"
|
||||
| "is_"
|
||||
| "is_not"
|
||||
| "param"
|
||||
| "pop"
|
||||
| "remove"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{ArgWithDefault, Arguments, Decorator};
|
||||
use ruff_python_ast::{Decorator, ParameterWithDefault, Parameters};
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -59,7 +59,7 @@ pub(crate) fn check_boolean_default_value_in_function_definition(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
arguments: &Arguments,
|
||||
parameters: &Parameters,
|
||||
) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
@@ -67,16 +67,16 @@ pub(crate) fn check_boolean_default_value_in_function_definition(
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
collect_call_path(&decorator.expression)
|
||||
.map_or(false, |call_path| call_path.as_slice() == [name, "setter"])
|
||||
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ArgWithDefault {
|
||||
def: _,
|
||||
for ParameterWithDefault {
|
||||
parameter: _,
|
||||
default,
|
||||
range: _,
|
||||
} in arguments.args.iter().chain(&arguments.posonlyargs)
|
||||
} in parameters.args.iter().chain(¶meters.posonlyargs)
|
||||
{
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use ruff_python_ast::{self as ast, ArgWithDefault, Arguments, Constant, Decorator, Expr, Ranged};
|
||||
use ruff_python_ast::{
|
||||
self as ast, Constant, Decorator, Expr, ParameterWithDefault, Parameters, Ranged,
|
||||
};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
@@ -80,7 +82,7 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||
checker: &mut Checker,
|
||||
name: &str,
|
||||
decorator_list: &[Decorator],
|
||||
arguments: &Arguments,
|
||||
parameters: &Parameters,
|
||||
) {
|
||||
if is_allowed_func_def(name) {
|
||||
return;
|
||||
@@ -88,21 +90,21 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||
|
||||
if decorator_list.iter().any(|decorator| {
|
||||
collect_call_path(&decorator.expression)
|
||||
.map_or(false, |call_path| call_path.as_slice() == [name, "setter"])
|
||||
.is_some_and(|call_path| call_path.as_slice() == [name, "setter"])
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in arguments.posonlyargs.iter().chain(&arguments.args)
|
||||
} in parameters.posonlyargs.iter().chain(¶meters.args)
|
||||
{
|
||||
if def.annotation.is_none() {
|
||||
if parameter.annotation.is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(expr) = &def.annotation else {
|
||||
let Some(expr) = ¶meter.annotation else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -120,7 +122,7 @@ pub(crate) fn check_positional_boolean_in_def(
|
||||
}
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
BooleanPositionalArgInFunctionDefinition,
|
||||
def.range(),
|
||||
parameter.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,12 @@ FBT.py:19:5: FBT001 Boolean positional arg in function definition
|
||||
21 | kwonly_nonvalued_nohint,
|
||||
|
|
||||
|
||||
FBT.py:85:19: FBT001 Boolean positional arg in function definition
|
||||
FBT.py:86:19: FBT001 Boolean positional arg in function definition
|
||||
|
|
||||
84 | # FBT001: Boolean positional arg in function definition
|
||||
85 | def foo(self, value: bool) -> None:
|
||||
85 | # FBT001: Boolean positional arg in function definition
|
||||
86 | def foo(self, value: bool) -> None:
|
||||
| ^^^^^^^^^^^ FBT001
|
||||
86 | pass
|
||||
87 | pass
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ FBT.py:69:38: FBT003 Boolean positional value in function call
|
||||
68 | g_action.set_enabled(True)
|
||||
69 | settings.set_enable_developer_extras(True)
|
||||
| ^^^^ FBT003
|
||||
70 | foo.is_(True)
|
||||
71 | bar.is_not(False)
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, Keyword, Ranged, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Constant, Expr, Keyword, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -106,26 +106,21 @@ impl Violation for EmptyMethodWithoutAbstractDecorator {
|
||||
|
||||
fn is_abc_class(bases: &[Expr], keywords: &[Keyword], semantic: &SemanticModel) -> bool {
|
||||
keywords.iter().any(|keyword| {
|
||||
keyword.arg.as_ref().map_or(false, |arg| arg == "metaclass")
|
||||
keyword.arg.as_ref().is_some_and(|arg| arg == "metaclass")
|
||||
&& semantic
|
||||
.resolve_call_path(&keyword.value)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["abc", "ABCMeta"])
|
||||
})
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["abc", "ABCMeta"]))
|
||||
}) || bases.iter().any(|base| {
|
||||
semantic.resolve_call_path(base).map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["abc", "ABC"])
|
||||
})
|
||||
semantic
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["abc", "ABC"]))
|
||||
})
|
||||
}
|
||||
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match stmt {
|
||||
Stmt::Pass(_) => true,
|
||||
Stmt::Expr(ast::StmtExpr {
|
||||
value,
|
||||
range: _range,
|
||||
}) => match value.as_ref() {
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => match value.as_ref() {
|
||||
Expr::Constant(ast::ExprConstant { value, .. }) => {
|
||||
matches!(value, Constant::Str(..) | Constant::Ellipsis)
|
||||
}
|
||||
@@ -141,14 +136,17 @@ pub(crate) fn abstract_base_class(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
arguments: Option<&Arguments>,
|
||||
body: &[Stmt],
|
||||
) {
|
||||
if bases.len() + keywords.len() != 1 {
|
||||
let Some(Arguments { args, keywords, .. }) = arguments else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args.len() + keywords.len() != 1 {
|
||||
return;
|
||||
}
|
||||
if !is_abc_class(bases, keywords, checker.semantic()) {
|
||||
if !is_abc_class(args, keywords, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext, Ranged, Stmt};
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Ranged, Stmt};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
@@ -53,12 +53,15 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
ctx: ExprContext::Load,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
args: if let Some(msg) = msg {
|
||||
vec![msg.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
arguments: Arguments {
|
||||
args: if let Some(msg) = msg {
|
||||
vec![msg.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
keywords: vec![],
|
||||
range: TextRange::default(),
|
||||
},
|
||||
keywords: vec![],
|
||||
range: TextRange::default(),
|
||||
}))),
|
||||
cause: None,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::fmt;
|
||||
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged, WithItem};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged, WithItem};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -81,23 +79,24 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
|
||||
for item in items {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
arguments,
|
||||
range: _,
|
||||
}) = &item.context_expr
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if args.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if item.optional_vars.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [arg] = arguments.args.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(exception) = checker
|
||||
.semantic()
|
||||
.resolve_call_path(args.first().unwrap())
|
||||
.resolve_call_path(arg)
|
||||
.and_then(|call_path| match call_path.as_slice() {
|
||||
["", "Exception"] => Some(ExceptionKind::Exception),
|
||||
["", "BaseException"] => Some(ExceptionKind::BaseException),
|
||||
@@ -113,10 +112,8 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem])
|
||||
} else if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pytest", "raises"])
|
||||
})
|
||||
&& find_keyword(keywords, "match").is_none()
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["pytest", "raises"]))
|
||||
&& arguments.find_keyword("match").is_none()
|
||||
{
|
||||
AssertionKind::PytestRaises
|
||||
} else {
|
||||
|
||||
@@ -70,7 +70,7 @@ impl Violation for CachedInstanceMethod {
|
||||
}
|
||||
|
||||
fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
semantic.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
semantic.resolve_call_path(expr).is_some_and(|call_path| {
|
||||
matches!(call_path.as_slice(), ["functools", "lru_cache" | "cache"])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, ArgWithDefault, Arguments, Expr, Ranged};
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Parameters, Ranged};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_diagnostics::Violation;
|
||||
@@ -105,7 +105,7 @@ where
|
||||
}
|
||||
|
||||
/// B008
|
||||
pub(crate) fn function_call_in_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
pub(crate) fn function_call_in_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
||||
// Map immutable calls to (module, member) format.
|
||||
let extend_immutable_calls: Vec<CallPath> = checker
|
||||
.settings
|
||||
@@ -116,15 +116,15 @@ pub(crate) fn function_call_in_argument_default(checker: &mut Checker, arguments
|
||||
.collect();
|
||||
let diagnostics = {
|
||||
let mut visitor = ArgumentDefaultVisitor::new(checker.semantic(), extend_immutable_calls);
|
||||
for ArgWithDefault {
|
||||
for ParameterWithDefault {
|
||||
default,
|
||||
def: _,
|
||||
parameter: _,
|
||||
range: _,
|
||||
} in arguments
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
if let Some(expr) = &default {
|
||||
visitor.visit_expr(expr);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr, ExprContext, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
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;
|
||||
use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprContext, Ranged, Stmt};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -86,8 +84,12 @@ struct SuspiciousVariablesVisitor<'a> {
|
||||
impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match stmt {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef { args, body, .. })
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { args, body, .. }) => {
|
||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
parameters, body, ..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
parameters, body, ..
|
||||
}) => {
|
||||
// Collect all loaded variable names.
|
||||
let mut visitor = LoadedNamesVisitor::default();
|
||||
visitor.visit_body(body);
|
||||
@@ -99,7 +101,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if includes_arg_name(&loaded.id, args) {
|
||||
if parameters.includes(&loaded.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -126,8 +128,12 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
match expr {
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
arguments:
|
||||
Arguments {
|
||||
args,
|
||||
keywords,
|
||||
range: _,
|
||||
},
|
||||
range: _,
|
||||
}) => {
|
||||
match func.as_ref() {
|
||||
@@ -157,7 +163,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
}
|
||||
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().map_or(false, |arg| arg == "key")
|
||||
if keyword.arg.as_ref().is_some_and(|arg| arg == "key")
|
||||
&& keyword.value.is_lambda_expr()
|
||||
{
|
||||
self.safe_functions.push(&keyword.value);
|
||||
@@ -165,7 +171,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
}
|
||||
}
|
||||
Expr::Lambda(ast::ExprLambda {
|
||||
args,
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
}) => {
|
||||
@@ -181,7 +187,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if includes_arg_name(&loaded.id, args) {
|
||||
if parameters.includes(&loaded.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, ArgWithDefault, Expr, Ranged};
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Ranged};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
@@ -71,22 +71,22 @@ where
|
||||
}
|
||||
}
|
||||
Expr::Lambda(ast::ExprLambda {
|
||||
args,
|
||||
parameters,
|
||||
body,
|
||||
range: _,
|
||||
}) => {
|
||||
visitor::walk_expr(self, body);
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default: _,
|
||||
range: _,
|
||||
} in args
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&args.args)
|
||||
.chain(&args.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
self.names.remove(def.arg.as_str());
|
||||
self.names.remove(parameter.name.as_str());
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{ArgWithDefault, Arguments, Ranged};
|
||||
use ruff_python_ast::{ParameterWithDefault, Parameters, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -57,26 +57,27 @@ impl Violation for MutableArgumentDefault {
|
||||
}
|
||||
|
||||
/// B006
|
||||
pub(crate) fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
pub(crate) fn mutable_argument_default(checker: &mut Checker, parameters: &Parameters) {
|
||||
// Scan in reverse order to right-align zip().
|
||||
for ArgWithDefault {
|
||||
def,
|
||||
for ParameterWithDefault {
|
||||
parameter,
|
||||
default,
|
||||
range: _,
|
||||
} in arguments
|
||||
} in parameters
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&arguments.args)
|
||||
.chain(&arguments.kwonlyargs)
|
||||
.chain(¶meters.args)
|
||||
.chain(¶meters.kwonlyargs)
|
||||
{
|
||||
let Some(default) = default else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if is_mutable_expr(default, checker.semantic())
|
||||
&& !def.annotation.as_ref().map_or(false, |expr| {
|
||||
is_immutable_annotation(expr, checker.semantic())
|
||||
})
|
||||
&& !parameter
|
||||
.annotation
|
||||
.as_ref()
|
||||
.is_some_and(|expr| is_immutable_annotation(expr, checker.semantic()))
|
||||
{
|
||||
checker
|
||||
.diagnostics
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use ruff_python_ast::{Expr, Keyword, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::find_keyword;
|
||||
use ruff_python_ast::{self as ast, Ranged};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -38,22 +36,20 @@ impl Violation for NoExplicitStacklevel {
|
||||
}
|
||||
|
||||
/// B028
|
||||
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, func: &Expr, keywords: &[Keyword]) {
|
||||
pub(crate) fn no_explicit_stacklevel(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["warnings", "warn"])
|
||||
})
|
||||
.resolve_call_path(&call.func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["warnings", "warn"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if find_keyword(keywords, "stacklevel").is_some() {
|
||||
if call.arguments.find_keyword("stacklevel").is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(NoExplicitStacklevel, func.range()));
|
||||
.push(Diagnostic::new(NoExplicitStacklevel, call.func.range()));
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ pub(crate) fn raise_without_from_inside_except(
|
||||
if let Some(name) = name {
|
||||
if exc
|
||||
.as_name_expr()
|
||||
.map_or(false, |ast::ExprName { id, .. }| name == id)
|
||||
.is_some_and(|ast::ExprName { id, .. }| name == id)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn re_sub_positional_args(checker: &mut Checker, call: &ast::ExprCall
|
||||
return;
|
||||
};
|
||||
|
||||
if call.args.len() > method.num_args() {
|
||||
if call.arguments.args.len() > method.num_args() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
ReSubPositionalArgs { method },
|
||||
call.range(),
|
||||
|
||||
@@ -313,9 +313,7 @@ pub(crate) fn reuse_of_groupby_generator(
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["itertools", "groupby"])
|
||||
})
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["itertools", "groupby"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ fn assignment(obj: &Expr, name: &str, value: &Expr, generator: Generator) -> Str
|
||||
range: TextRange::default(),
|
||||
})],
|
||||
value: Box::new(value.clone()),
|
||||
type_comment: None,
|
||||
range: TextRange::default(),
|
||||
});
|
||||
generator.stmt(&stmt)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user