codelaw-rules-python 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codelaw_rules_python/LICENSE +23 -0
- codelaw_rules_python/NOTICE +14 -0
- codelaw_rules_python/README.md +189 -0
- codelaw_rules_python/codelaw-pack.toml +6 -0
- codelaw_rules_python/corpus.toml +103 -0
- codelaw_rules_python/data/python/stdlib.json +331 -0
- codelaw_rules_python/presets/py/conventions.toml +5 -0
- codelaw_rules_python/presets/py/project.toml +4 -0
- codelaw_rules_python/presets/py/recommended.toml +99 -0
- codelaw_rules_python/rules/graph/no-unused-exports.md +69 -0
- codelaw_rules_python/rules/py/abstract-base-class-without-abstract-method.md +50 -0
- codelaw_rules_python/rules/py/all-sorted.cases.md +74 -0
- codelaw_rules_python/rules/py/all-sorted.md +33 -0
- codelaw_rules_python/rules/py/ambiguous-class-name.md +41 -0
- codelaw_rules_python/rules/py/ambiguous-function-name.md +41 -0
- codelaw_rules_python/rules/py/ambiguous-variable-name.cases.md +47 -0
- codelaw_rules_python/rules/py/ambiguous-variable-name.md +39 -0
- codelaw_rules_python/rules/py/assert-false.md +34 -0
- codelaw_rules_python/rules/py/assert-raises-exception.md +51 -0
- codelaw_rules_python/rules/py/assert-tuple.md +38 -0
- codelaw_rules_python/rules/py/assignment-to-os-environ.md +43 -0
- codelaw_rules_python/rules/py/batched-without-explicit-strict.md +52 -0
- codelaw_rules_python/rules/py/break-outside-loop.md +41 -0
- codelaw_rules_python/rules/py/cached-instance-method.md +55 -0
- codelaw_rules_python/rules/py/continue-outside-loop.md +41 -0
- codelaw_rules_python/rules/py/default-except-not-last.md +48 -0
- codelaw_rules_python/rules/py/duplicate-dict-key-variable.md +39 -0
- codelaw_rules_python/rules/py/duplicate-dict-key.md +39 -0
- codelaw_rules_python/rules/py/duplicate-handler-exception.md +43 -0
- codelaw_rules_python/rules/py/duplicate-set-value.md +33 -0
- codelaw_rules_python/rules/py/duplicate-try-block-exception.md +47 -0
- codelaw_rules_python/rules/py/empty-method-without-abstract-decorator.md +55 -0
- codelaw_rules_python/rules/py/except-with-empty-tuple.md +44 -0
- codelaw_rules_python/rules/py/except-with-non-exception-classes.md +44 -0
- codelaw_rules_python/rules/py/f-string-docstring.md +40 -0
- codelaw_rules_python/rules/py/f-string-missing-placeholders.cases.md +49 -0
- codelaw_rules_python/rules/py/f-string-missing-placeholders.md +33 -0
- codelaw_rules_python/rules/py/format-invalid.md +38 -0
- codelaw_rules_python/rules/py/format-missing-argument.md +38 -0
- codelaw_rules_python/rules/py/format-mixed-numbering.md +38 -0
- codelaw_rules_python/rules/py/format-unused-named-argument.md +37 -0
- codelaw_rules_python/rules/py/format-unused-positional-argument.md +37 -0
- codelaw_rules_python/rules/py/forward-annotation-syntax-error.md +44 -0
- codelaw_rules_python/rules/py/function-call-in-default-argument.md +51 -0
- codelaw_rules_python/rules/py/function-uses-loop-variable.cases.md +57 -0
- codelaw_rules_python/rules/py/function-uses-loop-variable.md +42 -0
- codelaw_rules_python/rules/py/getattr-with-constant.cases.md +31 -0
- codelaw_rules_python/rules/py/getattr-with-constant.md +33 -0
- codelaw_rules_python/rules/py/if-tuple.md +40 -0
- codelaw_rules_python/rules/py/import-shadowed-by-loop-var.md +45 -0
- codelaw_rules_python/rules/py/invalid-print-syntax.md +42 -0
- codelaw_rules_python/rules/py/is-literal.md +36 -0
- codelaw_rules_python/rules/py/jump-statement-in-finally.cases.md +15 -0
- codelaw_rules_python/rules/py/jump-statement-in-finally.md +47 -0
- codelaw_rules_python/rules/py/lambda-assignment.cases.md +48 -0
- codelaw_rules_python/rules/py/lambda-assignment.md +35 -0
- codelaw_rules_python/rules/py/late-future-import.md +40 -0
- codelaw_rules_python/rules/py/loop-variable-overrides-iterator.md +41 -0
- codelaw_rules_python/rules/py/map-without-explicit-strict.md +46 -0
- codelaw_rules_python/rules/py/module-import-not-at-top.cases.md +64 -0
- codelaw_rules_python/rules/py/module-import-not-at-top.md +46 -0
- codelaw_rules_python/rules/py/multiple-imports-on-one-line.md +38 -0
- codelaw_rules_python/rules/py/multiple-starred-expressions.md +38 -0
- codelaw_rules_python/rules/py/mutable-contextvar-default.md +42 -0
- codelaw_rules_python/rules/py/nested-star-import.md +42 -0
- codelaw_rules_python/rules/py/no-bare-except.cases.md +37 -0
- codelaw_rules_python/rules/py/no-bare-except.md +44 -0
- codelaw_rules_python/rules/py/no-explicit-stacklevel.md +38 -0
- codelaw_rules_python/rules/py/no-mutable-default.cases.md +58 -0
- codelaw_rules_python/rules/py/no-mutable-default.md +44 -0
- codelaw_rules_python/rules/py/no-print.cases.md +40 -0
- codelaw_rules_python/rules/py/no-print.md +42 -0
- codelaw_rules_python/rules/py/no-star-import.cases.md +11 -0
- codelaw_rules_python/rules/py/no-star-import.md +47 -0
- codelaw_rules_python/rules/py/no-tunable-literals.cases.md +54 -0
- codelaw_rules_python/rules/py/no-tunable-literals.md +58 -0
- codelaw_rules_python/rules/py/none-comparison.md +36 -0
- codelaw_rules_python/rules/py/not-in-test.md +35 -0
- codelaw_rules_python/rules/py/not-is-test.md +35 -0
- codelaw_rules_python/rules/py/percent-format-count-mismatch.md +38 -0
- codelaw_rules_python/rules/py/percent-format-expected-mapping.md +38 -0
- codelaw_rules_python/rules/py/percent-format-expected-sequence.md +38 -0
- codelaw_rules_python/rules/py/percent-format-invalid.md +38 -0
- codelaw_rules_python/rules/py/percent-format-missing-argument.md +38 -0
- codelaw_rules_python/rules/py/percent-format-mixed-placeholders.md +38 -0
- codelaw_rules_python/rules/py/percent-format-star-requires-sequence.md +38 -0
- codelaw_rules_python/rules/py/percent-format-unsupported-character.md +38 -0
- codelaw_rules_python/rules/py/percent-format-unused-named-argument.md +37 -0
- codelaw_rules_python/rules/py/raise-literal.md +38 -0
- codelaw_rules_python/rules/py/raise-not-implemented.md +38 -0
- codelaw_rules_python/rules/py/raise-without-from-inside-except.cases.md +46 -0
- codelaw_rules_python/rules/py/raise-without-from-inside-except.md +45 -0
- codelaw_rules_python/rules/py/re-sub-positional-args.md +42 -0
- codelaw_rules_python/rules/py/redefined-while-unused.cases.md +186 -0
- codelaw_rules_python/rules/py/redefined-while-unused.md +53 -0
- codelaw_rules_python/rules/py/redundant-tuple-in-exception-handler.md +39 -0
- codelaw_rules_python/rules/py/return-outside-function.md +41 -0
- codelaw_rules_python/rules/py/reuse-of-groupby-generator.cases.md +40 -0
- codelaw_rules_python/rules/py/reuse-of-groupby-generator.md +46 -0
- codelaw_rules_python/rules/py/setattr-with-constant.md +33 -0
- codelaw_rules_python/rules/py/star-arg-unpacking-after-keyword-arg.md +38 -0
- codelaw_rules_python/rules/py/star-import-usage.cases.md +35 -0
- codelaw_rules_python/rules/py/star-import-usage.md +43 -0
- codelaw_rules_python/rules/py/static-key-dict-comprehension.md +38 -0
- codelaw_rules_python/rules/py/strip-with-multi-characters.md +39 -0
- codelaw_rules_python/rules/py/too-many-expressions-in-star-assignment.md +38 -0
- codelaw_rules_python/rules/py/true-false-comparison.cases.md +33 -0
- codelaw_rules_python/rules/py/true-false-comparison.md +36 -0
- codelaw_rules_python/rules/py/type-comparison.md +41 -0
- codelaw_rules_python/rules/py/unary-prefix-increment.md +40 -0
- codelaw_rules_python/rules/py/undefined-export.md +55 -0
- codelaw_rules_python/rules/py/undefined-future-feature.md +39 -0
- codelaw_rules_python/rules/py/undefined-local.cases.md +80 -0
- codelaw_rules_python/rules/py/undefined-local.md +48 -0
- codelaw_rules_python/rules/py/undefined-name.cases.md +132 -0
- codelaw_rules_python/rules/py/undefined-name.md +42 -0
- codelaw_rules_python/rules/py/unintentional-type-annotation.cases.md +13 -0
- codelaw_rules_python/rules/py/unintentional-type-annotation.md +38 -0
- codelaw_rules_python/rules/py/unreliable-callable-check.md +36 -0
- codelaw_rules_python/rules/py/unsorted-imports.cases.md +159 -0
- codelaw_rules_python/rules/py/unsorted-imports.md +60 -0
- codelaw_rules_python/rules/py/unused-annotation.md +36 -0
- codelaw_rules_python/rules/py/unused-import.cases.md +289 -0
- codelaw_rules_python/rules/py/unused-import.md +45 -0
- codelaw_rules_python/rules/py/unused-loop-control-variable.cases.md +30 -0
- codelaw_rules_python/rules/py/unused-loop-control-variable.md +36 -0
- codelaw_rules_python/rules/py/unused-variable.cases.md +169 -0
- codelaw_rules_python/rules/py/unused-variable.md +38 -0
- codelaw_rules_python/rules/py/useless-comparison.md +41 -0
- codelaw_rules_python/rules/py/useless-contextlib-suppress.md +44 -0
- codelaw_rules_python/rules/py/useless-expression.cases.md +57 -0
- codelaw_rules_python/rules/py/useless-expression.md +45 -0
- codelaw_rules_python/rules/py/yield-outside-function.md +44 -0
- codelaw_rules_python/rules/py/zip-without-explicit-strict.cases.md +26 -0
- codelaw_rules_python/rules/py/zip-without-explicit-strict.md +46 -0
- codelaw_rules_python-0.2.0.dist-info/METADATA +199 -0
- codelaw_rules_python-0.2.0.dist-info/RECORD +139 -0
- codelaw_rules_python-0.2.0.dist-info/WHEEL +4 -0
- codelaw_rules_python-0.2.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/star-arg-unpacking-after-keyword-arg
|
|
3
|
+
title: "No `*args` after a keyword argument"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "reordering arguments can change which parameter each value binds to"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B026]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
In a call, `*args` comes before keyword arguments.
|
|
13
|
+
|
|
14
|
+
**Why:** `f(x=1, *args)` fills positions before `x`, which reads backwards and can raise `TypeError` for a duplicate argument.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/star-arg-unpacking-after-keyword-arg
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Move `*args` before the keyword arguments and check the call still binds as intended.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
run(name="build", *steps)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
run(*steps, name="build")
|
|
38
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# py/star-import-usage — cases
|
|
2
|
+
|
|
3
|
+
### case: names in __all__
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
from os.path import *
|
|
7
|
+
__all__ = ["join", "local"]
|
|
8
|
+
local = 1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```expect
|
|
12
|
+
2:12 py/star-import-usage
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### case: submodule bound by a relative star import
|
|
16
|
+
|
|
17
|
+
```py
|
|
18
|
+
from .events import *
|
|
19
|
+
print(events.__all__)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```expect
|
|
23
|
+
(none)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### case: builtins
|
|
27
|
+
|
|
28
|
+
```py
|
|
29
|
+
from os.path import *
|
|
30
|
+
print(len([]))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```expect
|
|
34
|
+
(none)
|
|
35
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/star-import-usage
|
|
3
|
+
title: "No names that may only come from a star import"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the module the name comes from has to be confirmed by a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F405]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
In a module with `from module import *`, every name the module reads is defined locally or imported by name.
|
|
13
|
+
|
|
14
|
+
**Why:** With a star import in play, a misspelled name and a name the star import provides look the same; neither can be checked.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/star-import-usage
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Find which star-imported module provides the name.
|
|
23
|
+
2. Import it by name from that module.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
from os.path import *
|
|
33
|
+
|
|
34
|
+
print(join("a", "b"))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Do
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
from os.path import join
|
|
41
|
+
|
|
42
|
+
print(join("a", "b"))
|
|
43
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/static-key-dict-comprehension
|
|
3
|
+
title: "A dict comprehension's key varies"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the intended key can't be inferred"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B035]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A dict comprehension's key depends on the loop variables.
|
|
13
|
+
|
|
14
|
+
**Why:** A constant key gives a dict with one entry, keeping only the last value.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/static-key-dict-comprehension
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Use a key that depends on the loop variable.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
sizes = {"size": len(name) for name in names}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
sizes = {name: len(name) for name in names}
|
|
38
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/strip-with-multi-characters
|
|
3
|
+
title: "`.strip()` isn't given a word"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether a character set or a suffix was meant needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B005]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`.strip()`, `.lstrip()` and `.rstrip()` aren't passed a multi-character string with repeated characters.
|
|
13
|
+
|
|
14
|
+
**Why:** They remove any of the given characters, not a prefix or suffix; `"note.txt".rstrip(".txt")` gives `"no"`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/strip-with-multi-characters
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Use `.removeprefix()` / `.removesuffix()` to remove a fixed string.
|
|
23
|
+
2. Keep `.strip()` only for a set of characters, written without repeats.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
name = filename.rstrip(".txt")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
name = filename.removesuffix(".txt")
|
|
39
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/too-many-expressions-in-star-assignment
|
|
3
|
+
title: "A starred assignment stays within Python's limits"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "how to restructure the unpacking is a design choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F621]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An unpacking assignment with a starred target has fewer than 256 targets before the star and fewer than 2^24 after it.
|
|
13
|
+
|
|
14
|
+
**Why:** Past those limits the assignment is a `SyntaxError`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/too-many-expressions-in-star-assignment
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Unpack into a list and index it instead of naming every target.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a100, a101, a102, a103, a104, a105, a106, a107, a108, a109, a110, a111, a112, a113, a114, a115, a116, a117, a118, a119, a120, a121, a122, a123, a124, a125, a126, a127, a128, a129, a130, a131, a132, a133, a134, a135, a136, a137, a138, a139, a140, a141, a142, a143, a144, a145, a146, a147, a148, a149, a150, a151, a152, a153, a154, a155, a156, a157, a158, a159, a160, a161, a162, a163, a164, a165, a166, a167, a168, a169, a170, a171, a172, a173, a174, a175, a176, a177, a178, a179, a180, a181, a182, a183, a184, a185, a186, a187, a188, a189, a190, a191, a192, a193, a194, a195, a196, a197, a198, a199, a200, a201, a202, a203, a204, a205, a206, a207, a208, a209, a210, a211, a212, a213, a214, a215, a216, a217, a218, a219, a220, a221, a222, a223, a224, a225, a226, a227, a228, a229, a230, a231, a232, a233, a234, a235, a236, a237, a238, a239, a240, a241, a242, a243, a244, a245, a246, a247, a248, a249, a250, a251, a252, a253, a254, a255, *rest = values
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
first, *rest = values
|
|
38
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# py/true-false-comparison — cases
|
|
2
|
+
|
|
3
|
+
### case: comparing with False
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
if done == False:
|
|
7
|
+
wait()
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```expect
|
|
11
|
+
1:4 py/true-false-comparison
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```fixed
|
|
15
|
+
if not done:
|
|
16
|
+
wait()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### case: compound operand is parenthesised
|
|
20
|
+
|
|
21
|
+
```py
|
|
22
|
+
if a or b != True:
|
|
23
|
+
stop()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```expect
|
|
27
|
+
1:9 py/true-false-comparison
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```fixed
|
|
31
|
+
if a or not b:
|
|
32
|
+
stop()
|
|
33
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/true-false-comparison
|
|
3
|
+
title: "Don't compare with `True` or `False`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "`x == True` is false for truthy values such as `1` or `[0]`; testing `x` makes them true"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E712]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A condition tests a value directly (`if ready:`, `if not ready:`) instead of comparing it with `True` or `False`.
|
|
13
|
+
|
|
14
|
+
**Why:** `== True` is noise and is false for truthy values that aren't exactly `True`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/true-false-comparison
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
if ready == True:
|
|
28
|
+
start()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Do
|
|
32
|
+
|
|
33
|
+
```py
|
|
34
|
+
if ready:
|
|
35
|
+
start()
|
|
36
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/type-comparison
|
|
3
|
+
title: "Don't compare types with `==`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "`isinstance` accepts subclasses, which may or may not be intended"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E721]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Types are compared with `is` / `is not`, or checked with `isinstance()`, not with `==` or `!=` against `type(x)`.
|
|
13
|
+
|
|
14
|
+
**Why:** `type(x) == T` ignores subclasses and can be fooled by a metaclass's `__eq__`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/type-comparison
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Use `isinstance(x, T)` when subclasses should match.
|
|
23
|
+
2. Use `type(x) is T` when only the exact type should.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
if type(value) == int:
|
|
33
|
+
count(value)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
if isinstance(value, int):
|
|
40
|
+
count(value)
|
|
41
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/unary-prefix-increment
|
|
3
|
+
title: "No `++x` or `--x`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether an increment or a plain value was meant needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B002]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Code doesn't write `++x` or `--x`.
|
|
13
|
+
|
|
14
|
+
**Why:** Python has no increment operator: `++x` is `+(+x)`, which leaves `x` unchanged.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/unary-prefix-increment
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Write `x += 1` (or `x -= 1`) if an increment was meant.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
count = 1
|
|
32
|
+
++count
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
count = 1
|
|
39
|
+
count += 1
|
|
40
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/undefined-export
|
|
3
|
+
title: "`__all__` names only defined names"
|
|
4
|
+
languages: [python]
|
|
5
|
+
exclude: ["**/__init__.py"]
|
|
6
|
+
fix: manual
|
|
7
|
+
fix_reason: "whether the name should be defined or dropped from `__all__` needs a person"
|
|
8
|
+
stability: preview
|
|
9
|
+
ported_from: [ruff/F822]
|
|
10
|
+
since: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Every name in a module's `__all__` is defined or imported in that module.
|
|
14
|
+
|
|
15
|
+
**Why:** `from module import *` raises `AttributeError` on a missing name, and readers trust `__all__` as the module's API.
|
|
16
|
+
|
|
17
|
+
```codelaw
|
|
18
|
+
native: py/undefined-export
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How to fix
|
|
22
|
+
|
|
23
|
+
1. Define or import the name, or remove it from `__all__`.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
__all__ = ["load", "save"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load():
|
|
36
|
+
pass
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
__all__ = ["load", "save"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load():
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def save():
|
|
50
|
+
pass
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Differences from the source
|
|
54
|
+
|
|
55
|
+
`__init__.py` files are excluded, as in Ruff, because their `__all__` may list submodules.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/undefined-future-feature
|
|
3
|
+
title: "`from __future__` names only real features"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the intended feature can't be guessed from a misspelling"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F407]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Every name imported from `__future__` is a feature Python defines.
|
|
13
|
+
|
|
14
|
+
**Why:** An unknown feature is a `SyntaxError` when the module is compiled.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/undefined-future-feature
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Check the name against the `__future__` module's features.
|
|
23
|
+
2. Correct it or remove the import.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
from __future__ import braces
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# py/undefined-local — cases
|
|
2
|
+
|
|
3
|
+
### case: annotations of a nested function
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
from typing import Any, Iterator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _unique(fn):
|
|
10
|
+
def unique(*args: Any, **kw: Any) -> Iterator[Any]:
|
|
11
|
+
return fn(*args, **kw)
|
|
12
|
+
return unique
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```expect
|
|
16
|
+
(none)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### case: builtin annotation in a nested function
|
|
20
|
+
|
|
21
|
+
```py
|
|
22
|
+
def outer():
|
|
23
|
+
def parse_line(line: str) -> str:
|
|
24
|
+
return line
|
|
25
|
+
return parse_line
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```expect
|
|
29
|
+
(none)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### case: read in the right-hand side
|
|
33
|
+
|
|
34
|
+
```py
|
|
35
|
+
x = 1
|
|
36
|
+
def f():
|
|
37
|
+
x = x + 1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```expect
|
|
41
|
+
3:9 py/undefined-local
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### case: assigned first
|
|
45
|
+
|
|
46
|
+
```py
|
|
47
|
+
x = 1
|
|
48
|
+
def f():
|
|
49
|
+
x = 2
|
|
50
|
+
return x
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```expect
|
|
54
|
+
(none)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### case: global declaration
|
|
58
|
+
|
|
59
|
+
```py
|
|
60
|
+
x = 1
|
|
61
|
+
def f():
|
|
62
|
+
global x
|
|
63
|
+
x += 1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```expect
|
|
67
|
+
(none)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### case: no outer name
|
|
71
|
+
|
|
72
|
+
```py
|
|
73
|
+
def f():
|
|
74
|
+
print(y)
|
|
75
|
+
y = 1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```expect
|
|
79
|
+
(none)
|
|
80
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/undefined-local
|
|
3
|
+
title: "A local isn't read before the function assigns it"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether to declare it `global`, `nonlocal` or rename the local needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F823]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A function doesn't read a name before assigning it, when that name also exists in an enclosing scope.
|
|
13
|
+
|
|
14
|
+
**Why:** Assigning makes the name local for the whole function, so the earlier read raises `UnboundLocalError` instead of seeing the outer value.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/undefined-local
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Declare the name `global` or `nonlocal` if the outer one is meant.
|
|
23
|
+
2. Otherwise rename the local variable.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
count = 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def increment():
|
|
36
|
+
count += 1
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Do
|
|
40
|
+
|
|
41
|
+
```py
|
|
42
|
+
count = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def increment():
|
|
46
|
+
global count
|
|
47
|
+
count += 1
|
|
48
|
+
```
|