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,99 @@
|
|
|
1
|
+
description = "Ruff 0.17.1's long-standing default (E4, E7, E9, F) plus flake8-bugbear (B), isort's import order (I001) and sorted __all__ (RUF022), reimplemented"
|
|
2
|
+
|
|
3
|
+
[rules]
|
|
4
|
+
"py/abstract-base-class-without-abstract-method" = "error"
|
|
5
|
+
"py/all-sorted" = "error"
|
|
6
|
+
"py/ambiguous-class-name" = "error"
|
|
7
|
+
"py/ambiguous-function-name" = "error"
|
|
8
|
+
"py/ambiguous-variable-name" = "error"
|
|
9
|
+
"py/assert-false" = "error"
|
|
10
|
+
"py/assert-raises-exception" = "error"
|
|
11
|
+
"py/assert-tuple" = "error"
|
|
12
|
+
"py/assignment-to-os-environ" = "error"
|
|
13
|
+
"py/batched-without-explicit-strict" = "error"
|
|
14
|
+
"py/break-outside-loop" = "error"
|
|
15
|
+
"py/cached-instance-method" = "error"
|
|
16
|
+
"py/continue-outside-loop" = "error"
|
|
17
|
+
"py/default-except-not-last" = "error"
|
|
18
|
+
"py/duplicate-dict-key" = "error"
|
|
19
|
+
"py/duplicate-dict-key-variable" = "error"
|
|
20
|
+
"py/duplicate-handler-exception" = "error"
|
|
21
|
+
"py/duplicate-set-value" = "error"
|
|
22
|
+
"py/duplicate-try-block-exception" = "error"
|
|
23
|
+
"py/empty-method-without-abstract-decorator" = "error"
|
|
24
|
+
"py/except-with-empty-tuple" = "error"
|
|
25
|
+
"py/except-with-non-exception-classes" = "error"
|
|
26
|
+
"py/f-string-docstring" = "error"
|
|
27
|
+
"py/f-string-missing-placeholders" = "error"
|
|
28
|
+
"py/format-invalid" = "error"
|
|
29
|
+
"py/format-missing-argument" = "error"
|
|
30
|
+
"py/format-mixed-numbering" = "error"
|
|
31
|
+
"py/format-unused-named-argument" = "error"
|
|
32
|
+
"py/format-unused-positional-argument" = "error"
|
|
33
|
+
"py/forward-annotation-syntax-error" = "error"
|
|
34
|
+
"py/function-call-in-default-argument" = "error"
|
|
35
|
+
"py/function-uses-loop-variable" = "error"
|
|
36
|
+
"py/getattr-with-constant" = "error"
|
|
37
|
+
"py/if-tuple" = "error"
|
|
38
|
+
"py/import-shadowed-by-loop-var" = "error"
|
|
39
|
+
"py/invalid-print-syntax" = "error"
|
|
40
|
+
"py/is-literal" = "error"
|
|
41
|
+
"py/jump-statement-in-finally" = "error"
|
|
42
|
+
"py/lambda-assignment" = "error"
|
|
43
|
+
"py/late-future-import" = "error"
|
|
44
|
+
"py/loop-variable-overrides-iterator" = "error"
|
|
45
|
+
"py/map-without-explicit-strict" = "error"
|
|
46
|
+
"py/module-import-not-at-top" = "error"
|
|
47
|
+
"py/multiple-imports-on-one-line" = "error"
|
|
48
|
+
"py/multiple-starred-expressions" = "error"
|
|
49
|
+
"py/mutable-contextvar-default" = "error"
|
|
50
|
+
"py/nested-star-import" = "error"
|
|
51
|
+
"py/no-bare-except" = "error"
|
|
52
|
+
"py/no-explicit-stacklevel" = "error"
|
|
53
|
+
"py/no-mutable-default" = "error"
|
|
54
|
+
"py/no-star-import" = "error"
|
|
55
|
+
"py/none-comparison" = "error"
|
|
56
|
+
"py/not-in-test" = "error"
|
|
57
|
+
"py/not-is-test" = "error"
|
|
58
|
+
"py/percent-format-count-mismatch" = "error"
|
|
59
|
+
"py/percent-format-expected-mapping" = "error"
|
|
60
|
+
"py/percent-format-expected-sequence" = "error"
|
|
61
|
+
"py/percent-format-invalid" = "error"
|
|
62
|
+
"py/percent-format-missing-argument" = "error"
|
|
63
|
+
"py/percent-format-mixed-placeholders" = "error"
|
|
64
|
+
"py/percent-format-star-requires-sequence" = "error"
|
|
65
|
+
"py/percent-format-unsupported-character" = "error"
|
|
66
|
+
"py/percent-format-unused-named-argument" = "error"
|
|
67
|
+
"py/raise-literal" = "error"
|
|
68
|
+
"py/raise-not-implemented" = "error"
|
|
69
|
+
"py/raise-without-from-inside-except" = "error"
|
|
70
|
+
"py/re-sub-positional-args" = "error"
|
|
71
|
+
"py/redefined-while-unused" = "error"
|
|
72
|
+
"py/redundant-tuple-in-exception-handler" = "error"
|
|
73
|
+
"py/return-outside-function" = "error"
|
|
74
|
+
"py/reuse-of-groupby-generator" = "error"
|
|
75
|
+
"py/setattr-with-constant" = "error"
|
|
76
|
+
"py/star-arg-unpacking-after-keyword-arg" = "error"
|
|
77
|
+
"py/star-import-usage" = "error"
|
|
78
|
+
"py/static-key-dict-comprehension" = "error"
|
|
79
|
+
"py/strip-with-multi-characters" = "error"
|
|
80
|
+
"py/too-many-expressions-in-star-assignment" = "error"
|
|
81
|
+
"py/true-false-comparison" = "error"
|
|
82
|
+
"py/type-comparison" = "error"
|
|
83
|
+
"py/unary-prefix-increment" = "error"
|
|
84
|
+
"py/undefined-export" = "error"
|
|
85
|
+
"py/undefined-future-feature" = "error"
|
|
86
|
+
"py/undefined-local" = "error"
|
|
87
|
+
"py/undefined-name" = "error"
|
|
88
|
+
"py/unintentional-type-annotation" = "error"
|
|
89
|
+
"py/unreliable-callable-check" = "error"
|
|
90
|
+
"py/unsorted-imports" = "error"
|
|
91
|
+
"py/unused-annotation" = "error"
|
|
92
|
+
"py/unused-import" = "error"
|
|
93
|
+
"py/unused-loop-control-variable" = "error"
|
|
94
|
+
"py/unused-variable" = "error"
|
|
95
|
+
"py/useless-comparison" = "error"
|
|
96
|
+
"py/useless-contextlib-suppress" = "error"
|
|
97
|
+
"py/useless-expression" = "error"
|
|
98
|
+
"py/yield-outside-function" = "error"
|
|
99
|
+
"py/zip-without-explicit-strict" = "error"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: graph/no-unused-exports
|
|
3
|
+
title: Exported symbols must be used somewhere
|
|
4
|
+
languages: [python]
|
|
5
|
+
scope: project
|
|
6
|
+
severity: error
|
|
7
|
+
fix: suggest
|
|
8
|
+
fix_reason: "Static analysis can't rule out reflective or external use, so removal needs a human or agent to accept the diff."
|
|
9
|
+
stability: preview
|
|
10
|
+
ported_from: [vulture/unused-function]
|
|
11
|
+
since: 0.1.0
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Every name a module exports is imported by another module, is reachable from an entry point, or is marked `# codelaw: keep`.
|
|
15
|
+
|
|
16
|
+
**Why:** a module-level function or class nothing imports is dead code that still looks like API. A module with `__all__` exports what it lists; without one, a public name counts only when its own module doesn't use it either.
|
|
17
|
+
|
|
18
|
+
```codelaw
|
|
19
|
+
graph:
|
|
20
|
+
symbol:
|
|
21
|
+
exported: true
|
|
22
|
+
kind: [function, class, variable, type, interface, enum]
|
|
23
|
+
unless:
|
|
24
|
+
- reachable_from: entry
|
|
25
|
+
- tagged: [public, keep]
|
|
26
|
+
when:
|
|
27
|
+
external_references: 0
|
|
28
|
+
certainty_from: module.dynamic
|
|
29
|
+
message: "`$SYMBOL` is exported but never used outside `$FILE`"
|
|
30
|
+
fix:
|
|
31
|
+
- when: { local_references: ">0" }
|
|
32
|
+
do: [strip-export-keyword]
|
|
33
|
+
- else:
|
|
34
|
+
do: [remove-declaration]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## How to fix
|
|
38
|
+
|
|
39
|
+
1. Run `codelaw graph why-unused <symbol>` (`<file>#<export>` or `<module>:<name>`) to see each entry, keep and import clause.
|
|
40
|
+
2. If something outside the repo uses it, add the file to `[graph] entry` or `[graph] keep`, add its decorator to `[graph] registrars`, or tag it `/** @public */` / `# codelaw: keep`.
|
|
41
|
+
3. Otherwise delete the declaration (and its `__all__` or `export { … }` entry), or drop `export` if its own file still uses it.
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### Python
|
|
46
|
+
|
|
47
|
+
#### Don't
|
|
48
|
+
|
|
49
|
+
```py files
|
|
50
|
+
# app/dates.py
|
|
51
|
+
def format_date(d):
|
|
52
|
+
return d.isoformat()
|
|
53
|
+
def parse_date(s):
|
|
54
|
+
return s
|
|
55
|
+
# app/main.py
|
|
56
|
+
from app.dates import format_date
|
|
57
|
+
print(format_date(None))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Do
|
|
61
|
+
|
|
62
|
+
```py files
|
|
63
|
+
# app/dates.py
|
|
64
|
+
def format_date(d):
|
|
65
|
+
return d.isoformat()
|
|
66
|
+
# app/main.py
|
|
67
|
+
from app.dates import format_date
|
|
68
|
+
print(format_date(None))
|
|
69
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/abstract-base-class-without-abstract-method
|
|
3
|
+
title: "An abstract base class has an abstract method"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "which methods subclasses must provide is a design decision"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B024]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A class that inherits only from `ABC` (or uses `ABCMeta`) declares at least one `@abstractmethod`.
|
|
13
|
+
|
|
14
|
+
**Why:** Without one, nothing stops the base class being instantiated, and readers can't tell what subclasses must provide.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/abstract-base-class-without-abstract-method
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Mark the methods subclasses must override with `@abstractmethod`, or
|
|
23
|
+
2. drop `ABC` if the class isn't meant to be abstract.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
from abc import ABC
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Shape(ABC):
|
|
36
|
+
def area(self):
|
|
37
|
+
return 0
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Do
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
from abc import ABC, abstractmethod
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Shape(ABC):
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def area(self):
|
|
49
|
+
return 0
|
|
50
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# py/all-sorted — cases
|
|
2
|
+
|
|
3
|
+
### case: multi-line list
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
__all__ = [
|
|
7
|
+
"b",
|
|
8
|
+
"a",
|
|
9
|
+
]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```expect
|
|
13
|
+
1:11 py/all-sorted
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```fixed
|
|
17
|
+
__all__ = [
|
|
18
|
+
"a",
|
|
19
|
+
"b",
|
|
20
|
+
]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### case: several items per line
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
__all__ = ["b", "a",
|
|
27
|
+
"c"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```expect
|
|
31
|
+
1:11 py/all-sorted
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```fixed
|
|
35
|
+
__all__ = ["a",
|
|
36
|
+
"b",
|
|
37
|
+
"c"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### case: case-sensitive within a group
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
__all__ = ["LWPCookieJar", "LoadError"]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```expect
|
|
47
|
+
(none)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### case: augmented and bare tuples
|
|
51
|
+
|
|
52
|
+
```py
|
|
53
|
+
__all__ = "b", "a"
|
|
54
|
+
if x:
|
|
55
|
+
__all__ += ["d", "c"]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```expect
|
|
59
|
+
1:11 py/all-sorted
|
|
60
|
+
3:16 py/all-sorted
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### case: comments block the fix
|
|
64
|
+
|
|
65
|
+
```py
|
|
66
|
+
__all__ = [
|
|
67
|
+
"b", # keep
|
|
68
|
+
"a",
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```expect
|
|
73
|
+
1:11 py/all-sorted
|
|
74
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/all-sorted
|
|
3
|
+
title: "`__all__` is sorted"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: auto
|
|
6
|
+
stability: preview
|
|
7
|
+
ported_from: [ruff/RUF022]
|
|
8
|
+
since: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A module's `__all__` lists its names in isort's order: CONSTANTS, then Classes, then other names, each group in natural order.
|
|
12
|
+
|
|
13
|
+
**Why:** A sorted `__all__` is easy to scan and to keep in sync with the module.
|
|
14
|
+
|
|
15
|
+
```codelaw
|
|
16
|
+
native: py/all-sorted
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Python
|
|
22
|
+
|
|
23
|
+
#### Don't
|
|
24
|
+
|
|
25
|
+
```py
|
|
26
|
+
__all__ = ["save", "Loader", "load", "VERSION"]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Do
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
__all__ = ["VERSION", "Loader", "load", "save"]
|
|
33
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/ambiguous-class-name
|
|
3
|
+
title: "No classes named `l`, `O` or `I`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the new name is the author's choice, and other modules may use the old one"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E742]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Classes aren't named `l`, `O` or `I`.
|
|
13
|
+
|
|
14
|
+
**Why:** In many fonts they are indistinguishable from `1` and `0`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/ambiguous-class-name
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Rename the class.
|
|
23
|
+
2. Update its uses, here and in modules that import it.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
class I:
|
|
33
|
+
pass
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
class Item:
|
|
40
|
+
pass
|
|
41
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/ambiguous-function-name
|
|
3
|
+
title: "No functions named `l`, `O` or `I`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the new name is the author's choice, and other modules may use the old one"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E743]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Functions aren't named `l`, `O` or `I`.
|
|
13
|
+
|
|
14
|
+
**Why:** In many fonts they are indistinguishable from `1` and `0`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/ambiguous-function-name
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Rename the function.
|
|
23
|
+
2. Update its callers, here and in modules that import it.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
def O():
|
|
33
|
+
return 0
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
def origin():
|
|
40
|
+
return 0
|
|
41
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# py/ambiguous-variable-name — cases
|
|
2
|
+
|
|
3
|
+
### case: lambda parameters are not checked
|
|
4
|
+
|
|
5
|
+
```py
|
|
6
|
+
f = lambda l: l
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```expect
|
|
10
|
+
(none)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### case: loop and with targets
|
|
14
|
+
|
|
15
|
+
```py
|
|
16
|
+
for l in items:
|
|
17
|
+
pass
|
|
18
|
+
with open(p) as I:
|
|
19
|
+
pass
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```expect
|
|
23
|
+
1:5 py/ambiguous-variable-name
|
|
24
|
+
3:17 py/ambiguous-variable-name
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### case: import alias
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
import numpy as l
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```expect
|
|
34
|
+
1:17 py/ambiguous-variable-name
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### case: augmented assignment
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
def f(l):
|
|
41
|
+
l -= 1
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```expect
|
|
45
|
+
1:7 py/ambiguous-variable-name
|
|
46
|
+
2:5 py/ambiguous-variable-name
|
|
47
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/ambiguous-variable-name
|
|
3
|
+
title: "No variables named `l`, `O` or `I`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the new name is the author's choice"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/E741]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Variables, parameters and import aliases aren't named `l`, `O` or `I`.
|
|
13
|
+
|
|
14
|
+
**Why:** In many fonts they are indistinguishable from `1` and `0`.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/ambiguous-variable-name
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Rename the variable to a descriptive name.
|
|
23
|
+
2. Update its uses.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
l = len(items)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### Do
|
|
36
|
+
|
|
37
|
+
```py
|
|
38
|
+
length = len(items)
|
|
39
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/assert-false
|
|
3
|
+
title: "Don't `assert False`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: suggest
|
|
6
|
+
fix_reason: "the check now also runs under `python -O`, where the assert was skipped"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B011]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Code that must never be reached raises `AssertionError` instead of using `assert False`.
|
|
13
|
+
|
|
14
|
+
**Why:** `python -O` removes asserts, so the check silently disappears.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/assert-false
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Python
|
|
23
|
+
|
|
24
|
+
#### Don't
|
|
25
|
+
|
|
26
|
+
```py
|
|
27
|
+
assert False, "unreachable"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Do
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
raise AssertionError("unreachable")
|
|
34
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/assert-raises-exception
|
|
3
|
+
title: "Tests don't expect a bare `Exception`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "the exception the test expects has to be named by its author"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B017]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
`assertRaises(Exception)` and `pytest.raises(Exception)` (without `match=`) aren't used as context managers.
|
|
13
|
+
|
|
14
|
+
**Why:** They pass for any error at all, including a typo in the code under test.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/assert-raises-exception
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Name the specific exception the code should raise.
|
|
23
|
+
2. With `pytest.raises`, add `match=` if the class can't be narrowed.
|
|
24
|
+
|
|
25
|
+
## Examples
|
|
26
|
+
|
|
27
|
+
### Python
|
|
28
|
+
|
|
29
|
+
#### Don't
|
|
30
|
+
|
|
31
|
+
```py
|
|
32
|
+
import unittest
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestParse(unittest.TestCase):
|
|
36
|
+
def test_bad_input(self):
|
|
37
|
+
with self.assertRaises(Exception):
|
|
38
|
+
parse("x")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Do
|
|
42
|
+
|
|
43
|
+
```py
|
|
44
|
+
import unittest
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestParse(unittest.TestCase):
|
|
48
|
+
def test_bad_input(self):
|
|
49
|
+
with self.assertRaises(ValueError):
|
|
50
|
+
parse("x")
|
|
51
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/assert-tuple
|
|
3
|
+
title: "`assert` doesn't test a tuple"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "removing the parentheses turns a no-op into a check that can now fail"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/F631]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
An `assert` tests a condition, not a parenthesised `(condition, message)` tuple.
|
|
13
|
+
|
|
14
|
+
**Why:** A non-empty tuple is always true, so the assert can never fail.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/assert-tuple
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Remove the parentheses so the message becomes the assert's second operand.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
assert (count > 0, "count must be positive")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
#### Do
|
|
35
|
+
|
|
36
|
+
```py
|
|
37
|
+
assert count > 0, "count must be positive"
|
|
38
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: py/assignment-to-os-environ
|
|
3
|
+
title: "Don't assign to `os.environ`"
|
|
4
|
+
languages: [python]
|
|
5
|
+
fix: manual
|
|
6
|
+
fix_reason: "whether to clear the environment first or only update it needs a person"
|
|
7
|
+
stability: preview
|
|
8
|
+
ported_from: [ruff/B003]
|
|
9
|
+
since: 0.1.0
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Code changes the environment through `os.environ`'s items, never by assigning a new object to `os.environ`.
|
|
13
|
+
|
|
14
|
+
**Why:** Assigning replaces Python's mapping but not the process environment, so child processes don't see the change.
|
|
15
|
+
|
|
16
|
+
```codelaw
|
|
17
|
+
native: py/assignment-to-os-environ
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
1. Update `os.environ` item by item (`os.environ[key] = value`), clearing it first if that was meant.
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### Python
|
|
27
|
+
|
|
28
|
+
#### Don't
|
|
29
|
+
|
|
30
|
+
```py
|
|
31
|
+
import os
|
|
32
|
+
|
|
33
|
+
os.environ = {"PATH": "/bin"}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Do
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
os.environ.clear()
|
|
42
|
+
os.environ["PATH"] = "/bin"
|
|
43
|
+
```
|