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.
Files changed (139) hide show
  1. codelaw_rules_python/LICENSE +23 -0
  2. codelaw_rules_python/NOTICE +14 -0
  3. codelaw_rules_python/README.md +189 -0
  4. codelaw_rules_python/codelaw-pack.toml +6 -0
  5. codelaw_rules_python/corpus.toml +103 -0
  6. codelaw_rules_python/data/python/stdlib.json +331 -0
  7. codelaw_rules_python/presets/py/conventions.toml +5 -0
  8. codelaw_rules_python/presets/py/project.toml +4 -0
  9. codelaw_rules_python/presets/py/recommended.toml +99 -0
  10. codelaw_rules_python/rules/graph/no-unused-exports.md +69 -0
  11. codelaw_rules_python/rules/py/abstract-base-class-without-abstract-method.md +50 -0
  12. codelaw_rules_python/rules/py/all-sorted.cases.md +74 -0
  13. codelaw_rules_python/rules/py/all-sorted.md +33 -0
  14. codelaw_rules_python/rules/py/ambiguous-class-name.md +41 -0
  15. codelaw_rules_python/rules/py/ambiguous-function-name.md +41 -0
  16. codelaw_rules_python/rules/py/ambiguous-variable-name.cases.md +47 -0
  17. codelaw_rules_python/rules/py/ambiguous-variable-name.md +39 -0
  18. codelaw_rules_python/rules/py/assert-false.md +34 -0
  19. codelaw_rules_python/rules/py/assert-raises-exception.md +51 -0
  20. codelaw_rules_python/rules/py/assert-tuple.md +38 -0
  21. codelaw_rules_python/rules/py/assignment-to-os-environ.md +43 -0
  22. codelaw_rules_python/rules/py/batched-without-explicit-strict.md +52 -0
  23. codelaw_rules_python/rules/py/break-outside-loop.md +41 -0
  24. codelaw_rules_python/rules/py/cached-instance-method.md +55 -0
  25. codelaw_rules_python/rules/py/continue-outside-loop.md +41 -0
  26. codelaw_rules_python/rules/py/default-except-not-last.md +48 -0
  27. codelaw_rules_python/rules/py/duplicate-dict-key-variable.md +39 -0
  28. codelaw_rules_python/rules/py/duplicate-dict-key.md +39 -0
  29. codelaw_rules_python/rules/py/duplicate-handler-exception.md +43 -0
  30. codelaw_rules_python/rules/py/duplicate-set-value.md +33 -0
  31. codelaw_rules_python/rules/py/duplicate-try-block-exception.md +47 -0
  32. codelaw_rules_python/rules/py/empty-method-without-abstract-decorator.md +55 -0
  33. codelaw_rules_python/rules/py/except-with-empty-tuple.md +44 -0
  34. codelaw_rules_python/rules/py/except-with-non-exception-classes.md +44 -0
  35. codelaw_rules_python/rules/py/f-string-docstring.md +40 -0
  36. codelaw_rules_python/rules/py/f-string-missing-placeholders.cases.md +49 -0
  37. codelaw_rules_python/rules/py/f-string-missing-placeholders.md +33 -0
  38. codelaw_rules_python/rules/py/format-invalid.md +38 -0
  39. codelaw_rules_python/rules/py/format-missing-argument.md +38 -0
  40. codelaw_rules_python/rules/py/format-mixed-numbering.md +38 -0
  41. codelaw_rules_python/rules/py/format-unused-named-argument.md +37 -0
  42. codelaw_rules_python/rules/py/format-unused-positional-argument.md +37 -0
  43. codelaw_rules_python/rules/py/forward-annotation-syntax-error.md +44 -0
  44. codelaw_rules_python/rules/py/function-call-in-default-argument.md +51 -0
  45. codelaw_rules_python/rules/py/function-uses-loop-variable.cases.md +57 -0
  46. codelaw_rules_python/rules/py/function-uses-loop-variable.md +42 -0
  47. codelaw_rules_python/rules/py/getattr-with-constant.cases.md +31 -0
  48. codelaw_rules_python/rules/py/getattr-with-constant.md +33 -0
  49. codelaw_rules_python/rules/py/if-tuple.md +40 -0
  50. codelaw_rules_python/rules/py/import-shadowed-by-loop-var.md +45 -0
  51. codelaw_rules_python/rules/py/invalid-print-syntax.md +42 -0
  52. codelaw_rules_python/rules/py/is-literal.md +36 -0
  53. codelaw_rules_python/rules/py/jump-statement-in-finally.cases.md +15 -0
  54. codelaw_rules_python/rules/py/jump-statement-in-finally.md +47 -0
  55. codelaw_rules_python/rules/py/lambda-assignment.cases.md +48 -0
  56. codelaw_rules_python/rules/py/lambda-assignment.md +35 -0
  57. codelaw_rules_python/rules/py/late-future-import.md +40 -0
  58. codelaw_rules_python/rules/py/loop-variable-overrides-iterator.md +41 -0
  59. codelaw_rules_python/rules/py/map-without-explicit-strict.md +46 -0
  60. codelaw_rules_python/rules/py/module-import-not-at-top.cases.md +64 -0
  61. codelaw_rules_python/rules/py/module-import-not-at-top.md +46 -0
  62. codelaw_rules_python/rules/py/multiple-imports-on-one-line.md +38 -0
  63. codelaw_rules_python/rules/py/multiple-starred-expressions.md +38 -0
  64. codelaw_rules_python/rules/py/mutable-contextvar-default.md +42 -0
  65. codelaw_rules_python/rules/py/nested-star-import.md +42 -0
  66. codelaw_rules_python/rules/py/no-bare-except.cases.md +37 -0
  67. codelaw_rules_python/rules/py/no-bare-except.md +44 -0
  68. codelaw_rules_python/rules/py/no-explicit-stacklevel.md +38 -0
  69. codelaw_rules_python/rules/py/no-mutable-default.cases.md +58 -0
  70. codelaw_rules_python/rules/py/no-mutable-default.md +44 -0
  71. codelaw_rules_python/rules/py/no-print.cases.md +40 -0
  72. codelaw_rules_python/rules/py/no-print.md +42 -0
  73. codelaw_rules_python/rules/py/no-star-import.cases.md +11 -0
  74. codelaw_rules_python/rules/py/no-star-import.md +47 -0
  75. codelaw_rules_python/rules/py/no-tunable-literals.cases.md +54 -0
  76. codelaw_rules_python/rules/py/no-tunable-literals.md +58 -0
  77. codelaw_rules_python/rules/py/none-comparison.md +36 -0
  78. codelaw_rules_python/rules/py/not-in-test.md +35 -0
  79. codelaw_rules_python/rules/py/not-is-test.md +35 -0
  80. codelaw_rules_python/rules/py/percent-format-count-mismatch.md +38 -0
  81. codelaw_rules_python/rules/py/percent-format-expected-mapping.md +38 -0
  82. codelaw_rules_python/rules/py/percent-format-expected-sequence.md +38 -0
  83. codelaw_rules_python/rules/py/percent-format-invalid.md +38 -0
  84. codelaw_rules_python/rules/py/percent-format-missing-argument.md +38 -0
  85. codelaw_rules_python/rules/py/percent-format-mixed-placeholders.md +38 -0
  86. codelaw_rules_python/rules/py/percent-format-star-requires-sequence.md +38 -0
  87. codelaw_rules_python/rules/py/percent-format-unsupported-character.md +38 -0
  88. codelaw_rules_python/rules/py/percent-format-unused-named-argument.md +37 -0
  89. codelaw_rules_python/rules/py/raise-literal.md +38 -0
  90. codelaw_rules_python/rules/py/raise-not-implemented.md +38 -0
  91. codelaw_rules_python/rules/py/raise-without-from-inside-except.cases.md +46 -0
  92. codelaw_rules_python/rules/py/raise-without-from-inside-except.md +45 -0
  93. codelaw_rules_python/rules/py/re-sub-positional-args.md +42 -0
  94. codelaw_rules_python/rules/py/redefined-while-unused.cases.md +186 -0
  95. codelaw_rules_python/rules/py/redefined-while-unused.md +53 -0
  96. codelaw_rules_python/rules/py/redundant-tuple-in-exception-handler.md +39 -0
  97. codelaw_rules_python/rules/py/return-outside-function.md +41 -0
  98. codelaw_rules_python/rules/py/reuse-of-groupby-generator.cases.md +40 -0
  99. codelaw_rules_python/rules/py/reuse-of-groupby-generator.md +46 -0
  100. codelaw_rules_python/rules/py/setattr-with-constant.md +33 -0
  101. codelaw_rules_python/rules/py/star-arg-unpacking-after-keyword-arg.md +38 -0
  102. codelaw_rules_python/rules/py/star-import-usage.cases.md +35 -0
  103. codelaw_rules_python/rules/py/star-import-usage.md +43 -0
  104. codelaw_rules_python/rules/py/static-key-dict-comprehension.md +38 -0
  105. codelaw_rules_python/rules/py/strip-with-multi-characters.md +39 -0
  106. codelaw_rules_python/rules/py/too-many-expressions-in-star-assignment.md +38 -0
  107. codelaw_rules_python/rules/py/true-false-comparison.cases.md +33 -0
  108. codelaw_rules_python/rules/py/true-false-comparison.md +36 -0
  109. codelaw_rules_python/rules/py/type-comparison.md +41 -0
  110. codelaw_rules_python/rules/py/unary-prefix-increment.md +40 -0
  111. codelaw_rules_python/rules/py/undefined-export.md +55 -0
  112. codelaw_rules_python/rules/py/undefined-future-feature.md +39 -0
  113. codelaw_rules_python/rules/py/undefined-local.cases.md +80 -0
  114. codelaw_rules_python/rules/py/undefined-local.md +48 -0
  115. codelaw_rules_python/rules/py/undefined-name.cases.md +132 -0
  116. codelaw_rules_python/rules/py/undefined-name.md +42 -0
  117. codelaw_rules_python/rules/py/unintentional-type-annotation.cases.md +13 -0
  118. codelaw_rules_python/rules/py/unintentional-type-annotation.md +38 -0
  119. codelaw_rules_python/rules/py/unreliable-callable-check.md +36 -0
  120. codelaw_rules_python/rules/py/unsorted-imports.cases.md +159 -0
  121. codelaw_rules_python/rules/py/unsorted-imports.md +60 -0
  122. codelaw_rules_python/rules/py/unused-annotation.md +36 -0
  123. codelaw_rules_python/rules/py/unused-import.cases.md +289 -0
  124. codelaw_rules_python/rules/py/unused-import.md +45 -0
  125. codelaw_rules_python/rules/py/unused-loop-control-variable.cases.md +30 -0
  126. codelaw_rules_python/rules/py/unused-loop-control-variable.md +36 -0
  127. codelaw_rules_python/rules/py/unused-variable.cases.md +169 -0
  128. codelaw_rules_python/rules/py/unused-variable.md +38 -0
  129. codelaw_rules_python/rules/py/useless-comparison.md +41 -0
  130. codelaw_rules_python/rules/py/useless-contextlib-suppress.md +44 -0
  131. codelaw_rules_python/rules/py/useless-expression.cases.md +57 -0
  132. codelaw_rules_python/rules/py/useless-expression.md +45 -0
  133. codelaw_rules_python/rules/py/yield-outside-function.md +44 -0
  134. codelaw_rules_python/rules/py/zip-without-explicit-strict.cases.md +26 -0
  135. codelaw_rules_python/rules/py/zip-without-explicit-strict.md +46 -0
  136. codelaw_rules_python-0.2.0.dist-info/METADATA +199 -0
  137. codelaw_rules_python-0.2.0.dist-info/RECORD +139 -0
  138. codelaw_rules_python-0.2.0.dist-info/WHEEL +4 -0
  139. 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
+ ```