sarj-python-lint 0.84.2__tar.gz → 0.84.3__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 (139) hide show
  1. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_registry.py +4 -2
  4. sarj_python_lint-0.84.3/src/sarj_python_lint/rules/no_delete_statement.py +104 -0
  5. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -1
  6. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +5 -5
  7. sarj_python_lint-0.84.3/src/sarj_python_lint/rules/no_unused_value_marker.py +141 -0
  8. sarj_python_lint-0.84.2/src/sarj_python_lint/rules/no_deleted_only_override_parameter.py +0 -257
  9. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/.gitignore +0 -0
  10. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/LICENSE +0 -0
  11. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/README.md +0 -0
  12. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/__init__.py +0 -0
  13. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/__main__.py +0 -0
  14. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_analysis_session.py +0 -0
  15. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_filesystem.py +0 -0
  16. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/_version.py +0 -0
  20. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/py.typed +0 -0
  21. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/ratchet.py +0 -0
  22. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rule_base.py +0 -0
  23. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/__init__.py +0 -0
  24. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  25. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_comments.py +0 -0
  26. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  27. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  28. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_first_party.py +0 -0
  29. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  30. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_imports.py +0 -0
  31. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_logging.py +0 -0
  32. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_paths.py +0 -0
  33. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_project_index.py +0 -0
  34. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  35. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_pytest.py +0 -0
  36. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_sql.py +0 -0
  37. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  38. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  39. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  40. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  41. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  42. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  43. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  44. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  45. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  46. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  47. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  48. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  49. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  50. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  51. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  52. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  53. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  54. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  55. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  56. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  57. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  58. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  59. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  60. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  61. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  62. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  63. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  64. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  65. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  66. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  67. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  68. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  69. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  70. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  71. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  72. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  73. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  74. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  75. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  76. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  77. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  78. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  79. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  80. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  81. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  82. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  83. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  84. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_vague_annotations.py +0 -0
  85. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  86. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  87. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  88. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  89. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  90. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  91. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  92. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  93. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  94. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  95. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  96. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  97. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  98. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  99. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  100. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  101. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  102. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  103. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  104. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  105. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  106. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  107. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  108. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  109. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  110. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  111. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  112. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  113. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  114. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  115. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  116. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  117. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  118. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  119. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  120. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  121. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  122. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  123. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  124. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  125. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  126. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  127. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  128. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  129. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/require_typed_http_test_response.py +0 -0
  130. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  131. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/stepdown.py +0 -0
  132. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  133. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  134. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  135. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  136. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  137. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  138. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  139. {sarj_python_lint-0.84.2 → sarj_python_lint-0.84.3}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 0.84.2
3
+ Version: 0.84.3
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.84.2"
3
+ version = "0.84.3"
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" }]
@@ -34,7 +34,7 @@ from sarj_python_lint.rules.no_copied_inherited_docstring import NoCopiedInherit
34
34
  from sarj_python_lint.rules.no_cors_wildcard_with_credentials import (
35
35
  NoCorsWildcardWithCredentials,
36
36
  )
37
- from sarj_python_lint.rules.no_deleted_only_override_parameter import NoDeletedOnlyOverrideParameter
37
+ from sarj_python_lint.rules.no_delete_statement import NoDeleteStatement
38
38
  from sarj_python_lint.rules.no_dunder_all import NoDunderAll
39
39
  from sarj_python_lint.rules.no_duplicate_dunder_all_entry import NoDuplicateDunderAllEntry
40
40
  from sarj_python_lint.rules.no_excessive_cognitive_complexity import NoExcessiveCognitiveComplexity
@@ -77,6 +77,7 @@ from sarj_python_lint.rules.no_unique_violation_message_match import (
77
77
  NoUniqueViolationMessageMatch,
78
78
  )
79
79
  from sarj_python_lint.rules.no_unnecessary_docstring import NoUnnecessaryDocstring
80
+ from sarj_python_lint.rules.no_unused_value_marker import NoUnusedValueMarker
80
81
  from sarj_python_lint.rules.no_vague_annotations import NoVagueAnnotations
