sarj-python-lint 1.72.1__tar.gz → 1.74.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 (254) hide show
  1. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/pyproject.toml +1 -1
  3. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_local_alias_fix.py +4 -9
  4. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_resource_provenance.py +3 -3
  5. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_redundant_local_alias.py +105 -22
  6. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/.gitignore +0 -0
  7. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/LICENSE +0 -0
  8. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/README.md +0 -0
  9. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/__init__.py +0 -0
  10. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/__main__.py +0 -0
  11. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  12. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_file_context.py +0 -0
  13. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_filesystem.py +0 -0
  14. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_fixes.py +0 -0
  15. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_parallel.py +0 -0
  16. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_python_target.py +0 -0
  17. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  18. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_secret_names.py +0 -0
  19. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_source.py +0 -0
  20. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/_version.py +0 -0
  21. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/benchmark.py +0 -0
  22. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/cast_access.py +0 -0
  23. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
  24. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/json_boundary.py +0 -0
  25. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/py.typed +0 -0
  26. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/ratchet.py +0 -0
  27. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rule_base.py +0 -0
  28. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  29. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
  30. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  31. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  32. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
  33. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  34. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  35. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  36. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  37. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  38. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
  39. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
  40. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  41. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  42. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_exports.py +0 -0
  43. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  44. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  45. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  46. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  47. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  48. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
  49. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  50. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  51. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
  52. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  53. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
  54. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  55. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  56. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  57. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  58. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  59. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  60. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_registry.py +0 -0
  61. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  62. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
  63. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
  64. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  65. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  66. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  67. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
  68. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  69. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  70. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  71. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  72. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
  73. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  74. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  75. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  76. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  77. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
  78. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  79. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  80. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  81. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  82. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
  83. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  84. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  85. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  86. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  87. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  88. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  89. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  90. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  91. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  92. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  93. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  94. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  95. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  96. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
  97. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  98. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  99. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  100. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  101. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
  102. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  103. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  104. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
  105. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  106. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
  107. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  108. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
  109. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
  110. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  111. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  112. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
  113. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
  114. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  115. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  116. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  117. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  118. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  119. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  120. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
  121. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  122. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  123. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
  124. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  125. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  126. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  127. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  128. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  129. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  130. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  131. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
  132. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  133. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  134. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  135. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
  136. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  137. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  138. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  139. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
  140. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  141. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  142. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  143. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  144. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  145. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  146. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  147. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  148. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  149. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  150. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  151. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  152. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  153. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
  154. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
  155. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
  156. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  157. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  158. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  159. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  160. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  161. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  162. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  163. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  164. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  165. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  166. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  167. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  168. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  169. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
  170. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  171. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  172. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  173. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  174. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
  175. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  176. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  177. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  178. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  179. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
  180. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  181. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  182. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  183. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  184. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  185. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  186. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  187. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  188. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  189. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  190. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  191. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  192. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  193. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  194. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  195. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  196. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  197. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
  198. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
  199. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  200. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
  201. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
  202. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  203. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  204. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  205. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  206. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  207. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  208. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  209. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  210. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
  211. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  212. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  213. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  214. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  215. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  216. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
  217. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
  218. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  219. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  220. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  221. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  222. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  223. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  224. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  225. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  226. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
  227. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  228. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  229. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  230. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  231. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  232. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  233. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  234. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  235. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  236. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  237. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
  238. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  239. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  240. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  241. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  242. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  243. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
  244. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  245. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  246. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  247. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  248. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  249. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  250. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  251. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  252. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  253. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
  254. {sarj_python_lint-1.72.1 → sarj_python_lint-1.74.1}/src/sarj_python_lint/suppression_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 1.72.1
3
+ Version: 1.74.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 = "1.72.1"
3
+ version = "1.74.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" }]
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from sarj_python_lint._file_context import PythonFileContext
17
17
 
18
18
 
