sarj-python-lint 0.81.12__tar.gz → 0.81.13__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.
Files changed (128) hide show
  1. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_registry.py +2 -0
  4. sarj_python_lint-0.81.13/src/sarj_python_lint/rules/no_deleted_only_override_parameter.py +253 -0
  5. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_dunder_all.py +1 -1
  6. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +5 -5
  7. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +1 -1
  8. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +4 -4
  9. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/over_mocked_test.py +1 -1
  10. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_class_row.py +1 -1
  11. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/.gitignore +0 -0
  12. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/LICENSE +0 -0
  13. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/README.md +0 -0
  14. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/__init__.py +0 -0
  15. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/__main__.py +0 -0
  16. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_analysis_session.py +0 -0
  17. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_filesystem.py +0 -0
  18. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_python_target.py +0 -0
  19. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  20. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_secret_names.py +0 -0
  21. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/_version.py +0 -0
  22. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/py.typed +0 -0
  23. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/ratchet.py +0 -0
  24. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rule_base.py +0 -0
  25. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/__init__.py +0 -0
  26. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  27. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_comments.py +0 -0
  28. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  29. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  30. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_first_party.py +0 -0
  31. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  32. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_imports.py +0 -0
  33. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_logging.py +0 -0
  34. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_paths.py +0 -0
  35. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_project_index.py +0 -0
  36. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  37. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_pytest.py +0 -0
  38. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_sql.py +0 -0
  39. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  40. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  41. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  42. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  43. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  44. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  45. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  46. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  47. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  48. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  49. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  50. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  51. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  52. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  53. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  54. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  55. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  56. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  57. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  58. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  59. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  60. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  61. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  62. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  63. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  64. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  65. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  66. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  67. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  68. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  69. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  70. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  71. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  72. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  73. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  74. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  75. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  76. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  77. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  78. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  79. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  80. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  81. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  82. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  83. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  84. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  85. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  86. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  87. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  88. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  89. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  90. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  91. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  92. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  93. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  94. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  95. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  96. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  97. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  98. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  99. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  100. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  101. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  102. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  103. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  104. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  105. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  106. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  107. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  108. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  109. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  110. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  111. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  112. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  113. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  114. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  115. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  116. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  117. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  118. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  119. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  120. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  121. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/stepdown.py +0 -0
  122. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  123. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  124. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  125. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  126. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  127. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  128. {sarj_python_lint-0.81.12 → sarj_python_lint-0.81.13}/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.81.12
3
+ Version: 0.81.13
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "0.81.12"
3
+ version = "0.81.13"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -33,6 +33,7 @@ from sarj_python_lint.rules.no_copied_inherited_docstring import NoCopiedInherit
33
33
  from sarj_python_lint.rules.no_cors_wildcard_with_credentials import (
34
34
  NoCorsWildcardWithCredentials,
35
35
  )
36
+ from sarj_python_lint.rules.no_deleted_only_override_parameter import NoDeletedOnlyOverrideParameter
36
37
  from sarj_python_lint.rules.no_dunder_all import NoDunderAll
37
38
  from sarj_python_lint.rules.no_duplicate_dunder_all_entry import NoDuplicateDunderAllEntry
38
39
  from sarj_python_lint.rules.no_fastapi_on_event import NoFastapiOnEvent
@@ -232,6 +233,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
232
233
  PreferSelfDocumentingConstant.id: PreferSelfDocumentingConstant,
233
234
  NoDuplicateDunderAllEntry.id: NoDuplicateDunderAllEntry,
234
235
  NoDunderAll.id: NoDunderAll,
236
+ NoDeletedOnlyOverrideParameter.id: NoDeletedOnlyOverrideParameter,
235
237
  NoCopiedInheritedDocstring.id: NoCopiedInheritedDocstring,
236
238
  RedundantClassDocstring.id: RedundantClassDocstring,