81
82
  from sarj_python_lint.rules.no_vague_suppression_description import (
82
83
  NoVagueSuppressionDescription,
@@ -252,7 +253,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
252
253
  PreferSelfDocumentingConstant.id: PreferSelfDocumentingConstant,
253
254
  NoDuplicateDunderAllEntry.id: NoDuplicateDunderAllEntry,
254
255
  NoDunderAll.id: NoDunderAll,
255
- NoDeletedOnlyOverrideParameter.id: NoDeletedOnlyOverrideParameter,
256
+ NoDeleteStatement.id: NoDeleteStatement,
256
257
  NoCopiedInheritedDocstring.id: NoCopiedInheritedDocstring,
257
258
  RedundantClassDocstring.id: RedundantClassDocstring,
258
259
  RedundantModuleDocstring.id: RedundantModuleDocstring,
@@ -264,6 +265,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
264
265
  NoRedundantLiteralDescription.id: NoRedundantLiteralDescription,
265
266
  NoTypedDocSections.id: NoTypedDocSections,
266
267
  NoUnnecessaryDocstring.id: NoUnnecessaryDocstring,
268
+ NoUnusedValueMarker.id: NoUnusedValueMarker,
267
269
  PreferNominalIdTypes.id: PreferNominalIdTypes,
268
270
  NoVagueAnnotations.id: NoVagueAnnotations,
269
271
  NoUniqueViolationMessageMatch.id: NoUniqueViolationMessageMatch,
@@ -0,0 +1,104 @@
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
+ @final
28
+ class NoDeleteStatement(Rule):
29
+ id = "no-delete-statement"
30
+ code = "SARJ442"
31
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
32
+ summary="Avoid `del` statements; prefer constructing an immutable replacement value.",
33
+ rationale=(
34
+ "Deleting a name, attribute, or collection entry introduces lifetime or in-place mutation semantics "
35
+ "that are harder to follow than explicit value construction."
36
+ ),
37
+ remediation=(
38
+ "Remove unused-value deletion markers. Rebuild collections with comprehensions, copies, or immutable "
39
+ "value helpers such as dataclasses.replace. When an external API or deliberate lifetime boundary "
40
+ "requires deletion, add an exact SARJ442 suppression on the statement and explain why."
41
+ ),
42
+ category=RuleCategory.MAINTAINABILITY,
43
+ autofix=AutofixPolicy.NONE,
44
+ aliases=("no-deleted-only-override-parameter",),
45
+ limitations=(
46
+ "Every authored Python del statement is reported once, regardless of how many targets it contains.",
47
+ "The rule does not automatically rewrite deletion because the correct immutable replacement depends on ownership and value semantics.",
48
+ "Generated and vendored files are excluded.",
49
+ ),
50
+ examples=(
51
+ RuleExample(
52
+ example_id="delete-mapping-entry",
53
+ title="Build a mapping without the removed entry",
54
+ outcome=ExampleOutcome.MATCH,
55
+ files=(
56
+ ExampleFile.python(
57
+ "app/request.py",
58
+ "payload = dict(raw_payload)\ndel payload['secret']\n",
59
+ ),
60
+ ),
61
+ focus_path=PurePosixPath("app/request.py"),
62
+ expected_count=1,
63
+ public=True,
64
+ ),
65
+ RuleExample(
66
+ example_id="immutable-mapping-filter",
67
+ title="Construct the desired mapping directly",
68
+ outcome=ExampleOutcome.NO_MATCH,
69
+ files=(
70
+ ExampleFile.python(
71
+ "app/request.py",
72
+ "payload = {key: value for key, value in raw_payload.items() if key != 'secret'}\n",
73
+ ),
74
+ ),
75
+ focus_path=PurePosixPath("app/request.py"),
76
+ expected_count=0,
77
+ public=True,
78
+ ),
79
+ ),
80
+ )
81
+ description = documentation.summary
82
+
83
+ def check(self, path: Path, source: str) -> list[Diagnostic]:
84
+ if "del" not in source or is_generated(path, source):
85
+ return []
86
+ tree = parse_or_none(path, source)
87
+ if tree is None:
88
+ return []
89
+ source_lines = source.splitlines()
90
+ return [
91
+ Diagnostic(
92
+ path=path,
93
+ line=node.lineno,
94
+ col=node.col_offset + 1,
95
+ code=self.code,
96
+ severity=Severity.WARNING,
97
+ message=(
98
+ "`del` introduces mutation or hidden lifetime behavior — construct the desired value "
99
+ "immutably, or add an exact SARJ442 suppression explaining why deletion is required."
100
+ ),
101
+ )
102
+ for node in ast.walk(tree)
103
+ if isinstance(node, ast.Delete) and not is_suppressed(source_lines, node.lineno, self.code)
104
+ ]
@@ -442,7 +442,6 @@ class _RuntimeConfigResolver:
442
442
  and binding.symbol is not None
443
443
  and self._is_settings_symbol(binding.module, binding.symbol, seen | {key})
444
444
  )
445
- del module_path
446
445
  self._settings_cache[key] = result
447
446
  return result
448
447
 
@@ -404,7 +404,7 @@ def _function_scopes(tree: ast.Module) -> list[_FunctionScope]:
404
404
 
405
405
  @override
406
406
  def visit_Lambda(self, node: ast.Lambda) -> None:
407
- del node
407
+ return
408
408
 
409
409
  Collector().visit(tree)
410
410
  return scopes
@@ -623,19 +623,19 @@ class _FunctionAnalyzer:
623
623
 
624
624
  @override
625
625
  def visit_Lambda(self, node: ast.Lambda) -> None:
626
- del node
626
+ return
627
627
 
628
628
  @override
629
629
  def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
630
- del node
630
+ return
631
631
 
632
632
  @override
633
633
  def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
634
- del node
634
+ return
635
635
 
636
636
  @override
637
637
  def visit_ClassDef(self, node: ast.ClassDef) -> None:
638
- del node
638
+ return
639
639
 
640
640
  Visitor().visit(node)
641
641
 
@@ -0,0 +1,141 @@
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
+ @final
28
+ class NoUnusedValueMarker(Rule):
29
+ id = "no-unused-value-marker"
30
+ code = "SARJ452"
31
+ documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
32
+ summary="Do not use standalone assignments to `_` to discard values.",
33
+ rationale=(
34
+ "A standalone `_ = value` assignment adds a binding without clarifying intent. A side-effecting call can "
35
+ "be invoked directly, while an unused parameter should be addressed at its declaration."
36
+ ),
37
+ remediation=(
38
+ "Invoke calls directly instead of assigning their result to `_`. Remove or rename locally owned unused "
39
+ "values; when an external interface requires the exact unused parameter, put a narrow reasoned linter "
40
+ "suppression on its declaration."
41
+ ),
42
+ category=RuleCategory.MAINTAINABILITY,
43
+ autofix=AutofixPolicy.NONE,
44
+ limitations=(
45
+ "Plain and annotated assignments whose standalone target is `_` are reported, including chained assignments.",
46
+ "Unpacking placeholders, loop targets, wildcard imports, and underscore-prefixed names are excluded.",
47
+ "Generated and vendored files are excluded.",
48
+ ),
49
+ examples=(
50
+ RuleExample(
51
+ example_id="unused-parameter-marker",
52
+ title="Do not manufacture a use for interface parameters",
53
+ outcome=ExampleOutcome.MATCH,
54
+ files=(
55
+ ExampleFile.python(
56
+ "app/adapter.py",
57
+ "def render(root: Path, manifest: Path) -> str:\n _ = root, manifest\n return 'ready'\n",
58
+ ),
59
+ ),
60
+ focus_path=PurePosixPath("app/adapter.py"),
61
+ expected_count=1,
62
+ public=True,
63
+ ),
64
+ RuleExample(
65
+ example_id="discarded-call-result",
66
+ title="Invoke a call directly when its result is intentionally unused",
67
+ outcome=ExampleOutcome.MATCH,
68
+ files=(
69
+ ExampleFile.python(
70
+ "app/adapter.py",
71
+ "_ = refresh_cache()\n",
72
+ ),
73
+ ),
74
+ focus_path=PurePosixPath("app/adapter.py"),
75
+ expected_count=1,
76
+ public=True,
77
+ scenario="call-result",
78
+ ),
79
+ RuleExample(
80
+ example_id="direct-call",
81
+ title="Run the call without creating a discard binding",
82
+ outcome=ExampleOutcome.NO_MATCH,
83
+ files=(
84
+ ExampleFile.python(
85
+ "app/adapter.py",
86
+ "refresh_cache()\n",
87
+ ),
88
+ ),
89
+ focus_path=PurePosixPath("app/adapter.py"),
90
+ expected_count=0,
91
+ public=True,
92
+ scenario="call-result",
93
+ ),
94
+ RuleExample(
95
+ example_id="unpacking-placeholder",
96
+ title="Keep a conventional placeholder inside unpacking",
97
+ outcome=ExampleOutcome.NO_MATCH,
98
+ files=(
99
+ ExampleFile.python(
100
+ "app/adapter.py",
101
+ "name, _ = load_pair()\n",
102
+ ),
103
+ ),
104
+ focus_path=PurePosixPath("app/adapter.py"),
105
+ expected_count=0,
106
+ public=True,
107
+ ),
108
+ ),
109
+ )
110
+ description = documentation.summary
111
+
112
+ def check(self, path: Path, source: str) -> list[Diagnostic]:
113
+ if "_" not in source or "=" not in source or is_generated(path, source):
114
+ return []
115
+ tree = parse_or_none(path, source)
116
+ if tree is None:
117
+ return []
118
+ source_lines = source.splitlines()
119
+ return [
120
+ Diagnostic(
121
+ path=path,
122
+ line=node.lineno,
123
+ col=node.col_offset + 1,
124
+ code=self.code,
125
+ severity=Severity.WARNING,
126
+ message=(
127
+ "standalone assignment to `_` discards a value through an unnecessary binding — invoke calls "
128
+ "directly or address unused values at their declaration."
129
+ ),
130
+ )
131
+ for node in ast.walk(tree)
132
+ if isinstance(node, (ast.Assign, ast.AnnAssign))
133
+ and _is_unused_value_marker(node)
134
+ and not is_suppressed(source_lines, node.lineno, self.code)
135
+ ]
136
+
137
+
138
+ def _is_unused_value_marker(node: ast.Assign | ast.AnnAssign) -> bool:
139
+ if isinstance(node, ast.AnnAssign):
140
+ return node.value is not None and isinstance(node.target, ast.Name) and node.target.id == "_"
141
+ return any(isinstance(target, ast.Name) and target.id == "_" for target in node.targets)
@@ -1,257 +0,0 @@
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
- _collect_override_aliases(statement, direct, conflicting)
128
- continue
129
- if isinstance(statement, ast.Import):
130
- for alias in statement.names:
131
- name = alias.asname or alias.name.split(".")[0]
132
- if alias.name in _OVERRIDE_MODULES and name not in modules:
133
- modules.add(name)
134
- else:
135
- conflicting.add(name)
136
- continue
137
- conflicting.update(_statement_bound_names(statement))
138
- return frozenset(direct - conflicting), frozenset(modules - conflicting)
139
-
140
-
141
- def _statement_bound_names(statement: ast.stmt) -> set[str]:
142
- names: set[str] = set()
143
- if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
144
- names.add(statement.name)
145
- for node in ast.walk(statement):
146
- if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store):
147
- names.add(node.id)
148
- elif isinstance(node, ast.alias):
149
- names.add(node.asname or node.name.split(".")[0])
150
- return names
151
-
152
-
153
- def _class_bound_names(node: ast.ClassDef) -> frozenset[str]:
154
- names: set[str] = set()
155
- for statement in node.body:
156
- if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
157
- names.add(statement.name)
158
- elif not isinstance(statement, ast.ClassDef):
159
- names.update(_statement_bound_names(statement))
160
- return frozenset(names)
161
-
162
-
163
- def _is_override(
164
- method: ast.FunctionDef | ast.AsyncFunctionDef,
165
- direct_aliases: frozenset[str],
166
- module_aliases: frozenset[str],
167
- ) -> bool:
168
- for decorator in method.decorator_list:
169
- if isinstance(decorator, ast.Name) and decorator.id in direct_aliases:
170
- return True
171
- if (
172
- isinstance(decorator, ast.Attribute)
173
- and decorator.attr == "override"
174
- and isinstance(decorator.value, ast.Name)
175
- and decorator.value.id in module_aliases
176
- ):
177
- return True
178
- return False
179
-
180
-
181
- def _method_diagnostics(
182
- path: Path,
183
- source_lines: list[str],
184
- method: ast.FunctionDef | ast.AsyncFunctionDef,
185
- ) -> list[Diagnostic]:
186
- delete_targets = _direct_delete_targets(method.body)
187
- parameters = {
188
- argument.arg
189
- for argument in (
190
- *method.args.posonlyargs,
191
- *method.args.args,
192
- *method.args.kwonlyargs,
193
- *(() if method.args.vararg is None else (method.args.vararg,)),
194
- *(() if method.args.kwarg is None else (method.args.kwarg,)),
195
- )
196
- }
197
- direct_nodes = {id(node) for nodes in delete_targets.values() for node in nodes}
198
- other_occurrences = {
199
- node.id
200
- for statement in method.body
201
- for node in ast.walk(statement)
202
- if isinstance(node, ast.Name) and id(node) not in direct_nodes
203
- }
204
- diagnostics: list[Diagnostic] = []
205
- for name in sorted(parameters.intersection(delete_targets) - other_occurrences):
206
- target = min(delete_targets[name], key=lambda node: (node.lineno, node.col_offset))
207
- if is_suppressed(source_lines, target.lineno, NoDeletedOnlyOverrideParameter.code):
208
- continue
209
- diagnostics.append(
210
- Diagnostic(
211
- path=path,
212
- line=target.lineno,
213
- col=target.col_offset + 1,
214
- code=NoDeletedOnlyOverrideParameter.code,
215
- severity=Severity.WARNING,
216
- message=(
217
- f"override parameter `{name}` is referenced only by direct deletion — remove the unused-marker "
218
- "deletion, or suppress this warning with a reason when early reference release is intentional."
219
- ),
220
- )
221
- )
222
- return diagnostics
223
-
224
-
225
- def _direct_delete_targets(statements: list[ast.stmt]) -> dict[str, list[ast.Name]]:
226
- targets: dict[str, list[ast.Name]] = {}
227
-
228
- def visit(node: ast.AST) -> None:
229
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)):
230
- return
231
- if isinstance(node, ast.Delete):
232
- for target in node.targets:
233
- for name in _bare_delete_names(target):
234
- targets.setdefault(name.id, []).append(name)
235
- for child in ast.iter_child_nodes(node):
236
- visit(child)
237
-
238
- for statement in statements:
239
- visit(statement)
240
- return targets
241
-
242
-
243
- def _bare_delete_names(target: ast.expr) -> list[ast.Name]:
244
- if isinstance(target, ast.Name):
245
- return [target]
246
- if isinstance(target, (ast.Tuple, ast.List)):
247
- return [name for item in target.elts for name in _bare_delete_names(item)]
248
- return []
249
-
250
-
251
- def _collect_override_aliases(statement: ast.ImportFrom, direct: set[str], conflicting: set[str]) -> None:
252
- for alias in statement.names:
253
- name = alias.asname or alias.name
254
- if alias.name == "override" and name not in direct:
255
- direct.add(name)
256
- else:
257
- conflicting.add(name)