19
+ _ALIAS_RETURN_LENGTH = 2
19
20
  _NAMESPACE_MUTATORS = frozenset(
20
21
  {
21
22
  "setattr",
@@ -86,9 +87,9 @@ class LocalAliasFixes:
86
87
  if not isinstance(source, ast.Name) or parameters[0].arg != source.id:
87
88
  return ()
88
89
  body = owner.body[1:] if ast.get_docstring(owner) is not None else owner.body
89
- if body[:1] != [assignment] or len(body[1:]) != 1 or not isinstance(body[-1], ast.Return):
90
+ if len(body) != _ALIAS_RETURN_LENGTH or body[0] is not assignment or not isinstance(body[1], ast.Return):
90
91
  return ()
91
- call = body[-1].value
92
+ call = body[1].value
92
93
  if not isinstance(call, ast.Call) or not isinstance(call.func, ast.Name) or call.func.id != "dict":
93
94
  return ()
94
95
  if call.args or len(call.keywords) != 1 or call.keywords[0].value is not use or not self._available:
@@ -110,14 +111,8 @@ class LocalAliasFixes:
110
111
  return ()
111
112
  if assignment.end_lineno != assignment.lineno:
112
113
  return ()
113
- line = self.context.source_lines[assignment.lineno - 1]
114
114
  raw = self._lines[assignment.lineno - 1]
115
- if (
116
- raw[: assignment.col_offset].strip()
117
- or raw[assignment.end_col_offset :].strip()
118
- or ";" in line
119
- or "\\" in line
120
- ):
115
+ if raw[: assignment.col_offset].strip() or raw[assignment.end_col_offset :].strip():
121
116
  return ()
122
117
  return (
123
118
  TextEdit(assignment.lineno, 1, assignment.lineno + 1, 1, ""),
@@ -428,7 +428,7 @@ class ResourceProvenance:
428
428
  name = binding.id if isinstance(binding, ast.Name) else binding.name
429
429
  if name in self.mutated or name in self.external_bindings:
430
430
  return False
431
- for read in self._binding_reads(name, binding):
431
+ for read in self.binding_reads(name, binding):
432
432
  alias = self._function_alias(read)
433
433
  if alias is not None:
434
434
  pending.append(alias)
@@ -469,7 +469,7 @@ class ResourceProvenance:
469
469
  and any(self.imported(constructor.func, f"contextlib.{kind}") for kind in ("ExitStack", "AsyncExitStack"))
470
470
  )
471
471
 
472
- def _binding_reads(self, name: str, binding: ast.AST) -> list[ast.Name]:
472
+ def binding_reads(self, name: str, binding: ast.AST) -> list[ast.Name]:
473
473
  return self._indexed_reads(name, binding, cache=self._reads_by_binding, resolve=self.binding)
474
474
 
475
475
  def _indexed_reads(
@@ -496,7 +496,7 @@ class ResourceProvenance:
496
496
  if key not in self.escape_cache:
497
497
  self.escape_cache[key] = all(
498
498
  self._method_or_context_read(read, allow_context=allow_context)
499
- for read in self._binding_reads(name.id, binding)
499
+ for read in self.binding_reads(name.id, binding)
500
500
  )
501
501
  return self.escape_cache[key]
502
502
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import ast
4
- from collections import defaultdict
5
4
  from pathlib import PurePosixPath
6
5
  import re
7
6
  from typing import TYPE_CHECKING, ClassVar, final, override
@@ -24,7 +23,7 @@ from sarj_python_lint.rules._suppression_comments import comment_lines
24
23
 
25
24
 
26
25
  if TYPE_CHECKING:
27
- from collections.abc import Mapping
26
+ from collections.abc import Iterator, Mapping
28
27
 
29
28
  from sarj_python_lint._file_context import PythonFileContext
30
29
  from sarj_python_lint.rules._resource_provenance import ResourceProvenance
@@ -32,7 +31,7 @@ if TYPE_CHECKING:
32
31
 
33
32
  _FUNCTIONS = (ast.FunctionDef, ast.AsyncFunctionDef)
34
33
  _SUITES = (*_FUNCTIONS, ast.With, ast.AsyncWith)
35
- _UNCERTAIN = (
34
+ _NESTED_SCOPES = (
36
35
  *_FUNCTIONS,
37
36
  ast.ClassDef,
38
37
  ast.Lambda,
@@ -40,6 +39,9 @@ _UNCERTAIN = (
40
39
  ast.SetComp,
41
40
  ast.DictComp,
42
41
  ast.GeneratorExp,
42
+ )
43
+ _UNCERTAIN = (
44
+ *_NESTED_SCOPES,
43
45
  ast.If,
44
46
  ast.For,
45
47
  ast.AsyncFor,
@@ -89,7 +91,8 @@ class NoRedundantLocalAlias(Rule):
89
91
  limitations=(
90
92
  "Only unannotated, single-target `alias = original` assignments with one binding and one read at `alias=alias` are checked.",
91
93
  "The original must be a parameter or an earlier initialized local with one binding; reassignment, deletion, imports, alias chains and uncertain initialization are excluded.",
92
- "Functions with if statements, loops, try statements, asserts, Boolean short-circuiting, nested scopes, global/nonlocal declarations or type aliases are excluded. Ordinary and async with bodies require ordered enclosing-suite binding evidence.",
94
+ "Completed function-body if prefixes and their Boolean tests are supported only before a fresh, uniquely initialized local and alias in that same body. Parameters, earlier source bindings and aliases nested in with bodies after such prefixes are excluded.",
95
+ "Loops, try statements, asserts, other Boolean short-circuiting, nested scopes, global/nonlocal declarations and type aliases are excluded. Ordinary and async with bodies require ordered enclosing-suite binding evidence.",
93
96
  "A conditional expression is admitted only when its test is a bare name bound once as a parameter, and neither local is read within the expression.",
94
97
  "Typed aliases, files with type comments, same-scope quoted references and conditional reads of either local are excluded. Captured bindings and module/class aliases are preserved.",
95
98
  "Files with wildcard imports, Pydantic imports or references to known namespace readers, dynamic attribute getters or reference-count observers are excluded. Wildcard exports cannot establish builtin identity; computed type names can consume caller-local bindings without a Name read in the AST.",
@@ -145,6 +148,36 @@ class NoRedundantLocalAlias(Rule):
145
148
  expected_count=0,
146
149
  public=True,
147
150
  ),
151
+ RuleExample(
152
+ example_id="completed-guard-prefix",
153
+ scenario="completed-guard",
154
+ title="Initialize the original after the completed guard",
155
+ outcome=ExampleOutcome.MATCH,
156
+ files=(
157
+ ExampleFile.python(
158
+ "case.py",
159
+ "def render(enabled):\n if not enabled:\n return None\n item = make_item()\n payload = item\n return build(payload=payload)\n",
160
+ ),
161
+ ),
162
+ focus_path=PurePosixPath("case.py"),
163
+ expected_count=1,
164
+ public=True,
165
+ ),
166
+ RuleExample(
167
+ example_id="direct-after-guard",
168
+ scenario="completed-guard",
169
+ title="Pass the fresh original directly",
170
+ outcome=ExampleOutcome.NO_MATCH,
171
+ files=(
172
+ ExampleFile.python(
173
+ "case.py",
174
+ "def render(enabled):\n if not enabled:\n return None\n item = make_item()\n return build(payload=item)\n",
175
+ ),
176
+ ),
177
+ focus_path=PurePosixPath("case.py"),
178
+ expected_count=0,
179
+ public=True,
180
+ ),
148
181
  ),
149
182
  )
150
183
  description = documentation.summary
@@ -159,7 +192,6 @@ class NoRedundantLocalAlias(Rule):
159
192
  return []
160
193
  provenance = context.resource_provenance
161
194
  names = {node.targets[0].id for node in candidates if isinstance(node.targets[0], ast.Name)}
162
- reads = _scope_reads(provenance, names)
163
195
  conditional_sources = _conditional_sources(context)
164
196
  quoted = _quoted_references(context, provenance, names)
165
197
  positions = {statement: i for owner in context.nodes(*_SUITES) for i, statement in enumerate(owner.body)}
@@ -172,7 +204,7 @@ class NoRedundantLocalAlias(Rule):
172
204
  continue
173
205
  if (provenance.scope(assignment), target.id) in quoted:
174
206
  continue
175
- use = _single_keyword_read(target, provenance, reads, keywords)
207
+ use = _single_keyword_read(target, provenance, keywords)
176
208
  if use is None:
177
209
  continue
178
210
  if not _dominates(assignment, use, context.parents, positions):
@@ -237,12 +269,13 @@ def _local_assignments(context: PythonFileContext, names: set[str]) -> list[ast.
237
269
  if not candidates or context.has_wildcard_import:
238
270
  return []
239
271
  provenance = context.resource_provenance
240
- blocked = {provenance.scope(node) for node in context.nodes(*_UNCERTAIN)}
241
- blocked.update(
242
- provenance.scope(node) for node in context.nodes(ast.IfExp) if not _parameter_choice(node, provenance)
243
- )
272
+ boundaries = _scope_boundaries(context, provenance)
244
273
  eligible = [
245
- node for node in candidates if isinstance(scope := provenance.scope(node), _FUNCTIONS) and scope not in blocked
274
+ node
275
+ for node in candidates
276
+ if isinstance(scope := provenance.scope(node), _FUNCTIONS)
277
+ and (boundary := boundaries.get(scope, 0)) is not None
278
+ and _after_prefix(node, boundary, provenance)
246
279
  ]
247
280
  if not eligible or context.dynamic_namespace or _observes_references(context):
248
281
  return []
@@ -251,6 +284,63 @@ def _local_assignments(context: PythonFileContext, names: set[str]) -> list[ast.
251
284
  return eligible
252
285
 
253
286
 
287
+ def _scope_boundaries(context: PythonFileContext, provenance: ResourceProvenance) -> dict[ast.AST | None, int | None]:
288
+ prefixes = _prefix_bounds(context)
289
+ boundaries: dict[ast.AST | None, int | None] = {}
290
+ for node in context.nodes(*_UNCERTAIN):
291
+ scope = provenance.scope(node)
292
+ prefix = prefixes.get(node)
293
+ if prefix is None or context.parents.get(prefix) is not scope:
294
+ boundaries[scope] = None
295
+ elif (boundary := boundaries.get(scope, 0)) is not None:
296
+ boundaries[scope] = max(boundary, prefix.end_lineno or prefix.lineno)
297
+ for node in context.nodes(ast.IfExp):
298
+ if not _parameter_choice(node, provenance):
299
+ boundaries[provenance.scope(node)] = None
300
+ return boundaries
301
+
302
+
303
+ def _prefix_bounds(context: PythonFileContext) -> dict[ast.AST, ast.If]:
304
+ bounds: dict[ast.AST, ast.If] = {}
305
+ for function in context.nodes(*_FUNCTIONS):
306
+ for root in function.body:
307
+ if isinstance(root, ast.If):
308
+ for node in _prefix_conditions(root):
309
+ bounds[node] = root
310
+ return bounds
311
+
312
+
313
+ def _prefix_conditions(root: ast.If) -> Iterator[ast.AST]:
314
+ pending: list[tuple[ast.AST, bool]] = [(root, False)]
315
+ while pending:
316
+ node, inside_test = pending.pop()
317
+ if isinstance(node, _NESTED_SCOPES):
318
+ continue
319
+ if isinstance(node, ast.If) or (isinstance(node, ast.BoolOp) and inside_test):
320
+ yield node
321
+ pending.extend(
322
+ (child, inside_test or (isinstance(node, ast.If) and child is node.test)) for child in children(node)
323
+ )
324
+
325
+
326
+ def _after_prefix(assignment: ast.Assign, boundary: int, provenance: ResourceProvenance) -> bool:
327
+ if not boundary:
328
+ return True
329
+ if not isinstance(assignment.value, ast.Name):
330
+ return False
331
+ scope = provenance.scope(assignment)
332
+ bindings = provenance.bindings.get(scope, {}).get(assignment.value.id, ())
333
+ if len(bindings) != 1 or not isinstance(bindings[0], ast.Name):
334
+ return False
335
+ initializer = provenance.parents.get(bindings[0])
336
+ return (
337
+ isinstance(initializer, ast.Assign | ast.AnnAssign)
338
+ and provenance.parents.get(initializer) is scope
339
+ and provenance.parents.get(assignment) is scope
340
+ and initializer.lineno > boundary
341
+ )
342
+
343
+
254
344
  def _compiles(context: PythonFileContext) -> bool:
255
345
  if context.tree is None:
256
346
  return False
@@ -272,14 +362,6 @@ def _parameter_choice(node: ast.IfExp, provenance: ResourceProvenance) -> bool:
272
362
  return len(bindings) == 1 and isinstance(bindings[0], ast.arg)
273
363
 
274
364
 
275
- def _scope_reads(provenance: ResourceProvenance, names: set[str]) -> dict[tuple[ast.AST | None, str], list[ast.Name]]:
276
- reads: dict[tuple[ast.AST | None, str], list[ast.Name]] = defaultdict(list)
277
- for name in names:
278
- for read in provenance.reads.get(name, ()):
279
- reads[provenance.scope(read), name].append(read)
280
- return reads
281
-
282
-
283
365
  def _conditional_sources(context: PythonFileContext) -> set[tuple[ast.AST | None, str]]:
284
366
  provenance = context.resource_provenance
285
367
  pending: list[ast.AST] = list(context.nodes(ast.IfExp))
@@ -299,12 +381,13 @@ def _conditional_sources(context: PythonFileContext) -> set[tuple[ast.AST | None
299
381
  def _single_keyword_read(
300
382
  target: ast.Name,
301
383
  provenance: ResourceProvenance,
302
- reads: Mapping[tuple[ast.AST | None, str], list[ast.Name]],
303
384
  keywords: Mapping[ast.Name, str],
304
385
  ) -> ast.Name | None:
305
386
  scope = provenance.scope(target)
306
- uses = reads.get((scope, target.id), ())
307
- if provenance.bindings.get(scope, {}).get(target.id) != [target] or len(uses) != 1:
387
+ if provenance.bindings.get(scope, {}).get(target.id) != [target]:
388
+ return None
389
+ uses = provenance.binding_reads(target.id, target)
390
+ if len(uses) != 1:
308
391
  return None
309
392
  [use] = uses
310
393
  if keywords.get(use) != target.id or _statement(use, provenance.parents) is None: