sarj-python-lint 0.24.0__tar.gz → 0.26.0__tar.gz
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.
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/.gitignore +1 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/PKG-INFO +7 -6
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/README.md +6 -5
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/ratchet.py +8 -8
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rule_base.py +1 -1
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_comments.py +128 -1
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_first_party.py +23 -17
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_registry.py +24 -28
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +2 -2
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/duplicate_test_body.py +9 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/kwonly_same_type_params.py +8 -6
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/mock_without_spec.py +1 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_comment_cruft.py +67 -15
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +1 -1
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +2 -2
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/no_implicit_attribute_access.py +106 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_patching_system_under_test.py +2 -2
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_restated_comment.py +2 -6
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +2 -2
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/over_mocked_test.py +65 -17
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +33 -6
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +3 -3
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +72 -12
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +275 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +141 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_pattern_matching.py +340 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_self_type_annotation.py +159 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_str_enum.py +1 -4
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +125 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +143 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +133 -0
- sarj_python_lint-0.26.0/src/sarj_python_lint/rules/primary_export_file_name.py +149 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/redundant_docstring.py +97 -17
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/require_port_for_service.py +6 -2
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/stepdown.py +4 -4
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/trailing_value_narration.py +65 -14
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/xfail_requires_strict.py +1 -1
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_fstring_in_log.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/single_public_export.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/zero_assertion_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://github.com/sarj-ai/standards/tree/main/packages/python
|
|
6
6
|
Project-URL: Repository, https://github.com/sarj-ai/standards
|
|
@@ -40,6 +40,7 @@ uv tool install sarj-python-lint
|
|
|
40
40
|
- id: sarj-prefer-struct-over-namedtuple
|
|
41
41
|
- id: sarj-no-comment-cruft
|
|
42
42
|
- id: sarj-no-fstring-in-log
|
|
43
|
+
- id: sarj-prefer-non-nullable-collection # SARJ074
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
### Test-quality rules (0.15.0)
|
|
@@ -239,7 +240,7 @@ disappears — and the query still runs — whenever the filter is empty:
|
|
|
239
240
|
|
|
240
241
|
```python
|
|
241
242
|
where_conditions = []
|
|
242
|
-
if args.organization_ids:
|
|
243
|
+
if args.organization_ids: # ← optional
|
|
243
244
|
where_conditions.append(SQL("organization_id = ANY(%s::uuid[])"))
|
|
244
245
|
...
|
|
245
246
|
where_clause = SQL(" AND ").join(where_conditions) if where_conditions else SQL("1=1")
|
|
@@ -281,12 +282,12 @@ is the assertion whose real condition slid out of the condition slot, because it
|
|
|
281
282
|
was a working assertion when it was typed:
|
|
282
283
|
|
|
283
284
|
```python
|
|
284
|
-
assert {
|
|
285
|
+
assert { # ← braces, not parentheses
|
|
285
286
|
"referencing a non existing `via_device` " in caplog.text
|
|
286
|
-
}
|
|
287
|
+
} # one-element SET, always truthy
|
|
287
288
|
|
|
288
|
-
assert [f"No logs found on hdfs for ti={ti}"]
|
|
289
|
-
assert True, cover_result_json[0]["success"][...]
|
|
289
|
+
assert [f"No logs found on hdfs for ti={ti}"] # the `== messages` was lost
|
|
290
|
+
assert True, cover_result_json[0]["success"][...] # slid into the MESSAGE slot
|
|
290
291
|
```
|
|
291
292
|
|
|
292
293
|
**The narrowness is the rule.** The obvious generalisation — "flag a comparison
|
|
@@ -22,6 +22,7 @@ uv tool install sarj-python-lint
|
|
|
22
22
|
- id: sarj-prefer-struct-over-namedtuple
|
|
23
23
|
- id: sarj-no-comment-cruft
|
|
24
24
|
- id: sarj-no-fstring-in-log
|
|
25
|
+
- id: sarj-prefer-non-nullable-collection # SARJ074
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
### Test-quality rules (0.15.0)
|
|
@@ -221,7 +222,7 @@ disappears — and the query still runs — whenever the filter is empty:
|
|
|
221
222
|
|
|
222
223
|
```python
|
|
223
224
|
where_conditions = []
|
|
224
|
-
if args.organization_ids:
|
|
225
|
+
if args.organization_ids: # ← optional
|
|
225
226
|
where_conditions.append(SQL("organization_id = ANY(%s::uuid[])"))
|
|
226
227
|
...
|
|
227
228
|
where_clause = SQL(" AND ").join(where_conditions) if where_conditions else SQL("1=1")
|
|
@@ -263,12 +264,12 @@ is the assertion whose real condition slid out of the condition slot, because it
|
|
|
263
264
|
was a working assertion when it was typed:
|
|
264
265
|
|
|
265
266
|
```python
|
|
266
|
-
assert {
|
|
267
|
+
assert { # ← braces, not parentheses
|
|
267
268
|
"referencing a non existing `via_device` " in caplog.text
|
|
268
|
-
}
|
|
269
|
+
} # one-element SET, always truthy
|
|
269
270
|
|
|
270
|
-
assert [f"No logs found on hdfs for ti={ti}"]
|
|
271
|
-
assert True, cover_result_json[0]["success"][...]
|
|
271
|
+
assert [f"No logs found on hdfs for ti={ti}"] # the `== messages` was lost
|
|
272
|
+
assert True, cover_result_json[0]["success"][...] # slid into the MESSAGE slot
|
|
272
273
|
```
|
|
273
274
|
|
|
274
275
|
**The narrowness is the rule.** The obvious generalisation — "flag a comparison
|
|
@@ -210,14 +210,14 @@ def gate(measurement: Measurement, baseline: Baseline) -> list[Failure]:
|
|
|
210
210
|
|
|
211
211
|
"""
|
|
212
212
|
failures = [
|
|
213
|
-
Failure(dimension="code", key=key, ceiling=
|
|
213
|
+
Failure(dimension="code", key=key, ceiling=c, actual=n)
|
|
214
214
|
for key, n in sorted(measurement.codes.items())
|
|
215
|
-
if n > baseline.codes.get(key, 0)
|
|
215
|
+
if n > (c := baseline.codes.get(key, 0))
|
|
216
216
|
]
|
|
217
217
|
failures += [
|
|
218
|
-
Failure(dimension="package", key=key, ceiling=
|
|
218
|
+
Failure(dimension="package", key=key, ceiling=c, actual=n)
|
|
219
219
|
for key, n in sorted(measurement.packages.items())
|
|
220
|
-
if n > baseline.packages.get(key, 0)
|
|
220
|
+
if n > (c := baseline.packages.get(key, 0))
|
|
221
221
|
]
|
|
222
222
|
failures += [
|
|
223
223
|
Failure(dimension="file", key=key, ceiling=ceiling, actual=n)
|
|
@@ -337,11 +337,11 @@ def discover_packages(root: Path, excluded_dir_names: frozenset[str] = DEFAULT_E
|
|
|
337
337
|
|
|
338
338
|
"""
|
|
339
339
|
return sorted(
|
|
340
|
-
|
|
340
|
+
name
|
|
341
341
|
for child in root.iterdir()
|
|
342
|
-
if child.
|
|
343
|
-
and
|
|
344
|
-
and
|
|
342
|
+
if (name := child.name) not in excluded_dir_names
|
|
343
|
+
and not name.startswith(".")
|
|
344
|
+
and child.is_dir()
|
|
345
345
|
and any(True for _ in _python_files(child, excluded_dir_names))
|
|
346
346
|
)
|
|
347
347
|
|
|
@@ -46,7 +46,7 @@ def is_suppressed(source_lines: Sequence[str], line: int, code: str) -> bool:
|
|
|
46
46
|
if not codes_str:
|
|
47
47
|
# Bare `# sarj-noqa` suppresses every SARJ code on the line
|
|
48
48
|
return True
|
|
49
|
-
codes = {
|
|
49
|
+
codes = {val.upper() for c in codes_str.split(",") if (val := c.strip())}
|
|
50
50
|
return code.upper() in codes
|
|
51
51
|
|
|
52
52
|
|
|
@@ -180,7 +180,134 @@ _CAMEL_RE = re.compile(r"[A-Z]+(?=[A-Z][a-z])|[A-Z]?[a-z]+|[A-Z]+|\d+")
|
|
|
180
180
|
# the prototype's list: shrinking it costs recall, growing it costs precision by
|
|
181
181
|
# letting a genuinely novel word be discounted.
|
|
182
182
|
STOPWORDS: frozenset[str] = frozenset(
|
|
183
|
-
[
|
|
183
|
+
[
|
|
184
|
+
"a",
|
|
185
|
+
"an",
|
|
186
|
+
"the",
|
|
187
|
+
"this",
|
|
188
|
+
"that",
|
|
189
|
+
"these",
|
|
190
|
+
"those",
|
|
191
|
+
"it",
|
|
192
|
+
"its",
|
|
193
|
+
"their",
|
|
194
|
+
"his",
|
|
195
|
+
"her",
|
|
196
|
+
"our",
|
|
197
|
+
"your",
|
|
198
|
+
"my",
|
|
199
|
+
"is",
|
|
200
|
+
"are",
|
|
201
|
+
"was",
|
|
202
|
+
"were",
|
|
203
|
+
"be",
|
|
204
|
+
"been",
|
|
205
|
+
"being",
|
|
206
|
+
"am",
|
|
207
|
+
"do",
|
|
208
|
+
"does",
|
|
209
|
+
"did",
|
|
210
|
+
"done",
|
|
211
|
+
"doing",
|
|
212
|
+
"has",
|
|
213
|
+
"have",
|
|
214
|
+
"had",
|
|
215
|
+
"having",
|
|
216
|
+
"will",
|
|
217
|
+
"would",
|
|
218
|
+
"shall",
|
|
219
|
+
"should",
|
|
220
|
+
"can",
|
|
221
|
+
"could",
|
|
222
|
+
"may",
|
|
223
|
+
"might",
|
|
224
|
+
"must",
|
|
225
|
+
"and",
|
|
226
|
+
"or",
|
|
227
|
+
"but",
|
|
228
|
+
"nor",
|
|
229
|
+
"so",
|
|
230
|
+
"yet",
|
|
231
|
+
"not",
|
|
232
|
+
"no",
|
|
233
|
+
"none",
|
|
234
|
+
"to",
|
|
235
|
+
"of",
|
|
236
|
+
"for",
|
|
237
|
+
"in",
|
|
238
|
+
"on",
|
|
239
|
+
"at",
|
|
240
|
+
"by",
|
|
241
|
+
"with",
|
|
242
|
+
"from",
|
|
243
|
+
"into",
|
|
244
|
+
"onto",
|
|
245
|
+
"out",
|
|
246
|
+
"up",
|
|
247
|
+
"down",
|
|
248
|
+
"over",
|
|
249
|
+
"under",
|
|
250
|
+
"about",
|
|
251
|
+
"as",
|
|
252
|
+
"if",
|
|
253
|
+
"then",
|
|
254
|
+
"than",
|
|
255
|
+
"when",
|
|
256
|
+
"where",
|
|
257
|
+
"which",
|
|
258
|
+
"who",
|
|
259
|
+
"whom",
|
|
260
|
+
"whose",
|
|
261
|
+
"what",
|
|
262
|
+
"how",
|
|
263
|
+
"why",
|
|
264
|
+
"while",
|
|
265
|
+
"we",
|
|
266
|
+
"you",
|
|
267
|
+
"they",
|
|
268
|
+
"i",
|
|
269
|
+
"he",
|
|
270
|
+
"she",
|
|
271
|
+
"them",
|
|
272
|
+
"him",
|
|
273
|
+
"us",
|
|
274
|
+
"me",
|
|
275
|
+
"also",
|
|
276
|
+
"just",
|
|
277
|
+
"only",
|
|
278
|
+
"even",
|
|
279
|
+
"still",
|
|
280
|
+
"already",
|
|
281
|
+
"again",
|
|
282
|
+
"there",
|
|
283
|
+
"here",
|
|
284
|
+
"all",
|
|
285
|
+
"any",
|
|
286
|
+
"each",
|
|
287
|
+
"every",
|
|
288
|
+
"some",
|
|
289
|
+
"via",
|
|
290
|
+
"per",
|
|
291
|
+
"etc",
|
|
292
|
+
"eg",
|
|
293
|
+
"ie",
|
|
294
|
+
"vs",
|
|
295
|
+
"need",
|
|
296
|
+
"needs",
|
|
297
|
+
"needed",
|
|
298
|
+
"want",
|
|
299
|
+
"wants",
|
|
300
|
+
"make",
|
|
301
|
+
"makes",
|
|
302
|
+
"making",
|
|
303
|
+
"let",
|
|
304
|
+
"lets",
|
|
305
|
+
"please",
|
|
306
|
+
"note",
|
|
307
|
+
"see",
|
|
308
|
+
"above",
|
|
309
|
+
"below",
|
|
310
|
+
]
|
|
184
311
|
)
|
|
185
312
|
|
|
186
313
|
|
{sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
@@ -50,23 +50,25 @@ _PROJECT_MARKER = "pyproject.toml"
|
|
|
50
50
|
|
|
51
51
|
# Never descend into these: a virtualenv or vendored tree holds *third-party*
|
|
52
52
|
# packages, and collecting their names would classify every dependency as ours.
|
|
53
|
-
_SKIP_DIR_NAMES = frozenset(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
_SKIP_DIR_NAMES = frozenset(
|
|
54
|
+
{
|
|
55
|
+
".git",
|
|
56
|
+
".mypy_cache",
|
|
57
|
+
".next",
|
|
58
|
+
".pytest_cache",
|
|
59
|
+
".ruff_cache",
|
|
60
|
+
".tox",
|
|
61
|
+
".turbo",
|
|
62
|
+
".venv",
|
|
63
|
+
"__pycache__",
|
|
64
|
+
"build",
|
|
65
|
+
"coverage",
|
|
66
|
+
"dist",
|
|
67
|
+
"node_modules",
|
|
68
|
+
"site-packages",
|
|
69
|
+
"venv",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
70
72
|
|
|
71
73
|
# Deep enough for a uv/pnpm-style monorepo (`<root>/packages/python/src/<pkg>`),
|
|
72
74
|
# shallow enough that the scan stays a few hundred `iterdir()` calls.
|
|
@@ -119,6 +121,10 @@ def own_top_package(path: Path) -> str | None:
|
|
|
119
121
|
top = ancestor.name
|
|
120
122
|
if ancestor == root:
|
|
121
123
|
break
|
|
124
|
+
if top is None and root is not None:
|
|
125
|
+
roots = _first_party_roots(root)
|
|
126
|
+
if len(roots) == 1:
|
|
127
|
+
return next(iter(roots))
|
|
122
128
|
return top
|
|
123
129
|
|
|
124
130
|
|
|
@@ -27,6 +27,7 @@ from sarj_python_lint.rules.no_first_party_private_import import (
|
|
|
27
27
|
)
|
|
28
28
|
from sarj_python_lint.rules.no_fstring_in_log import NoFstringInLog
|
|
29
29
|
from sarj_python_lint.rules.no_gen_random_uuid_in_sql import NoGenRandomUuidInSql
|
|
30
|
+
from sarj_python_lint.rules.no_implicit_attribute_access import NoImplicitAttributeAccess
|
|
30
31
|
from sarj_python_lint.rules.no_isinstance_union_chain import NoIsinstanceUnionChain
|
|
31
32
|
from sarj_python_lint.rules.no_offset_pagination import NoOffsetPagination
|
|
32
33
|
from sarj_python_lint.rules.no_optional_tenant_predicate import (
|
|
@@ -57,14 +58,20 @@ from sarj_python_lint.rules.prefer_fstring_over_concat import PreferFstringOverC
|
|
|
57
58
|
from sarj_python_lint.rules.prefer_library_fake import PreferLibraryFake
|
|
58
59
|
from sarj_python_lint.rules.prefer_match_assert_never import PreferMatchAssertNever
|
|
59
60
|
from sarj_python_lint.rules.prefer_match_pattern_destructuring import PreferMatchPatternDestructuring
|
|
61
|
+
from sarj_python_lint.rules.prefer_match_type_dispatch import PreferMatchTypeDispatch
|
|
60
62
|
from sarj_python_lint.rules.prefer_module_level_constant import (
|
|
61
63
|
PreferModuleLevelConstant,
|
|
62
64
|
)
|
|
63
65
|
from sarj_python_lint.rules.prefer_namedtuple_over_tuple_return import (
|
|
64
66
|
PreferNamedtupleOverTupleReturn,
|
|
65
67
|
)
|
|
68
|
+
from sarj_python_lint.rules.prefer_non_nullable_collection import (
|
|
69
|
+
PreferNonNullableCollection,
|
|
70
|
+
)
|
|
66
71
|
from sarj_python_lint.rules.prefer_or_pattern import PreferOrPattern
|
|
72
|
+
from sarj_python_lint.rules.prefer_pattern_matching import PreferPatternMatching
|
|
67
73
|
from sarj_python_lint.rules.prefer_real_store_in_tests import PreferRealStoreInTests
|
|
74
|
+
from sarj_python_lint.rules.prefer_self_type_annotation import PreferSelfTypeAnnotation
|
|
68
75
|
from sarj_python_lint.rules.prefer_str_enum import PreferStrEnum
|
|
69
76
|
from sarj_python_lint.rules.prefer_struct_over_namedtuple import (
|
|
70
77
|
PreferStructOverNamedtuple,
|
|
@@ -72,6 +79,14 @@ from sarj_python_lint.rules.prefer_struct_over_namedtuple import (
|
|
|
72
79
|
from sarj_python_lint.rules.prefer_timedelta_for_durations import (
|
|
73
80
|
PreferTimedeltaForDurations,
|
|
74
81
|
)
|
|
82
|
+
from sarj_python_lint.rules.prefer_walrus_comprehension_filter import (
|
|
83
|
+
PreferWalrusComprehensionFilter,
|
|
84
|
+
)
|
|
85
|
+
from sarj_python_lint.rules.prefer_walrus_regex_match import PreferWalrusRegexMatch
|
|
86
|
+
from sarj_python_lint.rules.prefer_walrus_stream_loop import PreferWalrusStreamLoop
|
|
87
|
+
from sarj_python_lint.rules.primary_export_file_name import (
|
|
88
|
+
PrimaryExportFileName,
|
|
89
|
+
)
|
|
75
90
|
from sarj_python_lint.rules.pydantic_at_boundaries import PydanticAtBoundaries
|
|
76
91
|
from sarj_python_lint.rules.redundant_docstring import RedundantDocstring
|
|
77
92
|
from sarj_python_lint.rules.require_port_for_service import RequirePortForService
|
|
@@ -95,34 +110,6 @@ from sarj_python_lint.rules.zero_assertion_test import ZeroAssertionTest
|
|
|
95
110
|
if TYPE_CHECKING:
|
|
96
111
|
from sarj_python_lint.rule_base import Rule
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
# Retired codes — never reuse these for new rules:
|
|
100
|
-
# SARJ004, SARJ005 (retired before the standards merge),
|
|
101
|
-
# SARJ027, SARJ029, SARJ030 (dropped in 0.11.1 as too noisy),
|
|
102
|
-
# SARJ033 httpx-client-requires-timeout, SARJ035 no-import-time-settings
|
|
103
|
-
# (dropped by user veto after the 0.13.x mined-rules review),
|
|
104
|
-
# SARJ055 no-filler-success-adverb (built and corpus-validated, then dropped
|
|
105
|
-
# — but not for the reason first recorded. The headline "4.7% of Airflow's
|
|
106
|
-
# info/debug logs" is a composition artifact: 237 of those 248 hits are in
|
|
107
|
-
# `providers/` (contributed vendor operators, heavily copy-pasted — the
|
|
108
|
-
# template "%s completed successfully." appears 22 times verbatim). Airflow's
|
|
109
|
-
# maintainer-owned `airflow-core/` sits at 1.15% and Home Assistant at 1.24%,
|
|
110
|
-
# so the real external baseline is ~1.2%, not 4.7%. The rule was re-measured
|
|
111
|
-
# and dropped on a stronger basis: the narrow variant — fire only when the
|
|
112
|
-
# adverb is the sole content beyond a bare verb — has 12 external hits and
|
|
113
|
-
# ZERO internal ones across all six repos, so it would govern nothing we
|
|
114
|
-
# write. The broad rule remains opt-in house style, not a defect check.
|
|
115
|
-
# (Noted for any future revisit: noura-be measures 7.92%, a genuine outlier
|
|
116
|
-
# against every corpus; that, not the narrow variant, is the case to make.),
|
|
117
|
-
# SARJ037 no-trivial-single-use-helper (prototyped and dropped for FP rate;
|
|
118
|
-
# see the 0.13.1 inlining commit for the corpus analysis).
|
|
119
|
-
# SARJ072 unbound-mock-assertion and SARJ073 raises-needs-specific-error
|
|
120
|
-
# (built, corpus-measured and dropped before ever being registered, in the
|
|
121
|
-
# wave that added SARJ058-071). SARJ072 found 0 hits across 16,130 files in
|
|
122
|
-
# 9 repos, including 0 against 8,322 real mock-assertion API uses; ruff B018
|
|
123
|
-
# covers its bare-statement shape. SARJ073 was a strict superset of ruff
|
|
124
|
-
# PT011, which this standard already enables — its one real gap,
|
|
125
|
-
# RuntimeError, is closed by `lint.flake8-pytest-style.raises-require-match-for`.
|
|
126
113
|
REGISTRY: dict[str, type[Rule]] = {
|
|
127
114
|
NoSequentialAwait.id: NoSequentialAwait,
|
|
128
115
|
InefficientStringConcatInLoop.id: InefficientStringConcatInLoop,
|
|
@@ -130,6 +117,7 @@ REGISTRY: dict[str, type[Rule]] = {
|
|
|
130
117
|
PreferStrEnum.id: PreferStrEnum,
|
|
131
118
|
NoFatTryBlocks.id: NoFatTryBlocks,
|
|
132
119
|
NoIsinstanceUnionChain.id: NoIsinstanceUnionChain,
|
|
120
|
+
NoImplicitAttributeAccess.id: NoImplicitAttributeAccess,
|
|
133
121
|
NoOffsetPagination.id: NoOffsetPagination,
|
|
134
122
|
PreferNamedtupleOverTupleReturn.id: PreferNamedtupleOverTupleReturn,
|
|
135
123
|
NoCorsWildcardWithCredentials.id: NoCorsWildcardWithCredentials,
|
|
@@ -185,7 +173,15 @@ REGISTRY: dict[str, type[Rule]] = {
|
|
|
185
173
|
PreferFstringOverConcat.id: PreferFstringOverConcat,
|
|
186
174
|
PreferMatchPatternDestructuring.id: PreferMatchPatternDestructuring,
|
|
187
175
|
PreferOrPattern.id: PreferOrPattern,
|
|
176
|
+
PreferPatternMatching.id: PreferPatternMatching,
|
|
188
177
|
RequirePortForService.id: RequirePortForService,
|
|
178
|
+
PreferNonNullableCollection.id: PreferNonNullableCollection,
|
|
179
|
+
PreferMatchTypeDispatch.id: PreferMatchTypeDispatch,
|
|
180
|
+
PrimaryExportFileName.id: PrimaryExportFileName,
|
|
181
|
+
PreferWalrusRegexMatch.id: PreferWalrusRegexMatch,
|
|
182
|
+
PreferWalrusComprehensionFilter.id: PreferWalrusComprehensionFilter,
|
|
183
|
+
PreferWalrusStreamLoop.id: PreferWalrusStreamLoop,
|
|
184
|
+
PreferSelfTypeAnnotation.id: PreferSelfTypeAnnotation,
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
__all__ = ["REGISTRY"]
|
|
@@ -901,9 +901,9 @@ def _parametrized_nonempty(node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[
|
|
|
901
901
|
def _parametrize_names(dec: ast.Call) -> list[str]:
|
|
902
902
|
argnames = dec.args[0] if dec.args else None
|
|
903
903
|
if isinstance(argnames, ast.Constant) and isinstance(argnames.value, str):
|
|
904
|
-
return [
|
|
904
|
+
return [p for part in argnames.value.split(",") if (p := part.strip())]
|
|
905
905
|
if isinstance(argnames, (ast.List, ast.Tuple)):
|
|
906
|
-
return [
|
|
906
|
+
return [val for elt in argnames.elts if isinstance(elt, ast.Constant) and isinstance(val := elt.value, str)]
|
|
907
907
|
return []
|
|
908
908
|
|
|
909
909
|
|
|
@@ -294,6 +294,15 @@ class DuplicateTestBody(Rule):
|
|
|
294
294
|
groups = _duplicate_groups(tree, source)
|
|
295
295
|
except tokenize.TokenError, IndentationError, SyntaxError:
|
|
296
296
|
return []
|
|
297
|
+
except RecursionError:
|
|
298
|
+
# Normalisation deep-copies each body and `ast.dump`s it, both of
|
|
299
|
+
# which recurse once per AST level. A left-nested `+` spine of a few
|
|
300
|
+
# thousand terms — generated or minified source, never hand-written;
|
|
301
|
+
# the deepest real test-file AST measured across 43k files is 33 —
|
|
302
|
+
# exhausts the stack. Returning nothing skips this one file, where
|
|
303
|
+
# letting it propagate would abort the whole lint run: `check()` is
|
|
304
|
+
# called per file by a CLI that does not catch it.
|
|
305
|
+
return []
|
|
297
306
|
|
|
298
307
|
diags = [
|
|
299
308
|
Diagnostic(
|
|
@@ -301,9 +301,9 @@ def _calls_super_same_name(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool
|
|
|
301
301
|
|
|
302
302
|
"""
|
|
303
303
|
return any(
|
|
304
|
-
isinstance(call.func, ast.Attribute)
|
|
305
|
-
and
|
|
306
|
-
and isinstance(inner :=
|
|
304
|
+
isinstance(func := call.func, ast.Attribute)
|
|
305
|
+
and func.attr == node.name
|
|
306
|
+
and isinstance(inner := func.value, ast.Call)
|
|
307
307
|
and isinstance(inner.func, ast.Name)
|
|
308
308
|
and inner.func.id == "super"
|
|
309
309
|
for call in ast.walk(node)
|
|
@@ -362,9 +362,11 @@ def _swap_prone_annotation(args: ast.arguments) -> str | None:
|
|
|
362
362
|
if isinstance(ann := p.annotation, ast.Name) and ann.id in _PRIMITIVES:
|
|
363
363
|
groups.setdefault(ann.id, []).append(p.arg)
|
|
364
364
|
for name, arg_names in sorted(groups.items(), key=lambda kv: -len(kv[1])):
|
|
365
|
-
if
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
if (
|
|
366
|
+
len(arg_names) >= _MIN_SAME_TYPE
|
|
367
|
+
and not (_is_symmetric_numbering(arg_names) or _is_conventional_order(arg_names))
|
|
368
|
+
and _is_high_value_group(name, arg_names)
|
|
369
|
+
):
|
|
368
370
|
return name
|
|
369
371
|
return None
|
|
370
372
|
|
{sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
@@ -126,6 +126,7 @@ _PATCHERS = frozenset({"patch"})
|
|
|
126
126
|
# Any one of these gives the double a real contract to honour.
|
|
127
127
|
_SPEC_KEYWORDS = frozenset({"spec", "spec_set", "autospec", "new", "new_callable", "wraps"})
|
|
128
128
|
|
|
129
|
+
|
|
129
130
|
# How many positional arguments a callee must receive before the spec/new
|
|
130
131
|
# parameter is filled. `Mock(spec, wraps, ...)`, `patch(target, new, ...)` and
|
|
131
132
|
# `patch.object(target, attribute, new, ...)` all take it positionally, and that
|
{sarj_python_lint-0.24.0 → sarj_python_lint-0.26.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
@@ -178,8 +178,6 @@ _DIRECTIVE_PREFIXES = (
|
|
|
178
178
|
"flake8:",
|
|
179
179
|
"nosec",
|
|
180
180
|
"nosemgrep",
|
|
181
|
-
"todo",
|
|
182
|
-
"fixme",
|
|
183
181
|
"hack",
|
|
184
182
|
"xxx:",
|
|
185
183
|
"-*-",
|
|
@@ -249,7 +247,7 @@ _SENTENCE_END_RE = re.compile(r"""[.!?:;)\]}"'`]$""")
|
|
|
249
247
|
# holds") aren't mistaken for an enumeration marker.
|
|
250
248
|
_STEP_NARRATION_RE = re.compile(
|
|
251
249
|
r"^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?"
|
|
252
|
-
r"|finally|lastly|now)\s*[,:]\s*\S
|
|
250
|
+
r"|finally|lastly|now)\s*[,:]\s*\S",
|
|
253
251
|
re.IGNORECASE,
|
|
254
252
|
)
|
|
255
253
|
|
|
@@ -274,14 +272,49 @@ _META_COMMENTARY_RE = re.compile(
|
|
|
274
272
|
# demo-gateway, 12 of 12 sampled were true positives. Closed vocabulary on
|
|
275
273
|
# purpose: a one-word comment outside this list ("# Riyadh") is far more likely
|
|
276
274
|
# to be a genuine label for a value.
|
|
277
|
-
_SECTION_LABEL_WORDS = frozenset(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
_SECTION_LABEL_WORDS = frozenset(
|
|
276
|
+
{
|
|
277
|
+
"actions",
|
|
278
|
+
"components",
|
|
279
|
+
"config",
|
|
280
|
+
"configuration",
|
|
281
|
+
"constant",
|
|
282
|
+
"constants",
|
|
283
|
+
"enums",
|
|
284
|
+
"exports",
|
|
285
|
+
"fixtures",
|
|
286
|
+
"getters",
|
|
287
|
+
"globals",
|
|
288
|
+
"handler",
|
|
289
|
+
"handlers",
|
|
290
|
+
"helper",
|
|
291
|
+
"helpers",
|
|
292
|
+
"hook",
|
|
293
|
+
"hooks",
|
|
294
|
+
"imports",
|
|
295
|
+
"interfaces",
|
|
296
|
+
"main",
|
|
297
|
+
"mocks",
|
|
298
|
+
"models",
|
|
299
|
+
"mutations",
|
|
300
|
+
"props",
|
|
301
|
+
"queries",
|
|
302
|
+
"reducers",
|
|
303
|
+
"routes",
|
|
304
|
+
"schemas",
|
|
305
|
+
"selectors",
|
|
306
|
+
"setters",
|
|
307
|
+
"setup",
|
|
308
|
+
"state",
|
|
309
|
+
"styles",
|
|
310
|
+
"teardown",
|
|
311
|
+
"type",
|
|
312
|
+
"types",
|
|
313
|
+
"util",
|
|
314
|
+
"utilities",
|
|
315
|
+
"utils",
|
|
316
|
+
}
|
|
317
|
+
)
|
|
285
318
|
_SECTION_LABEL_RE = re.compile(r"^([A-Za-z]+)\s*:?\s*$")
|
|
286
319
|
|
|
287
320
|
# "Helper function to check if a path is active" — the opener announces the
|
|
@@ -315,7 +348,13 @@ _LETS_RE = re.compile(
|
|
|
315
348
|
# `# Phase 2: reconcile`. Flagged only when the file carries exactly one of
|
|
316
349
|
# them — a *run* of them is a documented algorithm walkthrough, which is the
|
|
317
350
|
# kind of comment this rule exists to protect.
|
|
318
|
-
_ENUMERATION_RE = re.compile(r"^(?:\d+[.)]\s+\S|phase\s+\d+\b)", re.IGNORECASE)
|
|
351
|
+
_ENUMERATION_RE = re.compile(r"^(?:\d+[.)]\s+\S|(?:phase|step)\s+\d+\b)", re.IGNORECASE)
|
|
352
|
+
|
|
353
|
+
# Dummy translational comments: ultra-short comments that just restate the code.
|
|
354
|
+
_DUMMY_TRANSLATION_RE = re.compile(
|
|
355
|
+
r"^(?:increment|return|returns|get|gets|set\b(?! up\b)|sets\b(?! up\b)|function to|method to)\b",
|
|
356
|
+
re.IGNORECASE,
|
|
357
|
+
)
|
|
319
358
|
|
|
320
359
|
|
|
321
360
|
def _is_word_char(ch: str) -> bool:
|
|
@@ -493,6 +532,14 @@ def _is_redundant_narration(
|
|
|
493
532
|
return True
|
|
494
533
|
if _HELPER_OPENER_RE.match(c) or _LETS_RE.match(c):
|
|
495
534
|
return True
|
|
535
|
+
words = c.split()
|
|
536
|
+
if len(words) <= 4 and _DUMMY_TRANSLATION_RE.match(c) and not any(ch in c for ch in "():="):
|
|
537
|
+
# Exclude common rationale words or test markers
|
|
538
|
+
lower_c = c.lower()
|
|
539
|
+
if not any(w in lower_c for w in ("when", "because", "if", "so that", "due to", "for", "instead of", "to prevent", "to avoid", "only")):
|
|
540
|
+
# Also exclude single-word labels like `# get` which are often group labels in tests
|
|
541
|
+
if len(words) > 1:
|
|
542
|
+
return True
|
|
496
543
|
if not nested and _is_section_label(c):
|
|
497
544
|
return True
|
|
498
545
|
return isolated_enumeration and bool(_ENUMERATION_RE.match(c))
|
|
@@ -559,6 +606,9 @@ def _looks_like_code(body: str) -> bool:
|
|
|
559
606
|
if c.startswith("@"):
|
|
560
607
|
return _compiles(c + "\ndef _f():\n pass")
|
|
561
608
|
if _ASSIGN_OR_CALL_RE.match(c):
|
|
609
|
+
# Implicit type condition documentation (e.g. under `else:`)
|
|
610
|
+
if c.startswith("isinstance(") or c.startswith("issubclass("):
|
|
611
|
+
return False
|
|
562
612
|
return _is_assign_or_call(c)
|
|
563
613
|
return False
|
|
564
614
|
|
|
@@ -660,6 +710,10 @@ class NoCommentCruft(Rule):
|
|
|
660
710
|
) -> str | None:
|
|
661
711
|
if _CODE_REGEN_CALL_RE.match(body):
|
|
662
712
|
return None
|
|
713
|
+
if re.match(r"^(?:todo|fixme)\b", body, re.IGNORECASE):
|
|
714
|
+
if not narration_protected:
|
|
715
|
+
return "Untracked TODO/FIXME marker — add an issue ticket or context link."
|
|
716
|
+
return None
|
|
663
717
|
if _is_banner(body):
|
|
664
718
|
if _is_heading_underline(body, prev_body):
|
|
665
719
|
return None
|
|
@@ -670,9 +724,7 @@ class NoCommentCruft(Rule):
|
|
|
670
724
|
return "Commented-out code — delete it; git history remembers."
|
|
671
725
|
if narration_protected:
|
|
672
726
|
return None
|
|
673
|
-
if _is_redundant_narration(
|
|
674
|
-
body, prev_body, isolated_enumeration=isolated_enumeration, nested=nested
|
|
675
|
-
):
|
|
727
|
+
if _is_redundant_narration(body, prev_body, isolated_enumeration=isolated_enumeration, nested=nested):
|
|
676
728
|
return "Comment narrates the code — delete it or say why, not what. Code is self-documenting."
|
|
677
729
|
return None
|
|
678
730
|
|
|
@@ -48,7 +48,7 @@ class NoCorsWildcardWithCredentials(Rule):
|
|
|
48
48
|
for node in ast.walk(tree):
|
|
49
49
|
if not isinstance(node, ast.Call):
|
|
50
50
|
continue
|
|
51
|
-
keywords = {
|
|
51
|
+
keywords = {arg: kw.value for kw in node.keywords if (arg := kw.arg) is not None}
|
|
52
52
|
credentials = keywords.get("allow_credentials")
|
|
53
53
|
origins = keywords.get("allow_origins")
|
|
54
54
|
if credentials is None or origins is None:
|
|
@@ -148,9 +148,9 @@ def _from_import_hits(node: ast.ImportFrom) -> list[tuple[int, int, str, str]]:
|
|
|
148
148
|
if private_segment is not None:
|
|
149
149
|
return [(node.lineno, node.col_offset + 1, node.module, private_segment)]
|
|
150
150
|
return [
|
|
151
|
-
(alias.lineno, alias.col_offset + 1, node.module,
|
|
151
|
+
(alias.lineno, alias.col_offset + 1, node.module, name)
|
|
152
152
|
for alias in node.names
|
|
153
|
-
if _is_private_name(alias.name)
|
|
153
|
+
if _is_private_name(name := alias.name)
|
|
154
154
|
]
|
|
155
155
|
|
|
156
156
|
|