Compare commits

..

1 Commits

Author SHA1 Message Date
Charlie Marsh
7e80f19223 [ty] Try eliminating ~AlwaysFalsy and ~AlwaysTruthy from intersections 2025-12-29 18:04:04 -05:00
246 changed files with 3031 additions and 8413 deletions

View File

@@ -794,7 +794,7 @@ jobs:
echo '```console' > "$GITHUB_STEP_SUMMARY"
# Enable color output for pre-commit and remove it for the summary
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
SKIP=cargo-fmt uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
SKIP=cargo-fmt,clippy,dev-generate-all uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
exit_code="${PIPESTATUS[0]}"
echo '```' >> "$GITHUB_STEP_SUMMARY"

View File

@@ -70,7 +70,7 @@ jobs:
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -86,7 +86,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -153,7 +153,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-build-global
path: |
@@ -200,7 +200,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest

View File

@@ -59,6 +59,9 @@ jobs:
- name: Compute diagnostic diff
shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"

View File

@@ -135,3 +135,6 @@ repos:
rev: v0.11.0.1
hooks:
- id: shellcheck
ci:
skip: [cargo-fmt, dev-generate-all]

View File

@@ -58,8 +58,8 @@ anstream = { version = "0.6.18" }
anstyle = { version = "1.0.10" }
anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
argfile = { version = "0.2.0" }
assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" }
bitvec = { version = "1.0.1", default-features = false, features = [
@@ -71,30 +71,30 @@ camino = { version = "1.1.7" }
clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
csv = { version = "1.3.1" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
colored = { version = "3.0.0" }
compact_str = "0.9.0"
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
compact_str = "0.9.0"
criterion = { version = "0.8.0", default-features = false }
crossbeam = { version = "0.8.4" }
csv = { version = "1.3.1" }
dashmap = { version = "6.0.1" }
datatest-stable = { version = "0.3.3" }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
drop_bomb = { version = "0.1.5" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
etcetera = { version = "0.11.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }
get-size2 = { version = "0.7.3", features = [
"derive",
"smallvec",
"hashbrown",
"compact-str",
] }
getrandom = { version = "0.3.1" }
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
globwalk = { version = "0.9.1" }
@@ -116,8 +116,8 @@ is-macro = { version = "0.3.5" }
is-wsl = { version = "0.4.0" }
itertools = { version = "0.14.0" }
jiff = { version = "0.2.0" }
jod-thread = { version = "1.0.0" }
js-sys = { version = "0.3.69" }
jod-thread = { version = "1.0.0" }
libc = { version = "0.2.153" }
libcst = { version = "1.8.4", default-features = false }
log = { version = "0.4.17" }
@@ -138,9 +138,9 @@ pep440_rs = { version = "0.7.1" }
pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.13.4" }
quick-junit = { version = "0.5.0" }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck = { version = "1.0.3", default-features = false}
quickcheck_macros = { version = "1.0.0" }
quick-junit = { version = "0.5.0" }
quote = { version = "1.0.23" }
rand = { version = "0.9.0" }
rayon = { version = "1.10.0" }
@@ -197,9 +197,9 @@ tryfn = { version = "0.2.1" }
typed-arena = { version = "2.0.2" }
unic-ucd-category = { version = "0.9" }
unicode-ident = { version = "1.0.12" }
unicode-normalization = { version = "0.1.23" }
unicode-width = { version = "0.2.0" }
unicode_names2 = { version = "1.2.2" }
unicode-normalization = { version = "0.1.23" }
url = { version = "2.5.0" }
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
walkdir = { version = "2.3.2" }
@@ -209,13 +209,8 @@ wild = { version = "2" }
zip = { version = "0.6.6", default-features = false }
[workspace.metadata.cargo-shear]
ignored = [
"getrandom",
"ruff_options_metadata",
"uuid",
"get-size2",
"ty_completion_eval",
]
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2", "ty_completion_eval"]
[workspace.lints.rust]
unsafe_code = "warn"
@@ -275,10 +270,17 @@ if_not_else = "allow"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
lto = "fat"
codegen-units = 16
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
# Some crates don't change as much but benefit more from
# more expensive optimization passes, so we selectively
# decrease codegen-units in some cases.
@@ -289,12 +291,6 @@ codegen-units = 1
[profile.release.package.salsa]
codegen-units = 1
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
[profile.dev.package.insta]
opt-level = 3

View File

@@ -12,13 +12,6 @@ license = { workspace = true }
readme = "../../README.md"
default-run = "ruff"
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true, default-features = false, features = ["os"] }
@@ -68,12 +61,6 @@ tracing = { workspace = true, features = ["log"] }
walkdir = { workspace = true }
wild = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[dev-dependencies]
# Enable test rules during development
ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
@@ -89,5 +76,18 @@ ruff_python_trivia = { workspace = true }
tempfile = { workspace = true }
test-case = { workspace = true }
[package.metadata.cargo-shear]
# Used via macro expansion.
ignored = ["jiff"]
[package.metadata.dist]
dist = true
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[lints]
workspace = true

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -127,7 +127,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -263,7 +263,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -129,7 +129,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -265,7 +265,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -129,7 +129,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -265,7 +265,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -126,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -262,7 +262,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -126,7 +126,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -262,7 +262,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -125,7 +125,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -261,7 +261,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -238,7 +238,7 @@ linter.flake8_copyright.notice_rgx = (?i)Copyright\s+((?:\(C\)|©)\s+)?\d{4}((-|
linter.flake8_copyright.author = none
linter.flake8_copyright.min_file_size = 0
linter.flake8_errmsg.max_string_length = 0
linter.flake8_gettext.function_names = [
linter.flake8_gettext.functions_names = [
_,
gettext,
ngettext,
@@ -374,7 +374,6 @@ linter.pylint.max_locals = 15
linter.pylint.max_nested_blocks = 5
linter.pyupgrade.keep_runtime_typing = false
linter.ruff.parenthesize_tuple_in_subscript = false
linter.ruff.strictly_empty_init_modules = false
# Formatter Settings
formatter.exclude = []

View File

@@ -12,6 +12,10 @@ license = "MIT OR Apache-2.0"
[lib]
[features]
default = []
testing-colors = []
[dependencies]
anstyle = { workspace = true }
memchr = { workspace = true }
@@ -19,17 +23,12 @@ unicode-width = { workspace = true }
[dev-dependencies]
ruff_annotate_snippets = { workspace = true, features = ["testing-colors"] }
anstream = { workspace = true }
serde = { workspace = true, features = ["derive"] }
snapbox = { workspace = true, features = ["diff", "term-svg", "cmd", "examples"] }
toml = { workspace = true }
tryfn = { workspace = true }
[features]
default = []
testing-colors = []
[[test]]
name = "fixtures"
harness = false

View File

@@ -16,51 +16,6 @@ bench = false
test = false
doctest = false
[dependencies]
ruff_db = { workspace = true, features = ["testing"] }
ruff_linter = { workspace = true, optional = true }
ruff_python_ast = { workspace = true }
ruff_python_formatter = { workspace = true, optional = true }
ruff_python_parser = { workspace = true, optional = true }
ruff_python_trivia = { workspace = true, optional = true }
ty_project = { workspace = true, optional = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
criterion = { workspace = true, default-features = false, optional = true }
divan = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true, optional = true }
[dev-dependencies]
rayon = { workspace = true }
rustc-hash = { workspace = true }
[features]
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
# Enables the ruff instrumented benchmarks
ruff_instrumented = [
"criterion",
"ruff_linter",
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"mimalloc",
"tikv-jemallocator",
]
# Enables the ty instrumented benchmarks
ty_instrumented = ["criterion", "ty_project", "ruff_python_trivia"]
codspeed = ["codspeed-criterion-compat"]
# Enables the ty_walltime benchmarks
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
[[bench]]
name = "linter"
harness = false
@@ -91,5 +46,54 @@ name = "ty_walltime"
harness = false
required-features = ["ty_walltime"]
[dependencies]
ruff_db = { workspace = true, features = ["testing"] }
ruff_python_ast = { workspace = true }
ruff_linter = { workspace = true, optional = true }
ruff_python_formatter = { workspace = true, optional = true }
ruff_python_parser = { workspace = true, optional = true }
ruff_python_trivia = { workspace = true, optional = true }
ty_project = { workspace = true, optional = true }
divan = { workspace = true, optional = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
criterion = { workspace = true, default-features = false, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
[lints]
workspace = true
[features]
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
# Enables the ruff instrumented benchmarks
ruff_instrumented = [
"criterion",
"ruff_linter",
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"mimalloc",
"tikv-jemallocator",
]
# Enables the ty instrumented benchmarks
ty_instrumented = [
"criterion",
"ty_project",
"ruff_python_trivia",
]
codspeed = ["codspeed-criterion-compat"]
# Enables the ty_walltime benchmarks
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true, optional = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true, optional = true }
[dev-dependencies]
rustc-hash = { workspace = true }
rayon = { workspace = true }

View File

@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13116,
13109,
);
static TANJUN: Benchmark = Benchmark::new(

View File

@@ -11,11 +11,11 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
filetime = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }
itertools = { workspace = true }
regex = { workspace = true }
filetime = { workspace = true }
seahash = { workspace = true }
[dev-dependencies]

View File

@@ -48,12 +48,12 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
zip = { workspace = true }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
etcetera = { workspace = true, optional = true }
[target.'cfg(target_arch="wasm32")'.dependencies]
web-time = { version = "1.1.0" }
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
etcetera = { workspace = true, optional = true }
[dev-dependencies]
insta = { workspace = true, features = ["filters"] }
tempfile = { workspace = true }

View File

@@ -11,6 +11,10 @@ repository = { workspace = true }
license = { workspace = true }
[dependencies]
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
ruff = { workspace = true }
ruff_formatter = { workspace = true }
ruff_linter = { workspace = true, features = ["schemars"] }
@@ -22,10 +26,6 @@ ruff_python_formatter = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_workspace = { workspace = true, features = ["schemars"] }
ty = { workspace = true }
ty_project = { workspace = true, features = ["schemars"] }
ty_python_semantic = { workspace = true }
ty_static = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["wrap_help"] }

View File

@@ -10,10 +10,6 @@ documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[dependencies]
ruff_cache = { workspace = true }
ruff_macros = { workspace = true }
@@ -29,6 +25,10 @@ unicode-width = { workspace = true }
[dev-dependencies]
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[features]
serde = ["dep:serde", "ruff_text_size/serde"]
schemars = ["dep:schemars", "ruff_text_size/schemars"]

View File

@@ -9,10 +9,6 @@ repository.workspace = true
authors.workspace = true
license.workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[dependencies]
ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["os", "serde"] }
@@ -33,3 +29,7 @@ zip = { workspace = true, features = [] }
[lints]
workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]

View File

@@ -16,17 +16,17 @@ license = { workspace = true }
ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["junit", "serde"] }
ruff_diagnostics = { workspace = true, features = ["serde"] }
ruff_macros = { workspace = true }
ruff_notebook = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true, features = ["serde", "cache"] }
ruff_python_codegen = { workspace = true }
ruff_python_importer = { workspace = true }
ruff_python_index = { workspace = true }
ruff_python_literal = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_semantic = { workspace = true }
ruff_python_stdlib = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_source_file = { workspace = true, features = ["serde"] }
ruff_text_size = { workspace = true }
@@ -44,8 +44,8 @@ imperative = { workspace = true }
is-macro = { workspace = true }
is-wsl = { workspace = true }
itertools = { workspace = true }
jiff = { workspace = true }
libcst = { workspace = true }
jiff = { workspace = true }
log = { workspace = true }
memchr = { workspace = true }
natord = { workspace = true }
@@ -67,17 +67,17 @@ strum_macros = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
typed-arena = { workspace = true }
unicode-normalization = { workspace = true }
unicode-width = { workspace = true }
unicode_names2 = { workspace = true }
unicode-normalization = { workspace = true }
url = { workspace = true }
[dev-dependencies]
insta = { workspace = true, features = ["filters", "json", "redactions"] }
test-case = { workspace = true }
# Disable colored output in tests
colored = { workspace = true, features = ["no-color"] }
insta = { workspace = true, features = ["filters", "json", "redactions"] }
tempfile = { workspace = true }
test-case = { workspace = true }
[features]
default = []

View File

@@ -1,31 +0,0 @@
from __future__ import annotations
from airflow.lineage.hook import HookLineageCollector
# airflow.lineage.hook
hlc = HookLineageCollector()
hlc.create_asset("there")
hlc.create_asset("should", "be", "no", "posarg")
hlc.create_asset(name="but", uri="kwargs are ok")
hlc.create_asset()
HookLineageCollector().create_asset(name="but", uri="kwargs are ok")
HookLineageCollector().create_asset("there")
HookLineageCollector().create_asset("should", "be", "no", "posarg")
args = ["uri_value"]
hlc.create_asset(*args)
HookLineageCollector().create_asset(*args)
# Literal unpacking
hlc.create_asset(*["literal_uri"])
HookLineageCollector().create_asset(*["literal_uri"])
# starred args with keyword args
hlc.create_asset(*args, extra="value")
HookLineageCollector().create_asset(*args, extra="value")
# Double-starred keyword arguments
kwargs = {"uri": "value", "name": "test"}
hlc.create_asset(**kwargs)
HookLineageCollector().create_asset(**kwargs)

View File

@@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import overload, cast, TypeVar
from typing import overload, cast
from typing_extensions import override
@@ -256,15 +256,3 @@ class C:
"""Docstring."""
msg = t"{x}..."
raise NotImplementedError(msg)
###
# Unused arguments with `**kwargs`.
###
def f(
default: object = None, # noqa: ARG001
**kwargs: object,
) -> None:
TypeVar(**kwargs)

View File

@@ -53,25 +53,3 @@ for i in items:
items = [1, 2, 3, 4]
for i in items:
items[i]
# A case with multiple uses of the value to show off the secondary annotations
for instrument in ORCHESTRA:
data = json.dumps(
{
"instrument": instrument,
"section": ORCHESTRA[instrument],
}
)
print(f"saving data for {instrument} in {ORCHESTRA[instrument]}")
with open(f"{instrument}/{ORCHESTRA[instrument]}.txt", "w") as f:
f.write(data)
# This should still suppress the error
for ( # noqa: PLC0206
instrument
) in ORCHESTRA:
print(f"{instrument}: {ORCHESTRA[instrument]}")

View File

@@ -73,7 +73,3 @@ foo == 1 or foo == 1.0 # Different types, same hashed value
foo == False or foo == 0 # Different types, same hashed value
foo == 0.0 or foo == 0j # Different types, same hashed value
foo == "bar" or foo == "bar" # All members identical
foo == "bar" or foo == "bar" or foo == "buzz" # All but one members identical

View File

@@ -1,51 +0,0 @@
"""This is the module docstring."""
# convenience imports:
import os
from pathlib import Path
__all__ = ["MY_CONSTANT"]
__all__ += ["foo"]
__all__: list[str] = __all__
__all__ = __all__ = __all__
MY_CONSTANT = 5
"""This is an important constant."""
os.environ["FOO"] = 1
def foo():
return Path("foo.py")
def __getattr__(name): # ok
return name
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
if os.environ["FOO"] != "1": # RUF067
MY_CONSTANT = 4 # ok, don't flag nested statements
if TYPE_CHECKING: # ok
MY_CONSTANT = 3
import typing
if typing.TYPE_CHECKING: # ok
MY_CONSTANT = 2
__version__ = "1.2.3" # ok
def __dir__(): # ok
return ["foo"]
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__) # ok
__path__ = unknown.extend_path(__path__, __name__) # also ok
# non-`extend_path` assignments are not allowed
__path__ = 5 # RUF067
# also allow `__author__`
__author__ = "The Author" # ok

View File

@@ -1,54 +0,0 @@
"""
The code here is not in an `__init__.py` file and should not trigger the
lint.
"""
# convenience imports:
import os
from pathlib import Path
__all__ = ["MY_CONSTANT"]
__all__ += ["foo"]
__all__: list[str] = __all__
__all__ = __all__ = __all__
MY_CONSTANT = 5
"""This is an important constant."""
os.environ["FOO"] = 1
def foo():
return Path("foo.py")
def __getattr__(name): # ok
return name
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
if os.environ["FOO"] != "1": # RUF067
MY_CONSTANT = 4 # ok, don't flag nested statements
if TYPE_CHECKING: # ok
MY_CONSTANT = 3
import typing
if typing.TYPE_CHECKING: # ok
MY_CONSTANT = 2
__version__ = "1.2.3" # ok
def __dir__(): # ok
return ["foo"]
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__) # ok
__path__ = unknown.extend_path(__path__, __name__) # also ok
# non-`extend_path` assignments are not allowed
__path__ = 5 # RUF067
# also allow `__author__`
__author__ = "The Author" # ok

View File

@@ -1043,7 +1043,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
]) && flake8_gettext::is_gettext_func_call(
checker,
func,
&checker.settings().flake8_gettext.function_names,
&checker.settings().flake8_gettext.functions_names,
) {
if checker.is_rule_enabled(Rule::FStringInGetTextFuncCall) {
flake8_gettext::rules::f_string_in_gettext_func_call(checker, args);
@@ -1278,9 +1278,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::Airflow3SuggestedUpdate) {
airflow::rules::airflow_3_0_suggested_update_expr(checker, expr);
}
if checker.is_rule_enabled(Rule::Airflow3IncompatibleFunctionSignature) {
airflow::rules::airflow_3_incompatible_function_signature(checker, expr);
}
if checker.is_rule_enabled(Rule::UnnecessaryCastToInt) {
ruff::rules::unnecessary_cast_to_int(checker, call);
}

View File

@@ -1630,7 +1630,4 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
_ => {}
}
if checker.is_rule_enabled(Rule::NonEmptyInitModule) {
ruff::rules::non_empty_init_module(checker, stmt);
}
}