237
239
  RedundantModuleDocstring.id: RedundantModuleDocstring,
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import PurePosixPath
5
+ from typing import TYPE_CHECKING, ClassVar, final
6
+
7
+ from sarj_python_lint.rule_base import (
8
+ AutofixPolicy,
9
+ Diagnostic,
10
+ ExampleFile,
11
+ ExampleOutcome,
12
+ Rule,
13
+ RuleCategory,
14
+ RuleDocumentation,
15
+ RuleExample,
16
+ Severity,
17
+ is_suppressed,
18
+ parse_or_none,
19
+ )
20
+ from sarj_python_lint.rules._paths import is_generated
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from pathlib import Path
25
+
26
+
27
+ _OVERRIDE_MODULES = frozenset({"typing", "typing_extensions"})
28
+
29
+
30
+ @final
31
+ class NoDeletedOnlyOverrideParameter(Rule):
32
+ id = "no-deleted-only-override-parameter"
33
+ code = "SARJ442"
34
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
35
+ summary="Do not use direct deletion as the only reference to an override parameter.",
36
+ rationale=(
37
+ "Deleting an otherwise unread override parameter is commonly used as an unused-value marker, but "
38
+ "`@override` already preserves the interface contract without requiring a synthetic reference."
39
+ ),
40
+ remediation=(
41
+ "Keep the override signature and remove the deletion marker. If deletion intentionally shortens the "
42
+ "reference lifetime, retain it with an exact suppression that explains the lifetime boundary."
43
+ ),
44
+ category=RuleCategory.STYLE,
45
+ autofix=AutofixPolicy.NONE,
46
+ limitations=(
47
+ "Only direct methods of module-level classes decorated by a proven runtime import of typing.override or typing_extensions.override are checked.",
48
+ "A parameter is reported only when every occurrence in the method is a direct bare-name deletion; any load, rebinding, nested occurrence, attribute deletion, or subscription deletion excludes it.",
49
+ "Removing a deletion can change reference lifetime, finalization, frame tracing, or locals() visibility, so the rule has no autofix and supports an exact reasoned suppression.",
50
+ "Generated files are excluded.",
51
+ ),
52
+ examples=(
53
+ RuleExample(
54
+ example_id="override-parameter-delete-marker",
55
+ title="Remove a deletion used only as an override parameter marker",
56
+ outcome=ExampleOutcome.MATCH,
57
+ files=(
58
+ ExampleFile.python(
59
+ "app/handler.py",
60
+ "from typing import override\n\n"
61
+ "class Handler(BaseHandler):\n"
62
+ " @override\n"
63
+ " def handle(self, request: Request) -> None:\n"
64
+ " del request\n"
65
+ " emit_ready()\n",
66
+ ),
67
+ ),
68
+ focus_path=PurePosixPath("app/handler.py"),
69
+ expected_count=1,
70
+ public=True,
71
+ ),
72
+ RuleExample(
73
+ example_id="override-uses-parameter",
74
+ title="Keep an override parameter that contributes to behavior",
75
+ outcome=ExampleOutcome.NO_MATCH,
76
+ files=(
77
+ ExampleFile.python(
78
+ "app/handler.py",
79
+ "from typing import override\n\n"
80
+ "class Handler(BaseHandler):\n"
81
+ " @override\n"
82
+ " def handle(self, request: Request) -> None:\n"
83
+ " dispatch(request)\n",
84
+ ),
85
+ ),
86
+ focus_path=PurePosixPath("app/handler.py"),
87
+ expected_count=0,
88
+ public=True,
89
+ ),
90
+ ),
91
+ )
92
+ description = documentation.summary
93
+
94
+ def check(self, path: Path, source: str) -> list[Diagnostic]:
95
+ if "del" not in source or "override" not in source or is_generated(path, source):
96
+ return []
97
+ tree = parse_or_none(path, source)
98
+ if tree is None:
99
+ return []
100
+ direct_aliases, module_aliases = _proven_override_imports(tree)
101
+ if not direct_aliases and not module_aliases:
102
+ return []
103
+ source_lines = source.splitlines()
104
+ diagnostics: list[Diagnostic] = []
105
+ for statement in tree.body:
106
+ if not isinstance(statement, ast.ClassDef):
107
+ continue
108
+ class_bound = _class_bound_names(statement)
109
+ available_direct = direct_aliases - class_bound
110
+ available_modules = module_aliases - class_bound
111
+ for method in statement.body:
112
+ if not isinstance(method, (ast.FunctionDef, ast.AsyncFunctionDef)) or not _is_override(
113
+ method, available_direct, available_modules
114
+ ):
115
+ continue
116
+ diagnostics.extend(_method_diagnostics(path, source_lines, method))
117
+ diagnostics.sort(key=lambda item: (item.line, item.col))
118
+ return diagnostics
119
+
120
+
121
+ def _proven_override_imports(tree: ast.Module) -> tuple[frozenset[str], frozenset[str]]:
122
+ direct: set[str] = set()
123
+ modules: set[str] = set()
124
+ conflicting: set[str] = set()
125
+ for statement in tree.body:
126
+ if isinstance(statement, ast.ImportFrom) and statement.module in _OVERRIDE_MODULES:
127
+ for alias in statement.names:
128
+ name = alias.asname or alias.name
129
+ if alias.name == "override" and name not in direct:
130
+ direct.add(name)
131
+ else:
132
+ conflicting.add(name)
133
+ continue
134
+ if isinstance(statement, ast.Import):
135
+ for alias in statement.names:
136
+ name = alias.asname or alias.name.split(".")[0]
137
+ if alias.name in _OVERRIDE_MODULES and name not in modules:
138
+ modules.add(name)
139
+ else:
140
+ conflicting.add(name)
141
+ continue
142
+ conflicting.update(_statement_bound_names(statement))
143
+ return frozenset(direct - conflicting), frozenset(modules - conflicting)
144
+
145
+
146
+ def _statement_bound_names(statement: ast.stmt) -> set[str]:
147
+ names: set[str] = set()
148
+ if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
149
+ names.add(statement.name)
150
+ for node in ast.walk(statement):
151
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store):
152
+ names.add(node.id)
153
+ elif isinstance(node, ast.alias):
154
+ names.add(node.asname or node.name.split(".")[0])
155
+ return names
156
+
157
+
158
+ def _class_bound_names(node: ast.ClassDef) -> frozenset[str]:
159
+ names: set[str] = set()
160
+ for statement in node.body:
161
+ if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
162
+ names.add(statement.name)
163
+ elif not isinstance(statement, ast.ClassDef):
164
+ names.update(_statement_bound_names(statement))
165
+ return frozenset(names)
166
+
167
+
168
+ def _is_override(
169
+ method: ast.FunctionDef | ast.AsyncFunctionDef,
170
+ direct_aliases: frozenset[str],
171
+ module_aliases: frozenset[str],
172
+ ) -> bool:
173
+ for decorator in method.decorator_list:
174
+ if isinstance(decorator, ast.Name) and decorator.id in direct_aliases:
175
+ return True
176
+ if (
177
+ isinstance(decorator, ast.Attribute)
178
+ and decorator.attr == "override"
179
+ and isinstance(decorator.value, ast.Name)
180
+ and decorator.value.id in module_aliases
181
+ ):
182
+ return True
183
+ return False
184
+
185
+
186
+ def _method_diagnostics(
187
+ path: Path,
188
+ source_lines: list[str],
189
+ method: ast.FunctionDef | ast.AsyncFunctionDef,
190
+ ) -> list[Diagnostic]:
191
+ delete_targets = _direct_delete_targets(method.body)
192
+ parameters = {
193
+ argument.arg
194
+ for argument in (
195
+ *method.args.posonlyargs,
196
+ *method.args.args,
197
+ *method.args.kwonlyargs,
198
+ *(() if method.args.vararg is None else (method.args.vararg,)),
199
+ *(() if method.args.kwarg is None else (method.args.kwarg,)),
200
+ )
201
+ }
202
+ direct_nodes = {id(node) for nodes in delete_targets.values() for node in nodes}
203
+ other_occurrences = {
204
+ node.id
205
+ for statement in method.body
206
+ for node in ast.walk(statement)
207
+ if isinstance(node, ast.Name) and id(node) not in direct_nodes
208
+ }
209
+ diagnostics: list[Diagnostic] = []
210
+ for name in sorted(parameters.intersection(delete_targets) - other_occurrences):
211
+ target = min(delete_targets[name], key=lambda node: (node.lineno, node.col_offset))
212
+ if is_suppressed(source_lines, target.lineno, NoDeletedOnlyOverrideParameter.code):
213
+ continue
214
+ diagnostics.append(
215
+ Diagnostic(
216
+ path=path,
217
+ line=target.lineno,
218
+ col=target.col_offset + 1,
219
+ code=NoDeletedOnlyOverrideParameter.code,
220
+ severity=Severity.WARNING,
221
+ message=(
222
+ f"override parameter `{name}` is referenced only by direct deletion — remove the unused-marker "
223
+ "deletion, or suppress this warning with a reason when early reference release is intentional."
224
+ ),
225
+ )
226
+ )
227
+ return diagnostics
228
+
229
+
230
+ def _direct_delete_targets(statements: list[ast.stmt]) -> dict[str, list[ast.Name]]:
231
+ targets: dict[str, list[ast.Name]] = {}
232
+
233
+ def visit(node: ast.AST) -> None:
234
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)):
235
+ return
236
+ if isinstance(node, ast.Delete):
237
+ for target in node.targets:
238
+ for name in _bare_delete_names(target):
239
+ targets.setdefault(name.id, []).append(name)
240
+ for child in ast.iter_child_nodes(node):
241
+ visit(child)
242
+
243
+ for statement in statements:
244
+ visit(statement)
245
+ return targets
246
+
247
+
248
+ def _bare_delete_names(target: ast.expr) -> list[ast.Name]:
249
+ if isinstance(target, ast.Name):
250
+ return [target]
251
+ if isinstance(target, (ast.Tuple, ast.List)):
252
+ return [name for item in target.elts for name in _bare_delete_names(item)]
253
+ return []
@@ -218,7 +218,7 @@ class _ScopePruningVisitor(ast.NodeVisitor):
218
218
 
219
219
  @override
220
220
  def visit_Lambda(self, node: ast.Lambda) -> None:
221
- del node
221
+ return None
222
222
 
223
223
  @override
224
224
  def visit_If(self, node: ast.If) -> None:
@@ -874,20 +874,20 @@ class _LocalBindingCollector(ast.NodeVisitor):
874
874
 
875
875
  @override
876
876
  def visit_Lambda(self, node: ast.Lambda) -> None:
877
- del node
877
+ return None
878
878
 
879
879
  @override
880
880
  def visit_ListComp(self, node: ast.ListComp) -> None:
881
- del node
881
+ return None
882
882
 
883
883
  @override
884
884
  def visit_SetComp(self, node: ast.SetComp) -> None:
885
- del node
885
+ return None
886
886
 
887
887
  @override
888
888
  def visit_DictComp(self, node: ast.DictComp) -> None:
889
- del node
889
+ return None
890
890
 
891
891
  @override
892
892
  def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
893
- del node
893
+ return None
@@ -394,7 +394,7 @@ class _BindingVisitor(ast.NodeVisitor):
394
394
 
395
395
  @override
396
396
  def visit_Lambda(self, node: ast.Lambda) -> None:
397
- del node
397
+ return None
398
398
 
399
399
  @override
400
400
  def visit_Import(self, node: ast.Import) -> None:
