sarj-python-lint 0.80.8__tar.gz → 0.80.11__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.80.8 → sarj_python_lint-0.80.11}/PKG-INFO +1 -1
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/pyproject.toml +1 -1
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_fixed_record.py +3 -8
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_imports.py +2 -6
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_prose_budget.py +7 -1
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +2 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +3 -12
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/mock_without_spec.py +3 -7
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +2 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +7 -14
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +26 -15
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_redundant_literal_description.py +1 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_repeated_string_literal.py +12 -4
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +4 -4
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/over_mocked_test.py +1 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/phase_label_comment.py +29 -16
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_class_row.py +1 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +8 -7
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +1 -3
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +21 -5
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_library_fake.py +7 -12
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_match_assert_never.py +7 -17
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +5 -15
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +4 -7
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +10 -10
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +5 -9
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +12 -15
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +10 -13
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_str_enum.py +18 -21
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +2 -4
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +3 -1
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +5 -1
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/production_derived_test_cases.py +19 -8
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +8 -22
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/redundant_class_docstring.py +1 -6
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/redundant_docstring.py +2 -10
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +3 -2
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/restated_test_docstring.py +6 -3
- sarj_python_lint-0.80.11/src/sarj_python_lint/rules/trailing_value_narration.py +295 -0
- sarj_python_lint-0.80.8/src/sarj_python_lint/rules/trailing_value_narration.py +0 -197
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/.gitignore +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/LICENSE +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/README.md +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.80.
|
|
3
|
+
Version: 0.80.11
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
{sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
@@ -48,9 +48,7 @@ def builds_fixed_record(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
|
48
48
|
else:
|
|
49
49
|
aliases[target.id] = current.value.id
|
|
50
50
|
else:
|
|
51
|
-
invalidated_names.update(
|
|
52
|
-
target.id for target in current.targets if isinstance(target, ast.Name)
|
|
53
|
-
)
|
|
51
|
+
invalidated_names.update(target.id for target in current.targets if isinstance(target, ast.Name))
|
|
54
52
|
for target in current.targets:
|
|
55
53
|
if isinstance(target, ast.Subscript):
|
|
56
54
|
invalidated_names.update(_mutated_record_roots(target))
|
|
@@ -115,8 +113,7 @@ def builds_fixed_record(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
|
115
113
|
):
|
|
116
114
|
return False
|
|
117
115
|
return any(
|
|
118
|
-
_is_record_literal(value)
|
|
119
|
-
or (isinstance(value, ast.Name) and original_name(value.id) in intact_record_names)
|
|
116
|
+
_is_record_literal(value) or (isinstance(value, ast.Name) and original_name(value.id) in intact_record_names)
|
|
120
117
|
for value in returned
|
|
121
118
|
)
|
|
122
119
|
|
|
@@ -140,9 +137,7 @@ def _is_record_literal(node: ast.expr) -> bool:
|
|
|
140
137
|
return _is_record_literal(node.elt)
|
|
141
138
|
if not isinstance(node, ast.Dict):
|
|
142
139
|
return False
|
|
143
|
-
return bool(node.keys) and all(
|
|
144
|
-
isinstance(key, ast.Constant) and isinstance(key.value, str) for key in node.keys
|
|
145
|
-
)
|
|
140
|
+
return bool(node.keys) and all(isinstance(key, ast.Constant) and isinstance(key.value, str) for key in node.keys)
|
|
146
141
|
|
|
147
142
|
|
|
148
143
|
def _mutated_record_roots(target: ast.AST) -> set[str]:
|
|
@@ -39,9 +39,7 @@ class ImportIndex:
|
|
|
39
39
|
pass
|
|
40
40
|
|
|
41
41
|
imported_names = frozenset(candidates)
|
|
42
|
-
non_import_bindings = (
|
|
43
|
-
_module_non_import_bindings(tree) if module_scope_only else _non_import_bindings(tree)
|
|
44
|
-
)
|
|
42
|
+
non_import_bindings = _module_non_import_bindings(tree) if module_scope_only else _non_import_bindings(tree)
|
|
45
43
|
rebound = non_import_bindings & imported_names
|
|
46
44
|
bindings = {
|
|
47
45
|
local: next(iter(targets))
|
|
@@ -166,9 +164,7 @@ def _module_import_statements(tree: ast.Module) -> list[ast.stmt]:
|
|
|
166
164
|
for statement in tree.body:
|
|
167
165
|
if not isinstance(statement, ast.If) or not _is_type_checking_guard(statement.test):
|
|
168
166
|
continue
|
|
169
|
-
statements.extend(
|
|
170
|
-
child for child in statement.body if isinstance(child, (ast.Import, ast.ImportFrom))
|
|
171
|
-
)
|
|
167
|
+
statements.extend(child for child in statement.body if isinstance(child, (ast.Import, ast.ImportFrom)))
|
|
172
168
|
return statements
|
|
173
169
|
|
|
174
170
|
|
{sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
@@ -428,7 +428,13 @@ def _yielded_type(node: ast.expr) -> str | None:
|
|
|
428
428
|
node = parsed
|
|
429
429
|
if not isinstance(node, ast.Subscript):
|
|
430
430
|
return None
|
|
431
|
-
name =
|
|
431
|
+
name = (
|
|
432
|
+
node.value.attr
|
|
433
|
+
if isinstance(node.value, ast.Attribute)
|
|
434
|
+
else node.value.id
|
|
435
|
+
if isinstance(node.value, ast.Name)
|
|
436
|
+
else ""
|
|
437
|
+
)
|
|
432
438
|
if name not in {"AsyncGenerator", "AsyncIterable", "AsyncIterator", "Generator", "Iterable", "Iterator"}:
|
|
433
439
|
return None
|
|
434
440
|
members = node.slice.elts if isinstance(node.slice, ast.Tuple) else (node.slice,)
|
|
@@ -430,9 +430,8 @@ def _check_return(
|
|
|
430
430
|
continue
|
|
431
431
|
response_model = route.keywords.get("response_model")
|
|
432
432
|
status = _status_code(route.keywords.get("status_code"), index) or HTTPStatus.OK
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
or any(_documents_response_model(node, status, index) for node in _route_response_nodes(route))
|
|
433
|
+
if _concrete_response_model(response_model, index) or any(
|
|
434
|
+
_documents_response_model(node, status, index) for node in _route_response_nodes(route)
|
|
436
435
|
):
|
|
437
436
|
continue
|
|
438
437
|
return [
|
|
@@ -181,9 +181,7 @@ class InvalidPydanticFieldDefault(Rule):
|
|
|
181
181
|
files=(
|
|
182
182
|
ExampleFile.python(
|
|
183
183
|
"app/models.py",
|
|
184
|
-
"from pydantic import BaseModel\n\n"
|
|
185
|
-
"class User(BaseModel):\n"
|
|
186
|
-
" display_name: str = None\n",
|
|
184
|
+
"from pydantic import BaseModel\n\nclass User(BaseModel):\n display_name: str = None\n",
|
|
187
185
|
),
|
|
188
186
|
),
|
|
189
187
|
focus_path=PurePosixPath("app/models.py"),
|
|
@@ -231,11 +229,7 @@ class InvalidPydanticFieldDefault(Rule):
|
|
|
231
229
|
if statement.target.id.startswith("_"):
|
|
232
230
|
continue
|
|
233
231
|
contract = _annotation_contract(path, tree, statement.annotation)
|
|
234
|
-
if
|
|
235
|
-
contract.transforms_default
|
|
236
|
-
or transformers.all_fields
|
|
237
|
-
or statement.target.id in transformers.fields
|
|
238
|
-
):
|
|
232
|
+
if contract.transforms_default or transformers.all_fields or statement.target.id in transformers.fields:
|
|
239
233
|
continue
|
|
240
234
|
assignment_field = (
|
|
241
235
|
statement.value
|
|
@@ -276,10 +270,7 @@ def _is_direct_base_model(node: ast.ClassDef, imports: ImportIndex) -> bool:
|
|
|
276
270
|
return False
|
|
277
271
|
return all(
|
|
278
272
|
imports.resolves(base, sources=_PYDANTIC_BASE_MODEL_SOURCES, symbol="BaseModel")
|
|
279
|
-
or (
|
|
280
|
-
isinstance(base, ast.Subscript)
|
|
281
|
-
and imports.resolves(base.value, sources=_TYPING_SOURCES, symbol="Generic")
|
|
282
|
-
)
|
|
273
|
+
or (isinstance(base, ast.Subscript) and imports.resolves(base.value, sources=_TYPING_SOURCES, symbol="Generic"))
|
|
283
274
|
for base in node.bases
|
|
284
275
|
)
|
|
285
276
|
|
{sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
@@ -333,9 +333,7 @@ class _MockNames:
|
|
|
333
333
|
|
|
334
334
|
def _is_shadowed(self, name: str, node: ast.AST) -> bool:
|
|
335
335
|
scope = self.scopes.get(id(node))
|
|
336
|
-
return name in self.shadowed or (
|
|
337
|
-
scope is not None and name in self.local_shadowed.get(scope, frozenset())
|
|
338
|
-
)
|
|
336
|
+
return name in self.shadowed or (scope is not None and name in self.local_shadowed.get(scope, frozenset()))
|
|
339
337
|
|
|
340
338
|
|
|
341
339
|
class _FileFacts:
|
|
@@ -559,14 +557,12 @@ def _escaped_names(argument: ast.expr) -> set[str]:
|
|
|
559
557
|
|
|
560
558
|
def _mock_add_spec_has_contract(node: ast.Call) -> bool:
|
|
561
559
|
if any(
|
|
562
|
-
not isinstance(argument, ast.Starred)
|
|
563
|
-
and not (isinstance(argument, ast.Constant) and argument.value is None)
|
|
560
|
+
not isinstance(argument, ast.Starred) and not (isinstance(argument, ast.Constant) and argument.value is None)
|
|
564
561
|
for argument in node.args
|
|
565
562
|
):
|
|
566
563
|
return True
|
|
567
564
|
return any(
|
|
568
|
-
keyword.arg == "spec"
|
|
569
|
-
and not (isinstance(keyword.value, ast.Constant) and keyword.value.value is None)
|
|
565
|
+
keyword.arg == "spec" and not (isinstance(keyword.value, ast.Constant) and keyword.value.value is None)
|
|
570
566
|
for keyword in node.keywords
|
|
571
567
|
)
|
|
572
568
|
|
|
@@ -112,7 +112,7 @@ class NoAnalyticalAggregationInPostgresStore(Rule):
|
|
|
112
112
|
ExampleFile.python(
|
|
113
113
|
"app/event_store.py",
|
|
114
114
|
"import psycopg\n\n"
|
|
115
|
-
|
|
115
|
+
"QUERY = \"SELECT DATE_TRUNC('day', occurred_at), COUNT(*), AVG(latency_ms) "
|
|
116
116
|
'FROM event GROUP BY 1"\n',
|
|
117
117
|
),
|
|
118
118
|
),
|
|
@@ -127,8 +127,7 @@ class NoAnalyticalAggregationInPostgresStore(Rule):
|
|
|
127
127
|
files=(
|
|
128
128
|
ExampleFile.python(
|
|
129
129
|
"app/account_store.py",
|
|
130
|
-
|
|
131
|
-
'QUERY = "SELECT SUM(amount) FROM ledger WHERE account_id = %s FOR UPDATE"\n',
|
|
130
|
+
'import psycopg\n\nQUERY = "SELECT SUM(amount) FROM ledger WHERE account_id = %s FOR UPDATE"\n',
|
|
132
131
|
),
|
|
133
132
|
),
|
|
134
133
|
focus_path=PurePosixPath("app/account_store.py"),
|
|
@@ -23,9 +23,7 @@ if TYPE_CHECKING:
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
_PYDANTIC_BASE_MODEL_SOURCES = frozenset(
|
|
27
|
-
{"pydantic", "pydantic.main", "pydantic.v1", "pydantic.v1.main"}
|
|
28
|
-
)
|
|
26
|
+
_PYDANTIC_BASE_MODEL_SOURCES = frozenset({"pydantic", "pydantic.main", "pydantic.v1", "pydantic.v1.main"})
|
|
29
27
|
_PYDANTIC_SETTINGS_SOURCES = frozenset({"pydantic", "pydantic.v1", "pydantic_settings"})
|
|
30
28
|
_FIELD_VALIDATOR_SOURCES = frozenset({"pydantic", "pydantic.functional_validators"})
|
|
31
29
|
_LEGACY_VALIDATOR_SOURCES = frozenset(
|
|
@@ -199,9 +197,7 @@ def _is_class_var(annotation: ast.expr, imports: ImportIndex) -> bool:
|
|
|
199
197
|
|
|
200
198
|
def _literal_check_fields_false(decorator: ast.Call) -> bool:
|
|
201
199
|
return any(
|
|
202
|
-
keyword.arg == "check_fields"
|
|
203
|
-
and isinstance(keyword.value, ast.Constant)
|
|
204
|
-
and keyword.value.value is False
|
|
200
|
+
keyword.arg == "check_fields" and isinstance(keyword.value, ast.Constant) and keyword.value.value is False
|
|
205
201
|
for keyword in decorator.keywords
|
|
206
202
|
)
|
|
207
203
|
|
|
@@ -235,9 +231,7 @@ def _shadowed_in_enclosing_function(
|
|
|
235
231
|
return False
|
|
236
232
|
current = parents.get(owner)
|
|
237
233
|
while current is not None:
|
|
238
|
-
if isinstance(current, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)) and _function_binds(
|
|
239
|
-
current, root
|
|
240
|
-
):
|
|
234
|
+
if isinstance(current, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)) and _function_binds(current, root):
|
|
241
235
|
return True
|
|
242
236
|
current = parents.get(current)
|
|
243
237
|
return False
|
|
@@ -251,11 +245,10 @@ def _root_name(node: ast.expr) -> str | None:
|
|
|
251
245
|
|
|
252
246
|
def _function_binds(node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda, name: str) -> bool:
|
|
253
247
|
arguments = node.args
|
|
254
|
-
if
|
|
255
|
-
argument.arg == name
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
arguments.kwarg is not None and arguments.kwarg.arg == name
|
|
248
|
+
if (
|
|
249
|
+
any(argument.arg == name for argument in (*arguments.posonlyargs, *arguments.args, *arguments.kwonlyargs))
|
|
250
|
+
or (arguments.vararg is not None and arguments.vararg.arg == name)
|
|
251
|
+
or (arguments.kwarg is not None and arguments.kwarg.arg == name)
|
|
259
252
|
):
|
|
260
253
|
return True
|
|
261
254
|
body = node.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) else [node.body]
|
|
@@ -272,9 +272,7 @@ def _cursors(
|
|
|
272
272
|
)
|
|
273
273
|
|
|
274
274
|
|
|
275
|
-
def _initial_bindings(
|
|
276
|
-
function: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex
|
|
277
|
-
) -> dict[str, _Shape]:
|
|
275
|
+
def _initial_bindings(function: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> dict[str, _Shape]:
|
|
278
276
|
result: dict[str, _Shape] = {}
|
|
279
277
|
for name, annotation in _parameter_annotations(function).items():
|
|
280
278
|
shape = _connection_annotation_shape(annotation, imports)
|
|
@@ -480,7 +478,11 @@ def _connection_context_shape(
|
|
|
480
478
|
or _resolves_connection_connect(call.func, imports)
|
|
481
479
|
):
|
|
482
480
|
return _connect_call_shape(call, imports, shadowed)
|
|
483
|
-
if
|
|
481
|
+
if (
|
|
482
|
+
isinstance(call.func, ast.Attribute)
|
|
483
|
+
and call.func.attr == "connection"
|
|
484
|
+
and isinstance(call.func.value, ast.Name)
|
|
485
|
+
):
|
|
484
486
|
return visible.get(call.func.value.id)
|
|
485
487
|
return None
|
|
486
488
|
|
|
@@ -509,7 +511,12 @@ def _receiver_shape(
|
|
|
509
511
|
) -> _Shape:
|
|
510
512
|
if isinstance(receiver, ast.Name):
|
|
511
513
|
return visible.get(receiver.id, _Shape.UNKNOWN)
|
|
512
|
-
if
|
|
514
|
+
if (
|
|
515
|
+
owner is not None
|
|
516
|
+
and isinstance(receiver, ast.Attribute)
|
|
517
|
+
and isinstance(receiver.value, ast.Name)
|
|
518
|
+
and receiver.value.id == "self"
|
|
519
|
+
):
|
|
513
520
|
if receiver.attr in invalidated_attrs:
|
|
514
521
|
return _Shape.UNKNOWN
|
|
515
522
|
return class_attrs.get((owner.lineno, receiver.attr), _Shape.UNKNOWN)
|
|
@@ -587,7 +594,9 @@ def _cursor_shape(cursor: _Cursor, fetch: ast.Call, imports: ImportIndex, shadow
|
|
|
587
594
|
if _position(statement) >= _position(fetch):
|
|
588
595
|
break
|
|
589
596
|
match statement:
|
|
590
|
-
case ast.Assign(targets=[ast.Attribute(value=ast.Name(id=name), attr="row_factory")], value=value) if
|
|
597
|
+
case ast.Assign(targets=[ast.Attribute(value=ast.Name(id=name), attr="row_factory")], value=value) if (
|
|
598
|
+
name == cursor.name
|
|
599
|
+
):
|
|
591
600
|
assignments.append(value)
|
|
592
601
|
case _ if _assigns_cursor_factory(statement, cursor.name):
|
|
593
602
|
ambiguous = True
|
|
@@ -755,11 +764,7 @@ def _merge_origins(states: Iterable[dict[str, _OriginSet]]) -> dict[str, _Origin
|
|
|
755
764
|
materialized = tuple(states)
|
|
756
765
|
names = {name for state in materialized for name in state}
|
|
757
766
|
return {
|
|
758
|
-
name: frozenset(
|
|
759
|
-
origin
|
|
760
|
-
for state in materialized
|
|
761
|
-
for origin in state.get(name, frozenset({None}))
|
|
762
|
-
)
|
|
767
|
+
name: frozenset(origin for state in materialized for origin in state.get(name, frozenset({None})))
|
|
763
768
|
for name in names
|
|
764
769
|
}
|
|
765
770
|
|
|
@@ -789,7 +794,9 @@ def _statement_mutation_roots(statement: ast.stmt) -> frozenset[str]:
|
|
|
789
794
|
root: str | None = None
|
|
790
795
|
if isinstance(node, ast.Attribute | ast.Subscript) and isinstance(node.ctx, ast.Store | ast.Del):
|
|
791
796
|
root = _root_name(node)
|
|
792
|
-
elif
|
|
797
|
+
elif (
|
|
798
|
+
isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr in _MUTATING_METHODS
|
|
799
|
+
):
|
|
793
800
|
root = _root_name(node.func.value)
|
|
794
801
|
if root is not None:
|
|
795
802
|
roots.add(root)
|
|
@@ -894,9 +901,13 @@ def _scope_nodes(statements: Iterable[ast.stmt], *, stop_rebinding: str | None =
|
|
|
894
901
|
yield node
|
|
895
902
|
if isinstance(node, _SCOPE_NODES):
|
|
896
903
|
continue
|
|
897
|
-
if
|
|
898
|
-
|
|
899
|
-
|
|
904
|
+
if (
|
|
905
|
+
stop_rebinding is not None
|
|
906
|
+
and isinstance(node, ast.With | ast.AsyncWith)
|
|
907
|
+
and any(
|
|
908
|
+
isinstance(item.optional_vars, ast.Name) and item.optional_vars.id == stop_rebinding
|
|
909
|
+
for item in node.items
|
|
910
|
+
)
|
|
900
911
|
):
|
|
901
912
|
continue
|
|
902
913
|
pending.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
@@ -149,9 +149,7 @@ class NoRedundantLiteralDescription(Rule):
|
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def _is_direct_model(node: ast.ClassDef, imports: ImportIndex) -> bool:
|
|
152
|
-
return any(
|
|
153
|
-
imports.resolves(base, sources=_PYDANTIC_BASE_MODEL_SOURCES, symbol="BaseModel") for base in node.bases
|
|
154
|
-
)
|
|
152
|
+
return any(imports.resolves(base, sources=_PYDANTIC_BASE_MODEL_SOURCES, symbol="BaseModel") for base in node.bases)
|
|
155
153
|
|
|
156
154
|
|
|
157
155
|
def _local_closed_domains(tree: ast.Module, imports: ImportIndex) -> dict[str, frozenset[str]]:
|
|
@@ -163,9 +163,7 @@ class NoRepeatedStringLiteral(Rule):
|
|
|
163
163
|
function_scopes = {scope for n in nodes if (scope := scope_of.get(id(n), _MODULE_SCOPE)) != _MODULE_SCOPE}
|
|
164
164
|
canonical = canonical_constants.get(value, ())
|
|
165
165
|
eligible_canonical = tuple(
|
|
166
|
-
name
|
|
167
|
-
for name, line in canonical
|
|
168
|
-
if all(line < scope_line_of.get(scope, 0) for scope in function_scopes)
|
|
166
|
+
name for name, line in canonical if all(line < scope_line_of.get(scope, 0) for scope in function_scopes)
|
|
169
167
|
)
|
|
170
168
|
if len(eligible_canonical) == 1 and function_scopes:
|
|
171
169
|
(constant_name,) = eligible_canonical
|
|
@@ -282,6 +280,16 @@ def _preview(value: str) -> str:
|
|
|
282
280
|
|
|
283
281
|
def _is_skipped_path(path: Path) -> bool:
|
|
284
282
|
excluded_parts = frozenset(
|
|
285
|
-
{
|
|
283
|
+
{
|
|
284
|
+
"benchmark",
|
|
285
|
+
"benchmarks",
|
|
286
|
+
"example",
|
|
287
|
+
"examples",
|
|
288
|
+
"fixture",
|
|
289
|
+
"fixtures",
|
|
290
|
+
"migration",
|
|
291
|
+
"migrations",
|
|
292
|
+
"snapshots",
|
|
293
|
+
}
|
|
286
294
|
)
|
|
287
295
|
return is_test_path(path) or bool(excluded_parts.intersection(part.lower() for part in path.parts))
|
|
@@ -164,9 +164,7 @@ def _module_import_index(tree: ast.Module) -> ImportIndex:
|
|
|
164
164
|
if regular_names:
|
|
165
165
|
body.append(ast.ImportFrom(module=statement.module, names=regular_names, level=statement.level))
|
|
166
166
|
body.extend(
|
|
167
|
-
ast.Import(
|
|
168
|
-
names=[ast.alias(name=f"{statement.module}.errors", asname=alias.asname or alias.name)]
|
|
169
|
-
)
|
|
167
|
+
ast.Import(names=[ast.alias(name=f"{statement.module}.errors", asname=alias.asname or alias.name)])
|
|
170
168
|
for alias in statement.names
|
|
171
169
|
if alias.name == "errors"
|
|
172
170
|
)
|
|
@@ -311,7 +309,9 @@ def _classification_matches(statements: list[ast.stmt], context: _HandlerContext
|
|
|
311
309
|
|
|
312
310
|
|
|
313
311
|
def _branch_translates(statements: list[ast.stmt]) -> bool:
|
|
314
|
-
return any(
|
|
312
|
+
return any(
|
|
313
|
+
isinstance(node, (ast.Raise, ast.Return)) for statement in statements for node in _walk_same_scope(statement)
|
|
314
|
+
)
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def _message_matches(node: ast.expr, context: _HandlerContext) -> list[ast.expr]:
|
{sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
@@ -762,9 +762,7 @@ def _parametrized_arguments(
|
|
|
762
762
|
continue
|
|
763
763
|
argnames = _call_argument(decorator, 0, "argnames")
|
|
764
764
|
if isinstance(argnames, ast.Constant) and isinstance(argnames.value, str):
|
|
765
|
-
parametrized.update(
|
|
766
|
-
stripped_name for name in argnames.value.split(",") if (stripped_name := name.strip())
|
|
767
|
-
)
|
|
765
|
+
parametrized.update(stripped_name for name in argnames.value.split(",") if (stripped_name := name.strip()))
|
|
768
766
|
elif isinstance(argnames, (ast.List, ast.Tuple)):
|
|
769
767
|
parametrized.update(
|
|
770
768
|
element.value
|
|
@@ -15,6 +15,8 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
RuleCategory,
|
|
16
16
|
RuleDocumentation,
|
|
17
17
|
RuleExample,
|
|
18
|
+
Severity,
|
|
19
|
+
parse_or_none,
|
|
18
20
|
)
|
|
19
21
|
from sarj_python_lint.rules._comments import nested_comment_lines, standalone_comments
|
|
20
22
|
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
@@ -26,44 +28,49 @@ if TYPE_CHECKING:
|
|
|
26
28
|
|
|
27
29
|
# A bounded whole-body grammar: decoration and joins are ceremony only when
|
|
28
30
|
# every substantive token is itself a phase label.
|
|
29
|
-
_PHASE_WORD = (
|
|
30
|
-
r"arrange|act|assert(?:ion)?s?|given|when|then|exercise|execute|"
|
|
31
|
-
r"verif(?:y|ication)|cleanup|prepare|sanity(?:\s+check)?"
|
|
32
|
-
)
|
|
31
|
+
_PHASE_WORD = r"(?:arrange|act|assert(?:ion)?s?|given|when|then)(?:\s+phase)?"
|
|
33
32
|
_PHASE_RE = re.compile(
|
|
34
|
-
rf"^[-=~*_#.\s]{{0,40}}(?:{_PHASE_WORD})"
|
|
35
|
-
rf"(?:\s*(?:[/&+,|]
|
|
33
|
+
rf"^(?:\d+[.)]\s*)?[-=~*_#.\s]{{0,40}}(?:{_PHASE_WORD})"
|
|
34
|
+
rf"(?:\s*(?:[/&+,|]|-|->|→|and)\s*(?:{_PHASE_WORD}))*"
|
|
36
35
|
rf"[-=~*_#.\s:;!–—]{{0,40}}$",
|
|
37
36
|
re.IGNORECASE,
|
|
38
37
|
)
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
class TestPhaseLabelComment(Rule):
|
|
42
|
-
id: str = "test-phase-
|
|
41
|
+
id: str = "no-bare-test-phase-comments"
|
|
43
42
|
code: str = "SARJ089"
|
|
44
43
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
45
|
-
summary="
|
|
44
|
+
summary="Test files must not use bare phase comments such as Arrange, Act, Assert, Given, When, or Then.",
|
|
46
45
|
rationale="Phase labels narrate test structure without explaining behavior and often indicate that a test needs clearer names or smaller units.",
|
|
47
46
|
remediation="Delete the label; if the phases remain hard to follow, extract a named helper or split the test.",
|
|
48
47
|
category=RuleCategory.TESTING,
|
|
49
48
|
autofix=AutofixPolicy.NONE,
|
|
49
|
+
aliases=("test-phase-label-comment",),
|
|
50
50
|
limitations=(
|
|
51
|
-
"
|
|
51
|
+
"Standalone comments across recognized test and test-support files are checked, including fixtures, helpers, and module sections.",
|
|
52
|
+
"Trailing comments and comments inside bracketed expressions are excluded.",
|
|
52
53
|
"Comments containing words beyond the bounded phase-label grammar are preserved.",
|
|
53
54
|
),
|
|
54
55
|
examples=(
|
|
55
56
|
RuleExample(
|
|
56
|
-
example_id="bare-phase-
|
|
57
|
-
title="Bare
|
|
57
|
+
example_id="bare-test-phase-comments",
|
|
58
|
+
title="Bare Arrange, Act, and Assert labels",
|
|
58
59
|
outcome=ExampleOutcome.MATCH,
|
|
59
60
|
files=(
|
|
60
61
|
ExampleFile.python(
|
|
61
62
|
"tests/test_widget.py",
|
|
62
|
-
"def test_widget():\n
|
|
63
|
+
"def test_widget():\n"
|
|
64
|
+
" # Arrange\n"
|
|
65
|
+
" widget = make_widget()\n"
|
|
66
|
+
" # Act\n"
|
|
67
|
+
" result = widget.run()\n"
|
|
68
|
+
" # Assert\n"
|
|
69
|
+
" assert result.ok\n",
|
|
63
70
|
),
|
|
64
71
|
),
|
|
65
72
|
focus_path=PurePosixPath("tests/test_widget.py"),
|
|
66
|
-
expected_count=
|
|
73
|
+
expected_count=3,
|
|
67
74
|
public=True,
|
|
68
75
|
),
|
|
69
76
|
RuleExample(
|
|
@@ -73,7 +80,9 @@ class TestPhaseLabelComment(Rule):
|
|
|
73
80
|
files=(
|
|
74
81
|
ExampleFile.python(
|
|
75
82
|
"tests/test_widget.py",
|
|
76
|
-
"def test_widget():\n
|
|
83
|
+
"def test_widget():\n"
|
|
84
|
+
" # A stale token here would authorize the wrong tenant.\n"
|
|
85
|
+
" assert request_is_rejected()\n",
|
|
77
86
|
),
|
|
78
87
|
),
|
|
79
88
|
focus_path=PurePosixPath("tests/test_widget.py"),
|
|
@@ -86,7 +95,7 @@ class TestPhaseLabelComment(Rule):
|
|
|
86
95
|
|
|
87
96
|
@override
|
|
88
97
|
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
89
|
-
if is_generated(path, source) or not is_test_path(path):
|
|
98
|
+
if is_generated(path, source) or not is_test_path(path) or parse_or_none(path, source) is None:
|
|
90
99
|
return []
|
|
91
100
|
try:
|
|
92
101
|
standalone, _ = standalone_comments(source)
|
|
@@ -99,7 +108,11 @@ class TestPhaseLabelComment(Rule):
|
|
|
99
108
|
line=line,
|
|
100
109
|
col=col + 1,
|
|
101
110
|
code=self.code,
|
|
102
|
-
message=
|
|
111
|
+
message=(
|
|
112
|
+
f"Bare test phase comment {body.strip()!r} narrates structure; delete it or replace it "
|
|
113
|
+
"with non-obvious rationale."
|
|
114
|
+
),
|
|
115
|
+
severity=Severity.WARNING,
|
|
103
116
|
column_encoding=ColumnEncoding.CODEPOINTS,
|
|
104
117
|
)
|
|
105
118
|
for line, col, body in standalone
|
{sarj_python_lint-0.80.8 → sarj_python_lint-0.80.11}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
@@ -401,9 +401,7 @@ def _model_name(node: ast.expr) -> str | None:
|
|
|
401
401
|
|
|
402
402
|
def _is_rebound_between(scoped_nodes: list[ast.AST], name: str, start: int, end: int) -> bool:
|
|
403
403
|
return any(
|
|
404
|
-
start < node.lineno < end and _writes_name(node, name)
|
|
405
|
-
for node in scoped_nodes
|
|
406
|
-
if isinstance(node, ast.stmt)
|
|
404
|
+
start < node.lineno < end and _writes_name(node, name) for node in scoped_nodes if isinstance(node, ast.stmt)
|
|
407
405
|
)
|
|
408
406
|
|
|
409
407
|
|
|
@@ -50,7 +50,9 @@ _CATEGORY_WORDS = frozenset({"kind", "kinds", "type", "types"})
|
|
|
50
50
|
_DESCRIPTOR_WORDS = frozenset(
|
|
51
51
|
{"id", "ids", "kind", "kinds", "name", "names", "permission", "permissions", "scope", "scopes", "type", "types"}
|
|
52
52
|
)
|
|
53
|
-
_EXTERNAL_PREFIXES = frozenset(
|
|
53
|
+
_EXTERNAL_PREFIXES = frozenset(
|
|
54
|
+
{"given", "incoming", "presented", "provided", "received", "request", "submitted", "supplied"}
|
|
55
|
+
)
|
|
54
56
|
_EXPECTED_PREFIXES = frozenset({"configured", "current", "expected", "known", "stored"})
|
|
55
57
|
_AUTH_MAPPING_WORDS = frozenset({"authorization", "cookies", "headers", "path_params", "query_params"})
|
|
56
58
|
_STORED_OWNER_WORDS = frozenset({"config", "configuration", "self", "session", "settings"})
|
|
@@ -340,8 +342,10 @@ def _is_auth_mapping(node: ast.expr) -> bool:
|
|
|
340
342
|
|
|
341
343
|
|
|
342
344
|
def _is_auth_key(node: ast.expr) -> bool:
|
|
343
|
-
return
|
|
344
|
-
node
|
|
345
|
+
return (
|
|
346
|
+
isinstance(node, ast.Constant)
|
|
347
|
+
and isinstance(node.value, str)
|
|
348
|
+
and (node.value.lower() == "token" or _is_authenticator_name(node.value))
|
|
345
349
|
)
|
|
346
350
|
|
|
347
351
|
|
|
@@ -409,10 +413,7 @@ def _environment_names(module: ast.Module) -> frozenset[str]:
|
|
|
409
413
|
|
|
410
414
|
|
|
411
415
|
def _bound_names(scope: ast.FunctionDef | ast.AsyncFunctionDef) -> frozenset[str]:
|
|
412
|
-
names = {
|
|
413
|
-
argument.arg
|
|
414
|
-
for argument in (*scope.args.posonlyargs, *scope.args.args, *scope.args.kwonlyargs)
|
|
415
|
-
}
|
|
416
|
+
names = {argument.arg for argument in (*scope.args.posonlyargs, *scope.args.args, *scope.args.kwonlyargs)}
|
|
416
417
|
for assignment in _assignments(scope.body):
|
|
417
418
|
target, _ = _assignment_parts(assignment)
|
|
418
419
|
if target is not None:
|
|
@@ -559,9 +559,7 @@ def _shadowed_producers(statements: list[ast.stmt], before_line: int | None) ->
|
|
|
559
559
|
bound = _bound_names(statement)
|
|
560
560
|
if isinstance(statement, ast.Import):
|
|
561
561
|
bound.difference_update(
|
|
562
|
-
alias.asname or alias.name
|
|
563
|
-
for alias in statement.names
|
|
564
|
-
if alias.name in {"json", "re"}
|
|
562
|
+
alias.asname or alias.name for alias in statement.names if alias.name in {"json", "re"}
|
|
565
563
|
)
|
|
566
564
|
shadowed.update(bound & _PRODUCER_NAMES)
|
|
567
565
|
return frozenset(shadowed)
|
|
@@ -49,7 +49,23 @@ _MUTATING_METHODS = frozenset(
|
|
|
49
49
|
)
|
|
50
50
|
_CONCRETE_COLLECTION_METHODS = _MUTATING_METHODS | {"copy"}
|
|
51
51
|
_NON_ESCAPING_CALLS = frozenset(
|
|
52
|
-
{
|
|
52
|
+
{
|
|
53
|
+
"all",
|
|
54
|
+
"any",
|
|
55
|
+
"dict",
|
|
56
|
+
"enumerate",
|
|
57
|
+
"frozenset",
|
|
58
|
+
"iter",
|
|
59
|
+
"len",
|
|
60
|
+
"list",
|
|
61
|
+
"max",
|
|
62
|
+
"min",
|
|
63
|
+
"reversed",
|
|
64
|
+
"set",
|
|
65
|
+
"sorted",
|
|
66
|
+
"sum",
|
|
67
|
+
"tuple",
|
|
68
|
+
}
|
|
53
69
|
)
|
|
54
70
|
|
|
55
71
|
|
|
@@ -57,9 +73,7 @@ class PreferImmutableModuleConstant(Rule):
|
|
|
57
73
|
id: str = "prefer-immutable-module-constant"
|
|
58
74
|
code: str = "SARJ096"
|
|
59
75
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
60
|
-
summary=(
|
|
61
|
-
"Nonempty uppercase module collections allow top-level membership or keys to change at runtime."
|
|
62
|
-
),
|
|
76
|
+
summary=("Nonempty uppercase module collections allow top-level membership or keys to change at runtime."),
|
|
63
77
|
rationale=(
|
|
64
78
|
"A constant-looking collection can expose process-wide top-level mutation even when callers intend it as a "
|
|
65
79
|
"read-only lookup table."
|
|
@@ -259,7 +273,9 @@ class _MutationVisitor(ast.NodeVisitor):
|
|
|
259
273
|
self.visit(node.value)
|
|
260
274
|
|
|
261
275
|
def visit_AugAssign(self, node: ast.AugAssign) -> None:
|
|
262
|
-
self._record_name(
|
|
276
|
+
self._record_name(
|
|
277
|
+
_mutated_target(node.target) or (node.target.id if isinstance(node.target, ast.Name) else None)
|
|
278
|
+
)
|
|
263
279
|
self.visit(node.value)
|
|
264
280
|
|
|
265
281
|
def visit_Delete(self, node: ast.Delete) -> None:
|