View File

@@ -33,7 +33,7 @@ pub(crate) fn unresolved_references(checker: &Checker) {
}
// Allow __path__.
if checker.in_init_module() {
if checker.path.ends_with("__init__.py") {
if reference.name(checker.source()) == "__path__" {
continue;
}

View File

@@ -21,7 +21,7 @@
//! represents the lint-rule analysis phase. In the future, these steps may be separated into
//! distinct passes over the AST.
use std::cell::{OnceCell, RefCell};
use std::cell::RefCell;
use std::path::Path;
use itertools::Itertools;
@@ -198,8 +198,6 @@ pub(crate) struct Checker<'a> {
parsed_type_annotation: Option<&'a ParsedAnnotation>,
/// The [`Path`] to the file under analysis.
path: &'a Path,
/// Whether `path` points to an `__init__.py` file.
in_init_module: OnceCell<bool>,
/// The [`Path`] to the package containing the current file.
package: Option<PackageRoot<'a>>,
/// The module representation of the current file (e.g., `foo.bar`).
@@ -276,7 +274,6 @@ impl<'a> Checker<'a> {
noqa_line_for,
noqa,
path,
in_init_module: OnceCell::new(),
package,
module,
source_type,
@@ -485,11 +482,9 @@ impl<'a> Checker<'a> {
self.context.settings
}
/// Returns whether the file under analysis is an `__init__.py` file.
pub(crate) fn in_init_module(&self) -> bool {
*self
.in_init_module
.get_or_init(|| self.path.ends_with("__init__.py"))
/// The [`Path`] to the file under analysis.
pub(crate) const fn path(&self) -> &'a Path {
self.path
}
/// The [`Path`] to the package containing the current file.
@@ -1878,9 +1873,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
} else {
self.visit_non_type_definition(value);
}
} else {
// Ex: typing.TypeVar(**kwargs)
self.visit_non_type_definition(value);
}
}
}
@@ -3179,7 +3171,7 @@ impl<'a> Checker<'a> {
// F822
if self.is_rule_enabled(Rule::UndefinedExport) {
if is_undefined_export_in_dunder_init_enabled(self.settings())
|| !self.in_init_module()
|| !self.path.ends_with("__init__.py")
{
self.report_diagnostic(
pyflakes::rules::UndefinedExport {

View File

@@ -1060,7 +1060,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "064") => rules::ruff::rules::NonOctalPermissions,
(Ruff, "065") => rules::ruff::rules::LoggingEagerConversion,
(Ruff, "066") => rules::ruff::rules::PropertyWithoutReturn,
(Ruff, "067") => rules::ruff::rules::NonEmptyInitModule,
(Ruff, "100") => rules::ruff::rules::UnusedNOQA,
(Ruff, "101") => rules::ruff::rules::RedirectedNOQA,
@@ -1124,7 +1123,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Airflow, "002") => rules::airflow::rules::AirflowDagNoScheduleArgument,
(Airflow, "301") => rules::airflow::rules::Airflow3Removal,
(Airflow, "302") => rules::airflow::rules::Airflow3MovedToProvider,
(Airflow, "303") => rules::airflow::rules::Airflow3IncompatibleFunctionSignature,
(Airflow, "311") => rules::airflow::rules::Airflow3SuggestedUpdate,
(Airflow, "312") => rules::airflow::rules::Airflow3SuggestedToMoveToProvider,

View File

@@ -47,7 +47,6 @@ mod tests {
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_zendesk.py"))]
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_standard.py"))]
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR302_try.py"))]
#[test_case(Rule::Airflow3IncompatibleFunctionSignature, Path::new("AIR303.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_args.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_names.py"))]
#[test_case(Rule::Airflow3SuggestedUpdate, Path::new("AIR311_try.py"))]

View File

@@ -1,128 +0,0 @@
use crate::checkers::ast::Checker;
use crate::{FixAvailability, Violation};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{Arguments, Expr, ExprAttribute, ExprCall, Identifier};
use ruff_python_semantic::Modules;
use ruff_python_semantic::analyze::typing;
use ruff_text_size::Ranged;
/// ## What it does
/// Checks for Airflow function calls that will raise a runtime error in Airflow 3.0
/// due to function signature changes, such as functions that changed to accept only
/// keyword arguments, parameter reordering, or parameter type changes.
///
/// ## Why is this bad?
/// Airflow 3.0 introduces changes to function signatures. Code that
/// worked in Airflow 2.x will raise a runtime error if not updated in Airflow
/// 3.0.
///
/// ## Example
/// ```python
/// from airflow.lineage.hook import HookLineageCollector
///
/// collector = HookLineageCollector()
/// # Passing positional arguments will raise a runtime error in Airflow 3.0
/// collector.create_asset("s3://bucket/key")
/// ```
///
/// Use instead:
/// ```python
/// from airflow.lineage.hook import HookLineageCollector
///
/// collector = HookLineageCollector()
/// # Passing arguments as keyword arguments instead of positional arguments
/// collector.create_asset(uri="s3://bucket/key")
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.14.11")]
pub(crate) struct Airflow3IncompatibleFunctionSignature {
function_name: String,
change_type: FunctionSignatureChangeType,
}
impl Violation for Airflow3IncompatibleFunctionSignature {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
#[derive_message_formats]
fn message(&self) -> String {
let Airflow3IncompatibleFunctionSignature {
function_name,
change_type,
} = self;
match change_type {
FunctionSignatureChangeType::KeywordOnly { .. } => {
format!("`{function_name}` signature is changed in Airflow 3.0")
}
}
}
fn fix_title(&self) -> Option<String> {
let Airflow3IncompatibleFunctionSignature { change_type, .. } = self;
match change_type {
FunctionSignatureChangeType::KeywordOnly { message } => Some(message.to_string()),
}
}
}
/// AIR303
pub(crate) fn airflow_3_incompatible_function_signature(checker: &Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
let Expr::Call(ExprCall {
func, arguments, ..
}) = expr
else {
return;
};
let Expr::Attribute(ExprAttribute { attr, value, .. }) = func.as_ref() else {
return;
};
// Resolve the qualified name: try variable assignments first, then fall back to direct
// constructor calls.
let qualified_name = typing::resolve_assignment(value, checker.semantic()).or_else(|| {
value
.as_call_expr()
.and_then(|call| checker.semantic().resolve_qualified_name(&call.func))
});
let Some(qualified_name) = qualified_name else {
return;
};
check_keyword_only_method(checker, &qualified_name, attr, arguments);
}
fn check_keyword_only_method(
checker: &Checker,
qualified_name: &QualifiedName,
attr: &Identifier,
arguments: &Arguments,
) {
let has_positional_args =
arguments.find_positional(0).is_some() || arguments.args.iter().any(Expr::is_starred_expr);
if let ["airflow", "lineage", "hook", "HookLineageCollector"] = qualified_name.segments() {
if attr.as_str() == "create_asset" && has_positional_args {
checker.report_diagnostic(
Airflow3IncompatibleFunctionSignature {
function_name: attr.to_string(),
change_type: FunctionSignatureChangeType::KeywordOnly {
message: "Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)",
},
},
attr.range(),
);
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum FunctionSignatureChangeType {
/// Function signature changed to only accept keyword arguments.
KeywordOnly { message: &'static str },
}

View File

@@ -1,5 +1,4 @@
pub(crate) use dag_schedule_argument::*;
pub(crate) use function_signature_change_in_3::*;
pub(crate) use moved_to_provider_in_3::*;
pub(crate) use removal_in_3::*;
pub(crate) use suggested_to_move_to_provider_in_3::*;
@@ -7,7 +6,6 @@ pub(crate) use suggested_to_update_3_0::*;
pub(crate) use task_variable_name::*;
mod dag_schedule_argument;
mod function_signature_change_in_3;
mod moved_to_provider_in_3;
mod removal_in_3;
mod suggested_to_move_to_provider_in_3;

View File

@@ -1,114 +0,0 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:7:5
|
5 | # airflow.lineage.hook
6 | hlc = HookLineageCollector()
7 | hlc.create_asset("there")
| ^^^^^^^^^^^^
8 | hlc.create_asset("should", "be", "no", "posarg")
9 | hlc.create_asset(name="but", uri="kwargs are ok")
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:8:5
|
6 | hlc = HookLineageCollector()
7 | hlc.create_asset("there")
8 | hlc.create_asset("should", "be", "no", "posarg")
| ^^^^^^^^^^^^
9 | hlc.create_asset(name="but", uri="kwargs are ok")
10 | hlc.create_asset()
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:13:24
|
12 | HookLineageCollector().create_asset(name="but", uri="kwargs are ok")
13 | HookLineageCollector().create_asset("there")
| ^^^^^^^^^^^^
14 | HookLineageCollector().create_asset("should", "be", "no", "posarg")
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:14:24
|
12 | HookLineageCollector().create_asset(name="but", uri="kwargs are ok")
13 | HookLineageCollector().create_asset("there")
14 | HookLineageCollector().create_asset("should", "be", "no", "posarg")
| ^^^^^^^^^^^^
15 |
16 | args = ["uri_value"]
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:17:5
|
16 | args = ["uri_value"]
17 | hlc.create_asset(*args)
| ^^^^^^^^^^^^
18 | HookLineageCollector().create_asset(*args)
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:18:24
|
16 | args = ["uri_value"]
17 | hlc.create_asset(*args)
18 | HookLineageCollector().create_asset(*args)
| ^^^^^^^^^^^^
19 |
20 | # Literal unpacking
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:21:5
|
20 | # Literal unpacking
21 | hlc.create_asset(*["literal_uri"])
| ^^^^^^^^^^^^
22 | HookLineageCollector().create_asset(*["literal_uri"])
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:22:24
|
20 | # Literal unpacking
21 | hlc.create_asset(*["literal_uri"])
22 | HookLineageCollector().create_asset(*["literal_uri"])
| ^^^^^^^^^^^^
23 |
24 | # starred args with keyword args
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:25:5
|
24 | # starred args with keyword args
25 | hlc.create_asset(*args, extra="value")
| ^^^^^^^^^^^^
26 | HookLineageCollector().create_asset(*args, extra="value")
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)
AIR303 `create_asset` signature is changed in Airflow 3.0
--> AIR303.py:26:24
|
24 | # starred args with keyword args
25 | hlc.create_asset(*args, extra="value")
26 | HookLineageCollector().create_asset(*args, extra="value")
| ^^^^^^^^^^^^
27 |
28 | # Double-starred keyword arguments
|
help: Pass positional arguments as keyword arguments (e.g., `create_asset(uri=...)`)

View File

@@ -1,8 +1,6 @@
use ruff_db::diagnostic::Diagnostic;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
use crate::checkers::ast::Checker;
use crate::settings::LinterSettings;
@@ -193,27 +191,3 @@ pub(super) fn allow_boolean_trap(call: &ast::ExprCall, checker: &Checker) -> boo
false
}
pub(super) fn add_liskov_substitution_principle_help(
diagnostic: &mut Diagnostic,
function_name: &str,
decorator_list: &[ast::Decorator],
checker: &Checker,
) {
let semantic = checker.semantic();
let parent_scope = semantic.current_scope();
let pep8_settings = &checker.settings().pep8_naming;
if is_subject_to_liskov_substitution_principle(
function_name,
decorator_list,
parent_scope,
semantic,
&pep8_settings.classmethod_decorators,
&pep8_settings.staticmethod_decorators,
) {
diagnostic.help(
"Consider adding `@typing.override` if changing the function signature \
would violate the Liskov Substitution Principle",
);
}
}

View File

@@ -6,9 +6,7 @@ use ruff_python_semantic::analyze::visibility;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::rules::flake8_boolean_trap::helpers::{
add_liskov_substitution_principle_help, is_allowed_func_def,
};
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// ## What it does
/// Checks for the use of boolean positional arguments in function definitions,
@@ -141,9 +139,7 @@ pub(crate) fn boolean_default_value_positional_argument(
return;
}
let mut diagnostic = checker
.report_diagnostic(BooleanDefaultValuePositionalArgument, param.identifier());
add_liskov_substitution_principle_help(&mut diagnostic, name, decorator_list, checker);
checker.report_diagnostic(BooleanDefaultValuePositionalArgument, param.identifier());
}
}
}

View File

@@ -7,9 +7,7 @@ use ruff_python_semantic::analyze::visibility;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::rules::flake8_boolean_trap::helpers::{
add_liskov_substitution_principle_help, is_allowed_func_def,
};
use crate::rules::flake8_boolean_trap::helpers::is_allowed_func_def;
/// ## What it does
/// Checks for the use of boolean positional arguments in function definitions,
@@ -151,10 +149,7 @@ pub(crate) fn boolean_type_hint_positional_argument(
return;
}
let mut diagnostic =
checker.report_diagnostic(BooleanTypeHintPositionalArgument, parameter.identifier());
add_liskov_substitution_principle_help(&mut diagnostic, name, decorator_list, checker);
checker.report_diagnostic(BooleanTypeHintPositionalArgument, parameter.identifier());
}
}

View File

@@ -97,7 +97,6 @@ FBT001 Boolean-typed positional argument in function definition
| ^^^^^
91 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
FBT001 Boolean-typed positional argument in function definition
--> FBT.py:100:10

View File

@@ -38,10 +38,6 @@ use crate::checkers::ast::Checker;
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
/// ```
///
/// ## Options
///
/// - `lint.flake8-gettext.function-names`
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
#[derive(ViolationMetadata)]

View File

@@ -38,10 +38,6 @@ use crate::checkers::ast::Checker;
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
/// ```
///
/// ## Options
///
/// - `lint.flake8-gettext.function-names`
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
#[derive(ViolationMetadata)]

View File

@@ -37,10 +37,6 @@ use crate::checkers::ast::Checker;
/// _("Hello, %s!") % name # Looks for "Hello, %s!".
/// ```
///
/// ## Options
///
/// - `lint.flake8-gettext.function-names`
///
/// ## References
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
#[derive(ViolationMetadata)]

View File

@@ -5,7 +5,7 @@ use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey)]
pub struct Settings {
pub function_names: Vec<Name>,
pub functions_names: Vec<Name>,
}
pub fn default_func_names() -> Vec<Name> {
@@ -19,7 +19,7 @@ pub fn default_func_names() -> Vec<Name> {
impl Default for Settings {
fn default() -> Self {
Self {
function_names: default_func_names(),
functions_names: default_func_names(),
}
}
}
@@ -30,7 +30,7 @@ impl Display for Settings {
formatter = f,
namespace = "linter.flake8_gettext",
fields = [
self.function_names | array
self.functions_names | array
]
}
Ok(())

View File

@@ -32,13 +32,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// "dog"
/// )
/// ```
///
/// ## Options
///
/// Setting `lint.flake8-implicit-str-concat.allow-multiline = false` will disable this rule because
/// it would leave no allowed way to write a multi-line string.
///
/// - `lint.flake8-implicit-str-concat.allow-multiline`
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.201")]
pub(crate) struct ExplicitStringConcatenation;

View File

@@ -11,7 +11,7 @@ use crate::{FixAvailability, Violation};
///
/// ## Why is this bad?
/// `ByteString` has been deprecated since Python 3.9 and will be removed in
/// Python 3.17. The Python documentation recommends using either
/// Python 3.14. The Python documentation recommends using either
/// `collections.abc.Buffer` (or the `typing_extensions` backport
/// on Python <3.12) or a union like `bytes | bytearray | memoryview` instead.
///

View File

@@ -130,16 +130,10 @@ pub(crate) fn invalid_function_name(
return;
}
let mut diagnostic = checker.report_diagnostic(
checker.report_diagnostic(
InvalidFunctionName {
name: name.to_string(),
},
stmt.identifier(),
);
if parent_class.is_some() {
diagnostic.help(
"Consider adding `@typing.override` if this method \
overrides a method from a superclass",
);
}
}

View File

@@ -36,10 +36,6 @@ use crate::rules::pep8_naming::settings::IgnoreNames;
/// - Instead of `example-module-name` or `example module name`, use `example_module_name`.
/// - Instead of `ExampleModule`, use `example_module`.
///
/// ## Options
///
/// - `lint.pep8-naming.ignore-names`
///
/// [PEP 8]: https://peps.python.org/pep-0008/#package-and-module-names
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.248")]

View File

@@ -42,7 +42,6 @@ N802 Function name `testTest` should be lowercase
| ^^^^^^^^
41 | assert True
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
N802 Function name `bad_Name` should be lowercase
--> N802.py:65:9
@@ -53,7 +52,6 @@ N802 Function name `bad_Name` should be lowercase
| ^^^^^^^^
66 | pass
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
N802 Function name `dont_GET` should be lowercase
--> N802.py:84:9
@@ -64,7 +62,6 @@ N802 Function name `dont_GET` should be lowercase
| ^^^^^^^^
85 | pass
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
N802 Function name `dont_OPTIONS` should be lowercase
--> N802.py:95:9
@@ -75,7 +72,6 @@ N802 Function name `dont_OPTIONS` should be lowercase
| ^^^^^^^^^^^^
96 | pass
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
N802 Function name `dont_OPTIONS` should be lowercase
--> N802.py:106:9
@@ -86,4 +82,3 @@ N802 Function name `dont_OPTIONS` should be lowercase
| ^^^^^^^^^^^^
107 | pass
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass

View File

@@ -20,4 +20,3 @@ N802 Function name `stillBad` should be lowercase
| ^^^^^^^^
14 | return super().tearDown()
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass

View File

@@ -58,11 +58,6 @@ use crate::rules::pydocstyle::settings::Convention;
/// """
/// return distance / time
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.14.1")]
pub(crate) struct DocstringExtraneousParameter {
@@ -118,12 +113,6 @@ impl Violation for DocstringExtraneousParameter {
/// """
/// return distance / time
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
/// - `lint.pydocstyle.property-decorators`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.6")]
pub(crate) struct DocstringMissingReturns;
@@ -176,11 +165,6 @@ impl Violation for DocstringMissingReturns {
/// for _ in range(n):
/// print("Hello!")
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.6")]
pub(crate) struct DocstringExtraneousReturns;
@@ -234,11 +218,6 @@ impl Violation for DocstringExtraneousReturns {
/// for i in range(1, n + 1):
/// yield i
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.7")]
pub(crate) struct DocstringMissingYields;
@@ -291,11 +270,6 @@ impl Violation for DocstringMissingYields {
/// for _ in range(n):
/// print("Hello!")
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.7")]
pub(crate) struct DocstringExtraneousYields;
@@ -368,11 +342,6 @@ impl Violation for DocstringExtraneousYields {
/// except ZeroDivisionError as exc:
/// raise FasterThanLightError from exc
/// ```
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.5")]
pub(crate) struct DocstringMissingException {
@@ -441,11 +410,6 @@ impl Violation for DocstringMissingException {
/// It may often be desirable to document *all* exceptions that a function
/// could possibly raise, even those which are not explicitly raised using
/// `raise` statements in the function body.
///
/// ## Options
///
/// - `lint.pydoclint.ignore-one-line-docstrings`
/// - `lint.pydocstyle.convention`
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.5.5")]
pub(crate) struct DocstringExtraneousException {

View File

@@ -38,10 +38,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// foobar.__doc__ # "Docstring for foo\bar."
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [Python documentation: String and Bytes literals](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals)

View File

@@ -34,10 +34,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// """
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -33,10 +33,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
@@ -84,10 +80,6 @@ impl Violation for BlankLineBeforeFunction {
/// return sum(values) / len(values)
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -25,10 +25,6 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -63,10 +63,6 @@ use crate::docstrings::Docstring;
/// factorial.__doc__ # "Return the factorial of n."
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [Python documentation: `typing.overload`](https://docs.python.org/3/library/typing.html#typing.overload)

View File

@@ -44,10 +44,6 @@ use crate::{Edit, Fix};
/// The rule is also incompatible with the [formatter] when using
/// `format.indent-style="tab"`.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
@@ -97,10 +93,6 @@ impl Violation for DocstringTabIndentation {
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent indentation, making the rule redundant.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
@@ -154,10 +146,6 @@ impl AlwaysFixableViolation for UnderIndentation {
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent indentation, making the rule redundant.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -37,10 +37,6 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
/// """
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -27,10 +27,6 @@ use crate::rules::pydocstyle::helpers::ends_with_backslash;
/// """Return the factorial of n."""
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -24,10 +24,6 @@ use crate::docstrings::Docstring;
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -55,10 +55,6 @@ use crate::checkers::ast::Checker;
/// ## Notebook behavior
/// This rule is ignored for Jupyter Notebooks.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
@@ -143,10 +139,6 @@ impl Violation for UndocumentedPublicModule {
/// self.points += points
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
@@ -374,10 +366,6 @@ impl Violation for UndocumentedPublicFunction {
/// __all__ = ["player", "game"]
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
@@ -492,10 +480,6 @@ impl Violation for UndocumentedMagicMethod {
/// bar.__doc__ # "Class Bar."
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)

View File

@@ -32,10 +32,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// documentation generators, or custom introspection utilities that rely on
/// specific docstring formatting.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
///

View File

@@ -1066,10 +1066,6 @@ impl AlwaysFixableViolation for MissingBlankLineAfterLastSection {
/// raise FasterThanLightError from exc
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
@@ -1321,10 +1317,6 @@ impl Violation for UndocumentedParam {
/// raise FasterThanLightError from exc
/// ```
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)

View File

@@ -31,10 +31,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// We recommend against using this rule alongside the [formatter]. The
/// formatter enforces consistent quotes, making the rule redundant.
///
/// ## Options
///
/// - `lint.pydocstyle.ignore-decorators`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)

View File

@@ -389,7 +389,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope) {
}
}
let in_init = checker.in_init_module();
let in_init = checker.path().ends_with("__init__.py");
let fix_init = !checker.settings().ignore_init_module_imports;
let preview_mode = is_dunder_init_fix_unused_import_enabled(checker.settings());
let dunder_all_exprs = find_dunder_all_exprs(checker.semantic());

View File

@@ -1,17 +1,16 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::{
self as ast, Expr, ExprContext, StmtFor,
token::parenthesized_range,
self as ast, Expr, ExprContext,
visitor::{self, Visitor},
};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType};
use ruff_python_semantic::analyze::typing::is_dict;
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::Ranged;
use crate::Violation;
use crate::checkers::ast::{Checker, DiagnosticGuard};
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for dictionary iterations that extract the dictionary value
@@ -48,26 +47,20 @@ use crate::checkers::ast::{Checker, DiagnosticGuard};
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.8.0")]
pub(crate) struct DictIndexMissingItems<'a> {
key: &'a str,
dict: &'a str,
}
pub(crate) struct DictIndexMissingItems;
impl Violation for DictIndexMissingItems<'_> {
impl Violation for DictIndexMissingItems {
#[derive_message_formats]
fn message(&self) -> String {
"Extracting value from dictionary without calling `.items()`".to_string()
}
fn fix_title(&self) -> Option<String> {
let Self { key, dict } = self;
Some(format!("Use `for {key}, value in {dict}.items()` instead"))
}
}
/// PLC0206
pub(crate) fn dict_index_missing_items(checker: &Checker, stmt_for: &StmtFor) {
let StmtFor { iter, body, .. } = stmt_for;
pub(crate) fn dict_index_missing_items(checker: &Checker, stmt_for: &ast::StmtFor) {
let ast::StmtFor {
target, iter, body, ..
} = stmt_for;
// Extract the name of the iteration object (e.g., `obj` in `for key in obj:`).
let Some(dict_name) = extract_dict_name(iter) else {
@@ -84,46 +77,40 @@ pub(crate) fn dict_index_missing_items(checker: &Checker, stmt_for: &StmtFor) {
return;
}
SubscriptVisitor::new(stmt_for, dict_name, checker).visit_body(body);
let has_violation = {
let mut visitor = SubscriptVisitor::new(target, dict_name);
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.has_violation
};
if has_violation {
checker.report_diagnostic(DictIndexMissingItems, stmt_for.range());
}
}
/// A visitor to detect subscript operations on a target dictionary.
struct SubscriptVisitor<'a, 'b> {
struct SubscriptVisitor<'a> {
/// The target of the for loop (e.g., `key` in `for key in obj:`).
target: &'a Expr,
/// The name of the iterated object (e.g., `obj` in `for key in obj:`).
dict_name: &'a ast::ExprName,
/// The range to use for the primary diagnostic.
range: TextRange,
/// The [`Checker`] used to emit diagnostics.
checker: &'a Checker<'b>,
/// The [`DiagnosticGuard`] used to attach additional annotations for each subscript.
///
/// The guard is initially `None` and then set to `Some` when the first subscript is found.
guard: Option<DiagnosticGuard<'a, 'b>>,
/// Whether a violation has been detected.
has_violation: bool,
}
impl<'a, 'b> SubscriptVisitor<'a, 'b> {
fn new(stmt_for: &'a StmtFor, dict_name: &'a ast::ExprName, checker: &'a Checker<'b>) -> Self {
let StmtFor { target, iter, .. } = stmt_for;
let range = {
let target_start =
parenthesized_range(target.into(), stmt_for.into(), checker.tokens())
.map_or(target.start(), TextRange::start);
TextRange::new(target_start, iter.end())
};
impl<'a> SubscriptVisitor<'a> {
fn new(target: &'a Expr, dict_name: &'a ast::ExprName) -> Self {
Self {
target,
dict_name,
range,
checker,
guard: None,
has_violation: false,
}
}
}
impl<'a> Visitor<'a> for SubscriptVisitor<'a, '_> {
impl<'a> Visitor<'a> for SubscriptVisitor<'a> {
fn visit_expr(&mut self, expr: &'a Expr) {
// Given `obj[key]`, `value` must be `obj` and `slice` must be `key`.
if let Expr::Subscript(ast::ExprSubscript {
@@ -147,17 +134,7 @@ impl<'a> Visitor<'a> for SubscriptVisitor<'a, '_> {
return;
}
let guard = self.guard.get_or_insert_with(|| {
self.checker.report_diagnostic(
DictIndexMissingItems {
key: self.checker.locator().slice(self.target),
dict: self.checker.locator().slice(self.dict_name),
},
self.range,
)
});
guard.secondary_annotation("", expr);
self.has_violation = true;
} else {
visitor::walk_expr(self, expr);
}

View File

@@ -146,15 +146,11 @@ pub(crate) fn no_self_use(checker: &Checker, scope_id: ScopeId, scope: &Scope) {
.map(|binding_id| semantic.binding(binding_id))
.is_some_and(|binding| binding.kind.is_argument() && binding.is_unused())
{
let mut diagnostic = checker.report_diagnostic(
checker.report_diagnostic(
NoSelfUse {
method_name: name.to_string(),
},
func.identifier(),
);
diagnostic.help(
"Consider adding `@typing.override` if this method overrides \
a method from a superclass",
);
}
}

View File

@@ -140,18 +140,6 @@ pub(crate) fn repeated_equality_comparison(checker: &Checker, bool_op: &ast::Exp
continue;
}
if let Some((&first, rest)) = comparators.split_first() {
let first_comparable = ComparableExpr::from(first);
if rest
.iter()
.all(|&c| ComparableExpr::from(c) == first_comparable)
{
// Do not flag if all members are identical
continue;
}
}
// if we can determine that all the values are hashable, we can use a set
// TODO: improve with type inference
let all_hashable = comparators

View File

@@ -1,7 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
use ruff_python_semantic::analyze::{function_type, visibility};
use crate::Violation;
@@ -122,24 +121,11 @@ pub(crate) fn too_many_arguments(checker: &Checker, function_def: &ast::StmtFunc
return;
}
let mut diagnostic = checker.report_diagnostic(
checker.report_diagnostic(
TooManyArguments {
c_args: num_arguments,
max_args: checker.settings().pylint.max_args,
},
function_def.identifier(),
);
if is_subject_to_liskov_substitution_principle(
&function_def.name,
&function_def.decorator_list,
semantic.current_scope(),
semantic,
&checker.settings().pep8_naming.classmethod_decorators,
&checker.settings().pep8_naming.staticmethod_decorators,
) {
diagnostic.help(
"Consider adding `@typing.override` if changing the function signature \
would violate the Liskov Substitution Principle",
);
}
}

View File

@@ -1,6 +1,5 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, identifier::Identifier};
use ruff_python_semantic::analyze::function_type::is_subject_to_liskov_substitution_principle;
use ruff_python_semantic::analyze::{function_type, visibility};
use crate::Violation;
@@ -126,24 +125,11 @@ pub(crate) fn too_many_positional_arguments(
return;
}
let mut diagnostic = checker.report_diagnostic(
checker.report_diagnostic(
TooManyPositionalArguments {
c_pos: num_positional_args,
max_pos: checker.settings().pylint.max_positional_args,
},
function_def.identifier(),
);
if is_subject_to_liskov_substitution_principle(
&function_def.name,
&function_def.decorator_list,
semantic.current_scope(),
semantic,
&checker.settings().pep8_naming.classmethod_decorators,
&checker.settings().pep8_naming.staticmethod_decorators,
) {
diagnostic.help(
"Consider adding `@typing.override` if changing the function signature \
would violate the Liskov Substitution Principle",
);
}
}

View File

@@ -77,7 +77,7 @@ pub(crate) fn useless_import_alias(checker: &Checker, alias: &Alias) {
}
// A re-export in __init__.py is probably intentional.
if checker.in_init_module() {
if checker.path().ends_with("__init__.py") {
return;
}
@@ -116,7 +116,7 @@ pub(crate) fn useless_import_from_alias(
}
// A re-export in __init__.py is probably intentional.
if checker.in_init_module() {
if checker.path().ends_with("__init__.py") {
return;
}

View File

@@ -2,107 +2,72 @@
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:9:5
--> dict_index_missing_items.py:9:1
|
8 | # Errors
9 | for instrument in ORCHESTRA:
| ^^^^^^^^^^^^^^^^^^^^^^^
10 | print(f"{instrument}: {ORCHESTRA[instrument]}")
| ---------------------
8 | # Errors
9 | / for instrument in ORCHESTRA:
10 | | print(f"{instrument}: {ORCHESTRA[instrument]}")
| |___________________________________________________^
11 |
12 | for instrument in ORCHESTRA:
12 | for instrument in ORCHESTRA:
|
help: Use `for instrument, value in ORCHESTRA.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:12:5
--> dict_index_missing_items.py:12:1
|
10 | print(f"{instrument}: {ORCHESTRA[instrument]}")
10 | print(f"{instrument}: {ORCHESTRA[instrument]}")
11 |
12 | for instrument in ORCHESTRA:
| ^^^^^^^^^^^^^^^^^^^^^^^
13 | ORCHESTRA[instrument]
| ---------------------
12 | / for instrument in ORCHESTRA:
13 | | ORCHESTRA[instrument]
| |_________________________^
14 |
15 | for instrument in ORCHESTRA.keys():
15 | for instrument in ORCHESTRA.keys():
|
help: Use `for instrument, value in ORCHESTRA.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:15:5
--> dict_index_missing_items.py:15:1
|
13 | ORCHESTRA[instrument]
13 | ORCHESTRA[instrument]
14 |
15 | for instrument in ORCHESTRA.keys():
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16 | print(f"{instrument}: {ORCHESTRA[instrument]}")
| ---------------------
15 | / for instrument in ORCHESTRA.keys():
16 | | print(f"{instrument}: {ORCHESTRA[instrument]}")
| |___________________________________________________^
17 |
18 | for instrument in ORCHESTRA.keys():
18 | for instrument in ORCHESTRA.keys():
|
help: Use `for instrument, value in ORCHESTRA.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:18:5
--> dict_index_missing_items.py:18:1
|
16 | print(f"{instrument}: {ORCHESTRA[instrument]}")
16 | print(f"{instrument}: {ORCHESTRA[instrument]}")
17 |
18 | for instrument in ORCHESTRA.keys():
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 | ORCHESTRA[instrument]
| ---------------------
18 | / for instrument in ORCHESTRA.keys():
19 | | ORCHESTRA[instrument]
| |_________________________^
20 |
21 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
21 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
|
help: Use `for instrument, value in ORCHESTRA.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:21:5
--> dict_index_missing_items.py:21:1
|
19 | ORCHESTRA[instrument]
19 | ORCHESTRA[instrument]
20 |
21 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22 | print(f"{instrument}: {temp_orchestra[instrument]}")
| --------------------------
21 | / for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
22 | | print(f"{instrument}: {temp_orchestra[instrument]}")
| |________________________________________________________^
23 |
24 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
24 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
|
help: Use `for instrument, value in temp_orchestra.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:24:5
--> dict_index_missing_items.py:24:1
|
22 | print(f"{instrument}: {temp_orchestra[instrument]}")
22 | print(f"{instrument}: {temp_orchestra[instrument]}")
23 |
24 | for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25 | temp_orchestra[instrument]
| --------------------------
24 | / for instrument in (temp_orchestra := {"violin": "strings", "oboe": "woodwind"}):
25 | | temp_orchestra[instrument]
| |______________________________^
26 |
27 | # # OK
27 | # # OK
|
help: Use `for instrument, value in temp_orchestra.items()` instead
PLC0206 Extracting value from dictionary without calling `.items()`
--> dict_index_missing_items.py:59:5
|
58 | # A case with multiple uses of the value to show off the secondary annotations
59 | for instrument in ORCHESTRA:
| ^^^^^^^^^^^^^^^^^^^^^^^
60 | data = json.dumps(
61 | {
62 | "instrument": instrument,
63 | "section": ORCHESTRA[instrument],
| ---------------------
64 | }
65 | )
66 |
67 | print(f"saving data for {instrument} in {ORCHESTRA[instrument]}")
| ---------------------
68 |
69 | with open(f"{instrument}/{ORCHESTRA[instrument]}.txt", "w") as f:
| ---------------------
70 | f.write(data)
|
help: Use `for instrument, value in ORCHESTRA.items()` instead

View File

@@ -49,7 +49,6 @@ PLR0913 Too many arguments in function definition (8 > 5)
| ^
52 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
PLR0913 Too many arguments in function definition (8 > 5)
--> too_many_arguments.py:58:9
@@ -59,7 +58,6 @@ PLR0913 Too many arguments in function definition (8 > 5)
| ^
59 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
PLR0913 Too many arguments in function definition (8 > 5)
--> too_many_arguments.py:66:9
@@ -69,7 +67,6 @@ PLR0913 Too many arguments in function definition (8 > 5)
| ^
67 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
PLR0913 Too many arguments in function definition (6 > 5)
--> too_many_arguments.py:70:9
@@ -79,4 +76,3 @@ PLR0913 Too many arguments in function definition (6 > 5)
| ^
71 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle

View File

@@ -34,7 +34,6 @@ PLR0917 Too many positional arguments (6/5)
| ^
44 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle
PLR0917 Too many positional arguments (6/5)
--> too_many_positional_arguments.py:47:9
@@ -44,4 +43,3 @@ PLR0917 Too many positional arguments (6/5)
| ^
48 | pass
|
help: Consider adding `@typing.override` if changing the function signature would violate the Liskov Substitution Principle

View File

@@ -436,7 +436,6 @@ help: Merge multiple comparisons
73 + foo in {False, 0} # Different types, same hashed value
74 |
75 | foo == 0.0 or foo == 0j # Different types, same hashed value
76 |
note: This is an unsafe fix and may change runtime behavior
PLR1714 [*] Consider merging multiple comparisons: `foo in {0.0, 0j}`.
@@ -446,8 +445,6 @@ PLR1714 [*] Consider merging multiple comparisons: `foo in {0.0, 0j}`.
74 |
75 | foo == 0.0 or foo == 0j # Different types, same hashed value
| ^^^^^^^^^^^^^^^^^^^^^^^
76 |
77 | foo == "bar" or foo == "bar" # All members identical
|
help: Merge multiple comparisons
72 |
@@ -455,23 +452,4 @@ help: Merge multiple comparisons
74 |
- foo == 0.0 or foo == 0j # Different types, same hashed value
75 + foo in {0.0, 0j} # Different types, same hashed value
76 |
77 | foo == "bar" or foo == "bar" # All members identical
78 |
note: This is an unsafe fix and may change runtime behavior
PLR1714 [*] Consider merging multiple comparisons: `foo in {"bar", "bar", "buzz"}`.
--> repeated_equality_comparison.py:79:1
|
77 | foo == "bar" or foo == "bar" # All members identical
78 |
79 | foo == "bar" or foo == "bar" or foo == "buzz" # All but one members identical
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Merge multiple comparisons
76 |
77 | foo == "bar" or foo == "bar" # All members identical
78 |
- foo == "bar" or foo == "bar" or foo == "buzz" # All but one members identical
79 + foo in {"bar", "bar", "buzz"} # All but one members identical
note: This is an unsafe fix and may change runtime behavior

View File

@@ -9,7 +9,6 @@ PLR6301 Method `developer_greeting` could be a function, class method, or static
| ^^^^^^^^^^^^^^^^^^
8 | print(f"Greetings {name}!")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `greeting_1` could be a function, class method, or static method
--> no_self_use.py:10:9
@@ -20,7 +19,6 @@ PLR6301 Method `greeting_1` could be a function, class method, or static method
| ^^^^^^^^^^
11 | print("Hello!")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `greeting_2` could be a function, class method, or static method
--> no_self_use.py:13:9
@@ -31,7 +29,6 @@ PLR6301 Method `greeting_2` could be a function, class method, or static method
| ^^^^^^^^^^
14 | print("Hi!")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `validate_y` could be a function, class method, or static method
--> no_self_use.py:103:9
@@ -42,7 +39,6 @@ PLR6301 Method `validate_y` could be a function, class method, or static method
104 | if value <= 0:
105 | raise ValueError("y must be a positive integer")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `non_simple_assignment` could be a function, class method, or static method
--> no_self_use.py:128:9
@@ -54,7 +50,6 @@ PLR6301 Method `non_simple_assignment` could be a function, class method, or sta
129 | msg = foo = ""
130 | raise NotImplementedError(msg)
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `non_simple_assignment_2` could be a function, class method, or static method
--> no_self_use.py:132:9
@@ -66,7 +61,6 @@ PLR6301 Method `non_simple_assignment_2` could be a function, class method, or s
133 | msg[0] = ""
134 | raise NotImplementedError(msg)
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `unused_message` could be a function, class method, or static method
--> no_self_use.py:136:9
@@ -78,7 +72,6 @@ PLR6301 Method `unused_message` could be a function, class method, or static met
137 | msg = ""
138 | raise NotImplementedError("")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `unused_message_2` could be a function, class method, or static method
--> no_self_use.py:140:9
@@ -90,7 +83,6 @@ PLR6301 Method `unused_message_2` could be a function, class method, or static m
141 | msg = ""
142 | raise NotImplementedError(x)
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `developer_greeting` could be a function, class method, or static method
--> no_self_use.py:145:9
@@ -100,7 +92,6 @@ PLR6301 Method `developer_greeting` could be a function, class method, or static
| ^^^^^^^^^^^^^^^^^^
146 | print(t"Greetings {name}!")
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass
PLR6301 Method `tstring` could be a function, class method, or static method
--> no_self_use.py:151:9
@@ -112,4 +103,3 @@ PLR6301 Method `tstring` could be a function, class method, or static method
152 | msg = t"{x}"
153 | raise NotImplementedError(msg)
|
help: Consider adding `@typing.override` if this method overrides a method from a superclass

View File

@@ -34,10 +34,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// different values when introspecting types at runtime. However, in most cases,
/// the fix should be safe to apply.
///
/// ## Options
///
/// - `target-version`
///
/// [PEP 646]: https://peps.python.org/pep-0646/
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.10.0")]

View File

@@ -78,10 +78,6 @@ use super::{
/// This rule only applies to generic classes and does not include generic functions. See
/// [`non-pep695-generic-function`][UP047] for the function version.
///
/// ## Options
///
/// - `target-version`
///
/// [PEP 695]: https://peps.python.org/pep-0695/
/// [PEP 696]: https://peps.python.org/pep-0696/
/// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/

View File

@@ -71,10 +71,6 @@ use super::{DisplayTypeVars, TypeVarReferenceVisitor, check_type_vars, in_nested
/// This rule only applies to generic functions and does not include generic classes. See
/// [`non-pep695-generic-class`][UP046] for the class version.
///
/// ## Options
///
/// - `target-version`
///
/// [PEP 695]: https://peps.python.org/pep-0695/
/// [PEP 696]: https://peps.python.org/pep-0696/
/// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/

View File

@@ -78,10 +78,6 @@ use super::{
/// new type parameters are restricted in scope to their associated aliases. See
/// [`private-type-parameter`][UP049] for a rule to update these names.
///
/// ## Options
///
/// - `target-version`
///
/// [PEP 695]: https://peps.python.org/pep-0695/
/// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/
/// [UP046]: https://docs.astral.sh/ruff/rules/non-pep695-generic-class/

View File

@@ -72,10 +72,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
/// As such, migrating to `enum.StrEnum` will introduce a behavior change for
/// code that relies on the Python 3.11 behavior.
///
/// ## Options
///
/// - `target-version`
///
/// ## References
/// - [enum.StrEnum](https://docs.python.org/3/library/enum.html#enum.StrEnum)
///

View File

@@ -119,8 +119,6 @@ mod tests {
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
#[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))]
#[test_case(Rule::NonEmptyInitModule, Path::new("RUF067/modules/__init__.py"))]
#[test_case(Rule::NonEmptyInitModule, Path::new("RUF067/modules/okay.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
@@ -138,7 +136,6 @@ mod tests {
&LinterSettings {
ruff: super::settings::Settings {
parenthesize_tuple_in_subscript: true,
..super::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
},
@@ -154,7 +151,6 @@ mod tests {
&LinterSettings {
ruff: super::settings::Settings {
parenthesize_tuple_in_subscript: false,
..super::settings::Settings::default()
},
unresolved_target_version: PythonVersion::PY310.into(),
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
@@ -718,26 +714,4 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test]
fn strictly_empty_init_modules_ruf067() -> Result<()> {
assert_diagnostics_diff!(
Path::new("ruff/RUF067/modules/__init__.py"),
&LinterSettings {
ruff: super::settings::Settings {
strictly_empty_init_modules: false,
..super::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::NonEmptyInitModule)
},
&LinterSettings {
ruff: super::settings::Settings {
strictly_empty_init_modules: true,
..super::settings::Settings::default()
},
..LinterSettings::for_rule(Rule::NonEmptyInitModule)
},
);
Ok(())
}
}

View File

@@ -32,7 +32,6 @@ pub(crate) use mutable_dataclass_default::*;
pub(crate) use mutable_fromkeys_value::*;
pub(crate) use needless_else::*;
pub(crate) use never_union::*;
pub(crate) use non_empty_init_module::*;
pub(crate) use non_octal_permissions::*;
pub(crate) use none_not_at_end_of_union::*;
pub(crate) use parenthesize_chained_operators::*;
@@ -100,7 +99,6 @@ mod mutable_dataclass_default;
mod mutable_fromkeys_value;
mod needless_else;
mod never_union;
mod non_empty_init_module;
mod non_octal_permissions;
mod none_not_at_end_of_union;
mod parenthesize_chained_operators;

View File

@@ -1,259 +0,0 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::typing::is_type_checking_block;
use ruff_text_size::Ranged;
use crate::{Violation, checkers::ast::Checker};
/// ## What it does
///
/// Detects the presence of code in `__init__.py` files.
///
/// ## Why is this bad?
///
/// `__init__.py` files are often empty or only contain simple code to modify a module's API. As
/// such, it's easy to overlook them and their possible side effects when debugging.
///
/// ## Example
///
/// Instead of defining `MyClass` directly in `__init__.py`:
///
/// ```python
/// """My module docstring."""
///
///
/// class MyClass:
/// def my_method(self): ...
/// ```
///
/// move the definition to another file, import it, and include it in `__all__`:
///
/// ```python
/// """My module docstring."""
///
/// from submodule import MyClass
///
/// __all__ = ["MyClass"]
/// ```
///
/// Code in `__init__.py` files is also run at import time and can cause surprising slowdowns. To
/// disallow any code in `__init__.py` files, you can enable the
/// [`lint.ruff.strictly-empty-init-modules`] setting. In this case:
///
/// ```python
/// from submodule import MyClass
///
/// __all__ = ["MyClass"]
/// ```
///
/// the only fix is entirely emptying the file:
///
/// ```python
/// ```
///
/// ## Details
///
/// In non-strict mode, this rule allows several common patterns in `__init__.py` files:
///
/// - Imports
/// - Assignments to `__all__`, `__path__`, `__version__`, and `__author__`
/// - Module-level and attribute docstrings
/// - `if TYPE_CHECKING` blocks
/// - [PEP-562] module-level `__getattr__` and `__dir__` functions
///
/// ## Options
///
/// - [`lint.ruff.strictly-empty-init-modules`]
///
/// ## References
///
/// - [`flake8-empty-init-modules`](https://github.com/samueljsb/flake8-empty-init-modules/)
///
/// [PEP-562]: https://peps.python.org/pep-0562/
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.14.11")]
pub(crate) struct NonEmptyInitModule {
strictly_empty_init_modules: bool,
}
impl Violation for NonEmptyInitModule {
#[derive_message_formats]
fn message(&self) -> String {
if self.strictly_empty_init_modules {
"`__init__` module should not contain any code".to_string()
} else {
"`__init__` module should only contain docstrings and re-exports".to_string()
}
}
}
/// RUF067
pub(crate) fn non_empty_init_module(checker: &Checker, stmt: &Stmt) {
if !checker.in_init_module() {
return;
}
let semantic = checker.semantic();
// Only flag top-level statements
if !semantic.at_top_level() {
return;
}
let strictly_empty_init_modules = checker.settings().ruff.strictly_empty_init_modules;
if !strictly_empty_init_modules {
// Even though module-level attributes are disallowed, we still allow attribute docstrings
// to avoid needing two `noqa` comments in a case like:
//
// ```py
// MY_CONSTANT = 1 # noqa: RUF067
// "A very important constant"
// ```
if semantic.in_pep_257_docstring() || semantic.in_attribute_docstring() {
return;
}
match stmt {
// Allow imports
Stmt::Import(_) | Stmt::ImportFrom(_) => return,
// Allow PEP-562 module `__getattr__` and `__dir__`
Stmt::FunctionDef(func) if matches!(&*func.name, "__getattr__" | "__dir__") => return,
// Allow `TYPE_CHECKING` blocks
Stmt::If(stmt_if) if is_type_checking_block(stmt_if, semantic) => return,
_ => {}
}
if let Some(assignment) = Assignment::from_stmt(stmt) {
// Allow assignments to `__all__`.
//
// TODO(brent) should we allow additional cases here? Beyond simple assignments, you could
// also append or extend `__all__`.
//
// This is actually going slightly beyond the upstream rule already, which only checks for
// `Stmt::Assign`.
if assignment.is_assignment_to("__all__") {
return;
}
// Allow legacy namespace packages with assignments like:
//
// ```py
// __path__ = __import__('pkgutil').extend_path(__path__, __name__)
// ```
if assignment.is_assignment_to("__path__") && assignment.is_pkgutil_extend_path() {
return;
}
// Allow assignments to `__version__`.
if assignment.is_assignment_to("__version__") {
return;
}
// Allow assignments to `__author__`.
if assignment.is_assignment_to("__author__") {
return;
}
}
}
checker.report_diagnostic(
NonEmptyInitModule {
strictly_empty_init_modules,
},
stmt.range(),
);
}
/// Any assignment statement, including plain assignment, annotated assignments, and augmented
/// assignments.
struct Assignment<'a> {
targets: &'a [Expr],
value: Option<&'a Expr>,
}
impl<'a> Assignment<'a> {
fn from_stmt(stmt: &'a Stmt) -> Option<Self> {
let (targets, value) = match stmt {
Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
(targets.as_slice(), Some(&**value))
}
Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => {
(std::slice::from_ref(&**target), value.as_deref())
}
Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
(std::slice::from_ref(&**target), Some(&**value))
}
_ => return None,
};
Some(Self { targets, value })
}
/// Returns whether all of the assignment targets match `name`.
///
/// For example, both of the following would be allowed for a `name` of `__all__`:
///
/// ```py
/// __all__ = ["foo"]
/// __all__ = __all__ = ["foo"]
/// ```
///
/// but not:
///
/// ```py
/// __all__ = another_list = ["foo"]
/// ```
fn is_assignment_to(&self, name: &str) -> bool {
self.targets
.iter()
.all(|target| target.as_name_expr().is_some_and(|expr| expr.id == name))
}
/// Returns `true` if the value being assigned is a call to `pkgutil.extend_path`.
///
/// For example, both of the following would return true:
///
/// ```py
/// __path__ = __import__('pkgutil').extend_path(__path__, __name__)
/// __path__ = other.extend_path(__path__, __name__)
/// ```
///
/// We're intentionally a bit less strict here, not requiring that the receiver of the
/// `extend_path` call is the typical `__import__('pkgutil')` or `pkgutil`.
fn is_pkgutil_extend_path(&self) -> bool {
let Some(Expr::Call(ast::ExprCall {
func: extend_func,
arguments: extend_arguments,
..
})) = self.value
else {
return false;
};
let Expr::Attribute(ast::ExprAttribute {
attr: maybe_extend_path,
..
}) = &**extend_func
else {
return false;
};
// Test that this is an `extend_path(__path__, __name__)` call
if maybe_extend_path != "extend_path" {
return false;
}
let Some(Expr::Name(path)) = extend_arguments.find_argument_value("path", 0) else {
return false;
};
let Some(Expr::Name(name)) = extend_arguments.find_argument_value("name", 1) else {
return false;
};
path.id() == "__path__" && name.id() == "__name__"
}
}

View File

@@ -57,7 +57,7 @@ use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
/// ## References
/// - [Typing documentation: Legal parameters for `Literal` at type check time](https://typing.python.org/en/latest/spec/literal.html#legal-parameters-for-literal-at-type-check-time)
///
/// [PEP 586]: https://peps.python.org/pep-0586/
/// [PEP 586](https://peps.python.org/pep-0586/)
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.10.0")]
pub(crate) struct UnnecessaryNestedLiteral;

View File

@@ -7,7 +7,6 @@ use std::fmt;
#[derive(Debug, Clone, CacheKey, Default)]
pub struct Settings {
pub parenthesize_tuple_in_subscript: bool,
pub strictly_empty_init_modules: bool,
}
impl fmt::Display for Settings {
@@ -17,7 +16,6 @@ impl fmt::Display for Settings {
namespace = "linter.ruff",
fields = [
self.parenthesize_tuple_in_subscript,
self.strictly_empty_init_modules,
]
}
Ok(())

View File

@@ -1,53 +0,0 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:12:1
|
10 | __all__ = __all__ = __all__
11 |
12 | MY_CONSTANT = 5
| ^^^^^^^^^^^^^^^
13 | """This is an important constant."""
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:15:1
|
13 | """This is an important constant."""
14 |
15 | os.environ["FOO"] = 1
| ^^^^^^^^^^^^^^^^^^^^^
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:18:1
|
18 | / def foo():
19 | | return Path("foo.py")
| |_________________________^
20 |
21 | def __getattr__(name): # ok
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:26:1
|
24 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
25 |
26 | / if os.environ["FOO"] != "1": # RUF067
27 | | MY_CONSTANT = 4 # ok, don't flag nested statements
| |___________________^
28 |
29 | if TYPE_CHECKING: # ok
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:48:1
|
47 | # non-`extend_path` assignments are not allowed
48 | __path__ = 5 # RUF067
| ^^^^^^^^^^^^
49 |
50 | # also allow `__author__`
|

View File

@@ -1,4 +0,0 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---

View File

@@ -1,344 +0,0 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---
--- Linter settings ---
-linter.ruff.strictly_empty_init_modules = false
+linter.ruff.strictly_empty_init_modules = true
--- Summary ---
Removed: 5
Added: 24
--- Removed ---
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:12:1
|
10 | __all__ = __all__ = __all__
11 |
12 | MY_CONSTANT = 5
| ^^^^^^^^^^^^^^^
13 | """This is an important constant."""
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:15:1
|
13 | """This is an important constant."""
14 |
15 | os.environ["FOO"] = 1
| ^^^^^^^^^^^^^^^^^^^^^
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:18:1
|
18 | / def foo():
19 | | return Path("foo.py")
| |_________________________^
20 |
21 | def __getattr__(name): # ok
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:26:1
|
24 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
25 |
26 | / if os.environ["FOO"] != "1": # RUF067
27 | | MY_CONSTANT = 4 # ok, don't flag nested statements
| |___________________^
28 |
29 | if TYPE_CHECKING: # ok
|
RUF067 `__init__` module should only contain docstrings and re-exports
--> __init__.py:48:1
|
47 | # non-`extend_path` assignments are not allowed
48 | __path__ = 5 # RUF067
| ^^^^^^^^^^^^
49 |
50 | # also allow `__author__`
|
--- Added ---
RUF067 `__init__` module should not contain any code
--> __init__.py:1:1
|
1 | """This is the module docstring."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | # convenience imports:
|
RUF067 `__init__` module should not contain any code
--> __init__.py:4:1
|
3 | # convenience imports:
4 | import os
| ^^^^^^^^^
5 | from pathlib import Path
|
RUF067 `__init__` module should not contain any code
--> __init__.py:5:1
|
3 | # convenience imports:
4 | import os
5 | from pathlib import Path
| ^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 | __all__ = ["MY_CONSTANT"]
|
RUF067 `__init__` module should not contain any code
--> __init__.py:7:1
|
5 | from pathlib import Path
6 |
7 | __all__ = ["MY_CONSTANT"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
8 | __all__ += ["foo"]
9 | __all__: list[str] = __all__
|
RUF067 `__init__` module should not contain any code
--> __init__.py:8:1
|
7 | __all__ = ["MY_CONSTANT"]
8 | __all__ += ["foo"]
| ^^^^^^^^^^^^^^^^^^
9 | __all__: list[str] = __all__
10 | __all__ = __all__ = __all__
|
RUF067 `__init__` module should not contain any code
--> __init__.py:9:1
|
7 | __all__ = ["MY_CONSTANT"]
8 | __all__ += ["foo"]
9 | __all__: list[str] = __all__
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 | __all__ = __all__ = __all__
|
RUF067 `__init__` module should not contain any code
--> __init__.py:10:1
|
8 | __all__ += ["foo"]
9 | __all__: list[str] = __all__
10 | __all__ = __all__ = __all__
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 |
12 | MY_CONSTANT = 5
|
RUF067 `__init__` module should not contain any code
--> __init__.py:12:1
|
10 | __all__ = __all__ = __all__
11 |
12 | MY_CONSTANT = 5
| ^^^^^^^^^^^^^^^
13 | """This is an important constant."""
|
RUF067 `__init__` module should not contain any code
--> __init__.py:13:1
|
12 | MY_CONSTANT = 5
13 | """This is an important constant."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14 |
15 | os.environ["FOO"] = 1
|
RUF067 `__init__` module should not contain any code
--> __init__.py:15:1
|
13 | """This is an important constant."""
14 |
15 | os.environ["FOO"] = 1
| ^^^^^^^^^^^^^^^^^^^^^
|
RUF067 `__init__` module should not contain any code
--> __init__.py:18:1
|
18 | / def foo():
19 | | return Path("foo.py")
| |_________________________^
20 |
21 | def __getattr__(name): # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:21:1
|
19 | return Path("foo.py")
20 |
21 | / def __getattr__(name): # ok
22 | | return name
| |_______________^
23 |
24 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:24:1
|
22 | return name
23 |
24 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25 |
26 | if os.environ["FOO"] != "1": # RUF067
|
RUF067 `__init__` module should not contain any code
--> __init__.py:26:1
|
24 | __path__ = __import__('pkgutil').extend_path(__path__, __name__) # ok
25 |
26 | / if os.environ["FOO"] != "1": # RUF067
27 | | MY_CONSTANT = 4 # ok, don't flag nested statements
| |___________________^
28 |
29 | if TYPE_CHECKING: # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:29:1
|
27 | MY_CONSTANT = 4 # ok, don't flag nested statements
28 |
29 | / if TYPE_CHECKING: # ok
30 | | MY_CONSTANT = 3
| |___________________^
31 |
32 | import typing
|
RUF067 `__init__` module should not contain any code
--> __init__.py:32:1
|
30 | MY_CONSTANT = 3
31 |
32 | import typing
| ^^^^^^^^^^^^^
33 |
34 | if typing.TYPE_CHECKING: # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:34:1
|
32 | import typing
33 |
34 | / if typing.TYPE_CHECKING: # ok
35 | | MY_CONSTANT = 2
| |___________________^
36 |
37 | __version__ = "1.2.3" # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:37:1
|
35 | MY_CONSTANT = 2
36 |
37 | __version__ = "1.2.3" # ok
| ^^^^^^^^^^^^^^^^^^^^^
38 |
39 | def __dir__(): # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:39:1
|
37 | __version__ = "1.2.3" # ok
38 |
39 | / def __dir__(): # ok
40 | | return ["foo"]
| |__________________^
41 |
42 | import pkgutil
|
RUF067 `__init__` module should not contain any code
--> __init__.py:42:1
|
40 | return ["foo"]
41 |
42 | import pkgutil
| ^^^^^^^^^^^^^^
43 |
44 | __path__ = pkgutil.extend_path(__path__, __name__) # ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:44:1
|
42 | import pkgutil
43 |
44 | __path__ = pkgutil.extend_path(__path__, __name__) # ok
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45 | __path__ = unknown.extend_path(__path__, __name__) # also ok
|
RUF067 `__init__` module should not contain any code
--> __init__.py:45:1
|
44 | __path__ = pkgutil.extend_path(__path__, __name__) # ok
45 | __path__ = unknown.extend_path(__path__, __name__) # also ok
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46 |
47 | # non-`extend_path` assignments are not allowed
|
RUF067 `__init__` module should not contain any code
--> __init__.py:48:1
|
47 | # non-`extend_path` assignments are not allowed
48 | __path__ = 5 # RUF067
| ^^^^^^^^^^^^
49 |
50 | # also allow `__author__`
|
RUF067 `__init__` module should not contain any code
--> __init__.py:51:1
|
50 | # also allow `__author__`
51 | __author__ = "The Author" # ok
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|

View File

@@ -18,10 +18,10 @@ doctest = false
ruff_python_trivia = { workspace = true }
heck = { workspace = true }
itertools = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true, features = ["derive", "parsing", "extra-traits", "full"] }
itertools = { workspace = true }
[lints]
workspace = true

View File

@@ -20,12 +20,12 @@ ruff_text_size = { workspace = true }
anyhow = { workspace = true }
itertools = { workspace = true }
rand = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true, default-features = false, features = ["macros"] }
thiserror = { workspace = true }
uuid = { workspace = true }
rand = { workspace = true }
[dev-dependencies]
test-case = { workspace = true }

View File

@@ -10,10 +10,6 @@ documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[lib]
[dependencies]
@@ -46,7 +42,14 @@ serde = [
"dep:ruff_cache",
"compact_str/serde",
]
get-size = ["dep:get-size2", "ruff_text_size/get-size"]
get-size = [
"dep:get-size2",
"ruff_text_size/get-size"
]
[lints]
workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]

View File

@@ -12,8 +12,8 @@ license.workspace = true
[dependencies]
[dev-dependencies]
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_text_size = { workspace = true }

View File

@@ -10,10 +10,6 @@ documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[lib]
doctest = false
@@ -22,10 +18,10 @@ ruff_cache = { workspace = true }
ruff_db = { workspace = true }
ruff_formatter = { workspace = true }
ruff_macros = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_text_size = { workspace = true }
anyhow = { workspace = true }
@@ -36,8 +32,8 @@ memchr = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
schemars = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
smallvec = { workspace = true }
static_assertions = { workspace = true }
@@ -54,6 +50,16 @@ serde = { workspace = true }
serde_json = { workspace = true }
similar = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[[test]]
name = "fixtures"
harness = false
test = true
required-features = ["serde"]
[features]
default = ["serde"]
serde = [
@@ -64,11 +70,5 @@ serde = [
]
schemars = ["dep:schemars", "dep:serde_json", "ruff_formatter/schemars"]
[[test]]
name = "fixtures"
harness = false
test = true
required-features = ["serde"]
[lints]
workspace = true

View File

@@ -12,6 +12,10 @@ license = { workspace = true }
[lib]
[[test]]
name = "fixtures"
harness = false
[dependencies]
ruff_python_ast = { workspace = true, features = ["get-size"] }
ruff_python_trivia = { workspace = true }
@@ -25,8 +29,8 @@ memchr = { workspace = true }
rustc-hash = { workspace = true }
static_assertions = { workspace = true }
unicode-ident = { workspace = true }
unicode-normalization = { workspace = true }
unicode_names2 = { workspace = true }
unicode-normalization = { workspace = true }
[dev-dependencies]
ruff_annotate_snippets = { workspace = true }
@@ -41,9 +45,5 @@ serde = { workspace = true }
serde_json = { workspace = true }
walkdir = { workspace = true }
[[test]]
name = "fixtures"
harness = false
[lints]
workspace = true

View File

@@ -10,10 +10,6 @@ documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]
[dependencies]
ruff_cache = { workspace = true }
ruff_index = { workspace = true }
@@ -32,8 +28,12 @@ smallvec = { workspace = true }
[dev-dependencies]
insta = { workspace = true, features = ["filters", "json", "redactions"] }
ruff_python_parser = { workspace = true }
test-case = { workspace = true }
ruff_python_parser = { workspace = true }
[lints]
workspace = true
[package.metadata.cargo-shear]
# Used via `CacheKey` macro expansion.
ignored = ["ruff_cache"]

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