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,38 @@
1
+ ---
2
+ id: py/format-invalid
3
+ title: "A `.format` string is well formed"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "whether the brace is a field or literal text has to be decided"
7
+ stability: preview
8
+ ported_from: [ruff/F521]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A string literal passed through `.format()` has balanced braces; literal braces are written `{{` and `}}`.
13
+
14
+ **Why:** A stray brace raises `ValueError` when the string is formatted.
15
+
16
+ ```codelaw
17
+ native: py/format-invalid
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Close the field, or double a literal brace (`{{`, `}}`).
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "{".format(1)
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "{}".format(1)
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/format-missing-argument
3
+ title: "Every `.format` field has an argument"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the missing value has to be supplied"
7
+ stability: preview
8
+ ported_from: [ruff/F524]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `.format()` call passes an argument for every `{}`, `{0}` and `{name}` field.
13
+
14
+ **Why:** A missing argument raises `IndexError` or `KeyError` when the string is formatted.
15
+
16
+ ```codelaw
17
+ native: py/format-missing-argument
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Pass the missing argument, or remove the field.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "{} is {}".format("Ada")
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "{} is {}".format("Ada", 36)
38
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: py/format-mixed-numbering
3
+ title: "A `.format` string doesn't mix `{}` and `{0}`"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "which numbering the author wants has to be chosen"
7
+ stability: preview
8
+ ported_from: [ruff/F525]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `.format()` string numbers its fields automatically (`{}`) or explicitly (`{0}`), not both.
13
+
14
+ **Why:** Python raises `ValueError: cannot switch from manual field specification to automatic field numbering`.
15
+
16
+ ```codelaw
17
+ native: py/format-mixed-numbering
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Number every field, or none of them.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ message = "{0} is {}".format("Ada", 36)
32
+ ```
33
+
34
+ #### Do
35
+
36
+ ```py
37
+ message = "{0} is {1}".format("Ada", 36)
38
+ ```
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: py/format-unused-named-argument
3
+ title: "Every keyword passed to `.format` is used"
4
+ languages: [python]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [ruff/F522]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ A `.format()` call has no keyword arguments the string never names.
12
+
13
+ **Why:** An unused keyword is usually a misspelled field name.
14
+
15
+ ```codelaw
16
+ native: py/format-unused-named-argument
17
+ ```
18
+
19
+ ## Examples
20
+
21
+ ### Python
22
+
23
+ #### Don't
24
+
25
+ ```py
26
+ message = "{name}".format(name="Ada", age=36)
27
+ ```
28
+
29
+ #### Do
30
+
31
+ ```py
32
+ message = "{name}".format(name="Ada")
33
+ ```
34
+
35
+ ## Differences from the source
36
+
37
+ The fix is offered only when the removed values are plain names or literals.
@@ -0,0 +1,37 @@
1
+ ---
2
+ id: py/format-unused-positional-argument
3
+ title: "Every positional argument to `.format` is used"
4
+ languages: [python]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [ruff/F523]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ A `.format()` call has no positional arguments the string never refers to.
12
+
13
+ **Why:** An extra argument is usually a missing `{}` field.
14
+
15
+ ```codelaw
16
+ native: py/format-unused-positional-argument
17
+ ```
18
+
19
+ ## Examples
20
+
21
+ ### Python
22
+
23
+ #### Don't
24
+
25
+ ```py
26
+ message = "{}".format("Ada", 36)
27
+ ```
28
+
29
+ #### Do
30
+
31
+ ```py
32
+ message = "{}".format("Ada")
33
+ ```
34
+
35
+ ## Differences from the source
36
+
37
+ The fix removes unused arguments only from the end of the list, and only plain names or literals.
@@ -0,0 +1,44 @@
1
+ ---
2
+ id: py/forward-annotation-syntax-error
3
+ title: "A string annotation is valid Python"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the intended type can't be inferred from broken text"
7
+ stability: preview
8
+ ported_from: [ruff/F722]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A type annotation written as a string parses as a Python expression.
13
+
14
+ **Why:** Type checkers and `typing.get_type_hints()` fail on an annotation they can't parse.
15
+
16
+ ```codelaw
17
+ native: py/forward-annotation-syntax-error
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Correct the annotation so it parses.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ def total(values: "list[int") -> int:
32
+ return sum(values)
33
+ ```
34
+
35
+ #### Do
36
+
37
+ ```py
38
+ def total(values: "list[int]") -> int:
39
+ return sum(values)
40
+ ```
41
+
42
+ ## Differences from the source
43
+
44
+ Strings inside `Literal[...]` and `Annotated[...]` are not treated as annotations.
@@ -0,0 +1,51 @@
1
+ ---
2
+ id: py/function-call-in-default-argument
3
+ title: "No function calls in default arguments"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "whether the value should be computed per call or once at import needs a person"
7
+ stability: preview
8
+ options:
9
+ extend_immutable_calls: { type: "string[]", default: [], description: "more calls whose results are immutable, by qualified name" }
10
+ ported_from: [ruff/B008]
11
+ since: 0.1.0
12
+ ---
13
+
14
+ A parameter's default isn't computed by a function call, except calls known to return immutable values (`tuple()`, `frozenset()`, `int()`, `re.compile()`, `datetime.timedelta()`, …).
15
+
16
+ **Why:** The call runs once, when the function is defined, so every call shares the same result — a stale timestamp, or one shared object.
17
+
18
+ ```codelaw
19
+ native: py/function-call-in-default-argument
20
+ ```
21
+
22
+ ## How to fix
23
+
24
+ 1. Default to `None` and compute the value in the body, or
25
+ 2. compute it once into a module-level constant and use that as the default.
26
+
27
+ ## Examples
28
+
29
+ ### Python
30
+
31
+ #### Don't
32
+
33
+ ```py
34
+ import time
35
+
36
+
37
+ def log(message, when=time.time()):
38
+ print(when, message)
39
+ ```
40
+
41
+ #### Do
42
+
43
+ ```py
44
+ import time
45
+
46
+
47
+ def log(message, when=None):
48
+ if when is None:
49
+ when = time.time()
50
+ print(when, message)
51
+ ```
@@ -0,0 +1,57 @@
1
+ # py/function-uses-loop-variable — cases
2
+
3
+ ### case: every read is reported
4
+
5
+ ```py
6
+ for i in range(3):
7
+ def f():
8
+ return i + i
9
+ ```
10
+
11
+ ```expect
12
+ 3:16 py/function-uses-loop-variable
13
+ 3:20 py/function-uses-loop-variable
14
+ ```
15
+
16
+ ### case: key functions run immediately
17
+
18
+ ```py
19
+ for i in range(3):
20
+ rows.sort(key=lambda r: r[i])
21
+ ```
22
+
23
+ ```expect
24
+ (none)
25
+ ```
26
+
27
+ ### case: map runs immediately
28
+
29
+ ```py
30
+ for i in range(3):
31
+ list(map(lambda x: x + i, xs))
32
+ ```
33
+
34
+ ```expect
35
+ (none)
36
+ ```
37
+
38
+ ### case: comprehension variable
39
+
40
+ ```py
41
+ fs = [lambda: i for i in range(3)]
42
+ ```
43
+
44
+ ```expect
45
+ 1:15 py/function-uses-loop-variable
46
+ ```
47
+
48
+ ### case: parameter with the same name
49
+
50
+ ```py
51
+ for i in range(3):
52
+ f = lambda i: i
53
+ ```
54
+
55
+ ```expect
56
+ (none)
57
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: py/function-uses-loop-variable
3
+ title: "Closures don't capture a loop variable by name"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "binding the value now (a default argument, `functools.partial`) changes the function's signature"
7
+ stability: preview
8
+ ported_from: [ruff/B023]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A function or lambda defined in a loop doesn't read a variable the loop rebinds, unless it is called or consumed straight away.
13
+
14
+ **Why:** Python closures look names up when they run, so every closure sees the loop's last value.
15
+
16
+ ```codelaw
17
+ native: py/function-uses-loop-variable
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Bind the current value with a default argument (`lambda name=name: …`) or `functools.partial`.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ callbacks = []
32
+ for name in ["a", "b"]:
33
+ callbacks.append(lambda: print(name))
34
+ ```
35
+
36
+ #### Do
37
+
38
+ ```py
39
+ callbacks = []
40
+ for name in ["a", "b"]:
41
+ callbacks.append(lambda name=name: print(name))
42
+ ```
@@ -0,0 +1,31 @@
1
+ # py/getattr-with-constant — cases
2
+
3
+ ### case: with a default it stays
4
+
5
+ ```py
6
+ name = getattr(user, "name", None)
7
+ ```
8
+
9
+ ```expect
10
+ (none)
11
+ ```
12
+
13
+ ### case: private names are mangled
14
+
15
+ ```py
16
+ x = getattr(self, "__secret")
17
+ ```
18
+
19
+ ```expect
20
+ (none)
21
+ ```
22
+
23
+ ### case: not an identifier
24
+
25
+ ```py
26
+ x = getattr(obj, "a-b")
27
+ ```
28
+
29
+ ```expect
30
+ (none)
31
+ ```
@@ -0,0 +1,33 @@
1
+ ---
2
+ id: py/getattr-with-constant
3
+ title: "No `getattr` with a constant name"
4
+ languages: [python]
5
+ fix: auto
6
+ stability: preview
7
+ ported_from: [ruff/B009]
8
+ since: 0.1.0
9
+ ---
10
+
11
+ `getattr(obj, "name")` with a constant, valid attribute name is written `obj.name`.
12
+
13
+ **Why:** `getattr` with a constant name is no safer than attribute access, only harder to read.
14
+
15
+ ```codelaw
16
+ native: py/getattr-with-constant
17
+ ```
18
+
19
+ ## Examples
20
+
21
+ ### Python
22
+
23
+ #### Don't
24
+
25
+ ```py
26
+ name = getattr(user, "name")
27
+ ```
28
+
29
+ #### Do
30
+
31
+ ```py
32
+ name = user.name
33
+ ```
@@ -0,0 +1,40 @@
1
+ ---
2
+ id: py/if-tuple
3
+ title: "`if` doesn't test a tuple"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the intended condition can't be inferred from the tuple"
7
+ stability: preview
8
+ ported_from: [ruff/F634]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ An `if` or `elif` condition isn't a non-empty tuple.
13
+
14
+ **Why:** A non-empty tuple is always true, so the branch always runs.
15
+
16
+ ```codelaw
17
+ native: py/if-tuple
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Write the condition you meant, usually the tuple's first element.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ if (ready, "waiting"):
32
+ start()
33
+ ```
34
+
35
+ #### Do
36
+
37
+ ```py
38
+ if ready:
39
+ start()
40
+ ```
@@ -0,0 +1,45 @@
1
+ ---
2
+ id: py/import-shadowed-by-loop-var
3
+ title: "A loop variable doesn't reuse an imported name"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the new loop variable name is a choice the author has to make"
7
+ stability: preview
8
+ ported_from: [ruff/F402]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `for` loop's target doesn't rebind a name that an import in the same scope bound.
13
+
14
+ **Why:** After the loop the name no longer refers to the module, so later uses break in ways that are hard to trace.
15
+
16
+ ```codelaw
17
+ native: py/import-shadowed-by-loop-var
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Rename the loop variable.
23
+ 2. Update its uses inside the loop body.
24
+
25
+ ## Examples
26
+
27
+ ### Python
28
+
29
+ #### Don't
30
+
31
+ ```py
32
+ import path
33
+
34
+ for path in ["a.txt", "b.txt"]:
35
+ print(path)
36
+ ```
37
+
38
+ #### Do
39
+
40
+ ```py
41
+ import path
42
+
43
+ for name in ["a.txt", "b.txt"]:
44
+ print(path.join(name))
45
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: py/invalid-print-syntax
3
+ title: "No Python 2 `print >>`"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "the rewrite depends on how the Python 2 arguments were meant"
7
+ stability: preview
8
+ ported_from: [ruff/F633]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ `print` is called as a function; the Python 2 form `print >> stream, value` isn't used.
13
+
14
+ **Why:** In Python 3 it shifts the `print` function right, raising `TypeError`.
15
+
16
+ ```codelaw
17
+ native: py/invalid-print-syntax
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Rewrite as `print(value, file=stream)`.
23
+
24
+ ## Examples
25
+
26
+ ### Python
27
+
28
+ #### Don't
29
+
30
+ ```py
31
+ import sys
32
+
33
+ print >> sys.stderr, "error"
34
+ ```
35
+
36
+ #### Do
37
+
38
+ ```py
39
+ import sys
40
+
41
+ print("error", file=sys.stderr)
42
+ ```
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: py/is-literal
3
+ title: "Literals are compared with `==`, not `is`"
4
+ languages: [python]
5
+ fix: suggest
6
+ fix_reason: "`==` calls the object's `__eq__`, which can behave differently from identity"
7
+ stability: preview
8
+ ported_from: [ruff/F632]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ `is` and `is not` aren't used to compare with a string, bytes or number literal.
13
+
14
+ **Why:** Whether two equal literals are the same object is an implementation detail; Python warns about it with `SyntaxWarning`.
15
+
16
+ ```codelaw
17
+ native: py/is-literal
18
+ ```
19
+
20
+ ## Examples
21
+
22
+ ### Python
23
+
24
+ #### Don't
25
+
26
+ ```py
27
+ if name is "admin":
28
+ grant()
29
+ ```
30
+
31
+ #### Do
32
+
33
+ ```py
34
+ if name == "admin":
35
+ grant()
36
+ ```
@@ -0,0 +1,15 @@
1
+ # py/jump-statement-in-finally — cases
2
+
3
+ ### case: break inside a loop inside finally
4
+
5
+ ```py
6
+ try:
7
+ pass
8
+ finally:
9
+ for x in y:
10
+ break
11
+ ```
12
+
13
+ ```expect
14
+ (none)
15
+ ```
@@ -0,0 +1,47 @@
1
+ ---
2
+ id: py/jump-statement-in-finally
3
+ title: "No `return`, `break` or `continue` in `finally`"
4
+ languages: [python]
5
+ fix: manual
6
+ fix_reason: "how errors should propagate has to be decided"
7
+ stability: preview
8
+ ported_from: [ruff/B012]
9
+ since: 0.1.0
10
+ ---
11
+
12
+ A `finally` block doesn't leave through `return`, `break` or `continue`.
13
+
14
+ **Why:** Leaving `finally` that way silently discards any exception in flight.
15
+
16
+ ```codelaw
17
+ native: py/jump-statement-in-finally
18
+ ```
19
+
20
+ ## How to fix
21
+
22
+ 1. Move the `return`, `break` or `continue` out of the `finally` block.
23
+ 2. Handle the exception explicitly with `except` if it should be swallowed.
24
+
25
+ ## Examples
26
+
27
+ ### Python
28
+
29
+ #### Don't
30
+
31
+ ```py
32
+ def read(path):
33
+ try:
34
+ return load(path)
35
+ finally:
36
+ return None
37
+ ```
38
+
39
+ #### Do
40
+
41
+ ```py
42
+ def read(path):
43
+ try:
44
+ return load(path)
45
+ finally:
46
+ close(path)
47
+ ```