@@ -392,19 +392,19 @@ class FunctionAnalyzer(ast.NodeVisitor):
392
392
 
393
393
  @override
394
394
  def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
395
- del node # A nested helper owns a separate scope and is intentionally not inferred.
395
+ return None # A nested helper owns a separate scope and is intentionally not inferred.
396
396
 
397
397
  @override
398
398
  def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
399
- del node
399
+ return None
400
400
 
401
401
  @override
402
402
  def visit_ClassDef(self, node: ast.ClassDef) -> None:
403
- del node
403
+ return None
404
404
 
405
405
  @override
406
406
  def visit_Lambda(self, node: ast.Lambda) -> None:
407
- del node
407
+ return None
408
408
 
409
409
  @override
410
410
  def visit_Assign(self, node: ast.Assign) -> None:
@@ -307,7 +307,7 @@ class _BindingVisitor(ast.NodeVisitor):
307
307
 
308
308
  @override
309
309
  def visit_Lambda(self, node: ast.Lambda) -> None:
310
- del node
310
+ return None
311
311
 
312
312
  @override
313
313
  def visit_Import(self, node: ast.Import) -> None:
@@ -216,7 +216,7 @@ class _BindingVisitor(ast.NodeVisitor):
216
216
 
217
217
  @override
218
218
  def visit_Lambda(self, node: ast.Lambda) -> None:
219
- del node
219
+ return None
220
220
 
221
221
  @override
222
222
  def visit_Import(self, node: ast.Import) -> None: