sarj-python-lint 0.87.0__tar.gz → 0.87.1__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 (141) hide show
  1. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/.gitignore +4 -3
  2. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/PKG-INFO +1 -1
  3. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/pyproject.toml +1 -1
  4. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +68 -8
  5. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/LICENSE +0 -0
  6. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/README.md +0 -0
  7. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/__init__.py +0 -0
  8. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/__main__.py +0 -0
  9. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  10. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_filesystem.py +0 -0
  11. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_python_target.py +0 -0
  12. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  13. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_secret_names.py +0 -0
  14. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/_version.py +0 -0
  15. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/py.typed +0 -0
  16. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/ratchet.py +0 -0
  17. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rule_base.py +0 -0
  18. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  19. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  20. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  21. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  22. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  23. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  24. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  25. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  26. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  27. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  28. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  29. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  30. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  31. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  32. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_registry.py +0 -0
  33. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  34. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  35. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  36. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  37. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  38. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  39. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  40. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  41. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  42. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  43. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  44. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  45. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  46. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  47. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  48. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  49. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  50. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  51. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  52. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  53. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  54. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  55. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  56. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  57. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  58. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  59. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  60. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  61. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  62. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  63. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  64. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  65. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  66. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  67. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  68. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  69. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  70. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  71. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  72. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  73. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  74. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  75. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  76. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  77. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  78. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  79. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  80. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  81. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  82. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  83. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  84. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  85. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  86. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  87. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  88. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  89. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  90. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  91. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  92. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  93. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  94. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  95. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  96. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  97. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  98. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  99. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  100. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  101. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  102. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  103. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  104. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  105. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  106. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  107. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  108. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  109. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  110. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  111. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  112. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  113. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  114. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  115. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  116. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  117. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  118. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  119. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  120. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  121. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  122. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  123. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  124. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  125. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  126. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  127. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  128. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  129. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  130. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  131. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/require_typed_http_test_response.py +0 -0
  132. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  133. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  134. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  135. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  136. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  137. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  138. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  139. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  140. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  141. {sarj_python_lint-0.87.0 → sarj_python_lint-0.87.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
@@ -32,9 +32,10 @@ Thumbs.db
32
32
  .zed/
33
33
  *.swp
34
34
 
35
- # Claude Code local state (shared settings.json is tracked)
36
- .claude/*
37
- !.claude/settings.json
35
+ .claude/
36
+ apps/docs/.wrangler/
37
+ apps/docs/.dev.vars*
38
+ !apps/docs/.dev.vars.example
38
39
  .worktrees/
39
40
  .sarj-private-refs.toml
40
41
  .sarj-standards-rollout.toml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 0.87.0
3
+ Version: 0.87.1
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.87.0"
3
+ version = "0.87.1"
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" }]
@@ -32,7 +32,8 @@ class NoUnusedValueMarker(Rule):
32
32
  summary="Do not use standalone assignments to `_` to discard values.",
33
33
  rationale=(
34
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."
35
+ "be invoked directly. Pure references to formal parameters are excluded because callback and protocol "
36
+ "signatures may require names that Ruff otherwise reports as unused."
36
37
  ),
37
38
  remediation=(
38
39
  "Invoke calls directly instead of assigning their result to `_`. Remove or rename locally owned unused "
@@ -43,14 +44,15 @@ class NoUnusedValueMarker(Rule):
43
44
  autofix=AutofixPolicy.NONE,
44
45
  limitations=(
45
46
  "Plain and annotated assignments whose standalone target is `_` are reported, including chained assignments.",
47
+ "Pure name or tuple references to the enclosing function's unchanged parameters are excluded.",
46
48
  "Unpacking placeholders, loop targets, wildcard imports, and underscore-prefixed names are excluded.",
47
49
  "Generated and vendored files are excluded.",
48
50
  ),
49
51
  examples=(
50
52
  RuleExample(
51
- example_id="unused-parameter-marker",
52
- title="Do not manufacture a use for interface parameters",
53
- outcome=ExampleOutcome.MATCH,
53
+ example_id="callback-parameter-marker",
54
+ title="A callback keeps its required parameter names",
55
+ outcome=ExampleOutcome.NO_MATCH,
54
56
  files=(
55
57
  ExampleFile.python(
56
58
  "app/adapter.py",
@@ -58,6 +60,15 @@ class NoUnusedValueMarker(Rule):
58
60
  ),
59
61
  ),
60
62
  focus_path=PurePosixPath("app/adapter.py"),
63
+ expected_count=0,
64
+ public=False,
65
+ ),
66
+ RuleExample(
67
+ example_id="discarded-nonparameter-value",
68
+ title="A standalone discard binding is unnecessary",
69
+ outcome=ExampleOutcome.MATCH,
70
+ files=(ExampleFile.python("app/adapter.py", "value = 1\n_ = value\n"),),
71
+ focus_path=PurePosixPath("app/adapter.py"),
61
72
  expected_count=1,
62
73
  public=True,
63
74
  ),
@@ -116,6 +127,18 @@ class NoUnusedValueMarker(Rule):
116
127
  if tree is None:
117
128
  return []
118
129
  source_lines = source.splitlines()
130
+ markers = [
131
+ node
132
+ for node in ast.walk(tree)
133
+ if isinstance(node, (ast.Assign, ast.AnnAssign)) and _is_unused_value_marker(node)
134
+ ]
135
+ if not markers:
136
+ return []
137
+ parents = (
138
+ {child: parent for parent in ast.walk(tree) for child in ast.iter_child_nodes(parent)}
139
+ if any(isinstance(node, ast.Assign) and isinstance(node.value, (ast.Name, ast.Tuple)) for node in markers)
140
+ else {}
141
+ )
119
142
  return [
120
143
  Diagnostic(
121
144
  path=path,
@@ -128,10 +151,8 @@ class NoUnusedValueMarker(Rule):
128
151
  "directly or address unused values at their declaration."
129
152
  ),
130
153
  )
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)
154
+ for node in markers
155
+ if not _is_pure_parameter_marker(node, parents) and not is_suppressed(source_lines, node.lineno, self.code)
135
156
  ]
136
157
 
137
158
 
@@ -139,3 +160,42 @@ def _is_unused_value_marker(node: ast.Assign | ast.AnnAssign) -> bool:
139
160
  if isinstance(node, ast.AnnAssign):
140
161
  return node.value is not None and isinstance(node.target, ast.Name) and node.target.id == "_"
141
162
  return any(isinstance(target, ast.Name) and target.id == "_" for target in node.targets)
163
+
164
+
165
+ def _is_pure_parameter_marker(
166
+ node: ast.Assign | ast.AnnAssign,
167
+ parents: dict[ast.AST, ast.AST],
168
+ ) -> bool:
169
+ if not isinstance(node, ast.Assign) or len(node.targets) != 1:
170
+ return False
171
+ if not isinstance(node.targets[0], ast.Name) or node.targets[0].id != "_":
172
+ return False
173
+ names = _marker_reference_names(node.value)
174
+ if names is None:
175
+ return False
176
+ function = _enclosing_function(node, parents)
177
+ if function is None:
178
+ return False
179
+ parameters = {argument.arg for argument in ast.walk(function.args) if isinstance(argument, ast.arg)}
180
+ return names <= parameters and not any(
181
+ isinstance(child, ast.Name) and isinstance(child.ctx, ast.Store) and child.id in names
182
+ for child in ast.walk(function)
183
+ )
184
+
185
+
186
+ def _marker_reference_names(value: ast.expr) -> set[str] | None:
187
+ values = value.elts if isinstance(value, ast.Tuple) else [value]
188
+ if not values or not all(isinstance(item, ast.Name) for item in values):
189
+ return None
190
+ return {item.id for item in values if isinstance(item, ast.Name)}
191
+
192
+
193
+ def _enclosing_function(
194
+ node: ast.AST, parents: dict[ast.AST, ast.AST]
195
+ ) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
196
+ current = node
197
+ while current in parents:
198
+ current = parents[current]
199
+ if isinstance(current, (ast.FunctionDef, ast.AsyncFunctionDef)):
200
+ return current
201
+ return None