sarj-python-lint 1.43.1__tar.gz → 1.44.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 (212) hide show
  1. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/PKG-INFO +1 -1
  2. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/pyproject.toml +1 -1
  3. sarj_python_lint-1.44.1/src/sarj_python_lint/rules/_type_guard_symbols.py +263 -0
  4. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +64 -13
  5. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_partial_type_is.py +152 -20
  6. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +18 -4
  7. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +119 -28
  8. sarj_python_lint-1.43.1/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -112
  9. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/.gitignore +0 -0
  10. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/LICENSE +0 -0
  11. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/README.md +0 -0
  12. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/__init__.py +0 -0
  13. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/__main__.py +0 -0
  14. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_analysis_session.py +0 -0
  15. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_file_context.py +0 -0
  16. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_filesystem.py +0 -0
  17. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_python_target.py +0 -0
  18. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  19. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_secret_names.py +0 -0
  20. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_source.py +0 -0
  21. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/_version.py +0 -0
  22. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/benchmark.py +0 -0
  23. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
  24. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/json_boundary.py +0 -0
  25. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/py.typed +0 -0
  26. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/ratchet.py +0 -0
  27. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rule_base.py +0 -0
  28. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/__init__.py +0 -0
  29. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
  30. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  31. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
  32. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
  33. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_comments.py +0 -0
  34. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
  35. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
  36. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
  37. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  38. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_exports.py +0 -0
  39. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  40. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
  41. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
  42. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_imports.py +0 -0
  43. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
  44. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
  45. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_logging.py +0 -0
  46. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
  47. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
  48. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_paths.py +0 -0
  49. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
  50. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  51. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
  52. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
  53. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_registry.py +0 -0
  54. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
  55. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
  56. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
  57. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_sql.py +0 -0
  58. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
  59. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  60. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  61. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
  62. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
  63. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
  64. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
  65. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
  66. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
  67. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  68. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  69. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
  70. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
  71. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
  72. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
  73. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
  74. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
  75. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  76. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  77. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  78. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
  79. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
  80. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
  81. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
  82. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
  83. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
  84. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
  85. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  86. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
  87. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  88. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
  89. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  90. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
  91. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
  92. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
  93. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  94. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
  95. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  96. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
  97. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  98. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
  99. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
  100. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  101. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  102. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
  103. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
  104. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
  105. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
  106. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
  107. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
  108. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
  109. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
  110. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
  111. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
  112. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  113. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
  114. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  115. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
  116. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
  117. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
  118. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
  119. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
  120. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  121. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
  122. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  123. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
  124. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  125. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  126. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  127. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
  128. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  129. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
  130. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  131. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
  132. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  133. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
  134. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
  135. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
  136. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
  137. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
  138. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
  139. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
  140. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
  141. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  142. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  143. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  144. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
  145. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  146. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
  147. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  148. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
  149. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
  150. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  151. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
  152. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
  153. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  154. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  155. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
  156. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
  157. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
  158. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  159. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
  160. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
  161. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  162. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
  163. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  164. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
  165. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  166. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
  167. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
  168. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
  169. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
  170. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  171. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  172. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
  173. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  174. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  175. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
  176. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  177. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  178. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  179. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
  180. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  181. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  182. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  183. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
  184. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
  185. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
  186. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
  187. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
  188. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
  189. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
  190. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
  191. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
  192. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  193. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  194. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  195. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
  196. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
  197. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
  198. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
  199. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
  200. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  201. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
  202. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
  203. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
  204. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  205. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
  206. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
  207. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
  208. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  209. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
  210. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  211. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
  212. {sarj_python_lint-1.43.1 → sarj_python_lint-1.44.1}/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: 1.43.1
3
+ Version: 1.44.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.43.1"
3
+ version = "1.44.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" }]
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from collections import Counter
5
+ from typing import TYPE_CHECKING
6
+
7
+ from sarj_python_lint.rules._imports import TYPING_SOURCES
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from sarj_python_lint._file_context import PythonFileContext
12
+ from sarj_python_lint.rules._imports import ImportIndex
13
+
14
+
15
+ _NAMESPACE_IMPORT_CAPABILITIES = frozenset(
16
+ {"__dict__", "__builtins__", "__getattribute__", "__setattr__", "__delattr__"}
17
+ )
18
+ _NAMESPACE_RETURNING_FUNCTIONS = frozenset({"__lazy_import__"})
19
+ _BUILTIN_NAMESPACE_FUNCTIONS = frozenset(
20
+ {
21
+ "__build_class__",
22
+ "__import__",
23
+ "__lazy_import__",
24
+ "abs",
25
+ "aiter",
26
+ "all",
27
+ "anext",
28
+ "any",
29
+ "ascii",
30
+ "bin",
31
+ "breakpoint",
32
+ "callable",
33
+ "chr",
34
+ "compile",
35
+ "delattr",
36
+ "dir",
37
+ "divmod",
38
+ "eval",
39
+ "exec",
40
+ "format",
41
+ "getattr",
42
+ "globals",
43
+ "hasattr",
44
+ "hash",
45
+ "hex",
46
+ "id",
47
+ "input",
48
+ "isinstance",
49
+ "issubclass",
50
+ "iter",
51
+ "len",
52
+ "locals",
53
+ "max",
54
+ "min",
55
+ "next",
56
+ "oct",
57
+ "ord",
58
+ "pow",
59
+ "print",
60
+ "repr",
61
+ "round",
62
+ "setattr",
63
+ "sorted",
64
+ "sum",
65
+ "vars",
66
+ }
67
+ )
68
+
69
+
70
+ def builtin_guard_symbol(node: ast.expr | None, imports: ImportIndex, builtin_symbols: frozenset[str]) -> str | None:
71
+ if node is None:
72
+ return None
73
+ symbol = imports.resolved_symbol(node, sources=frozenset({"builtins"}))
74
+ if symbol in builtin_symbols:
75
+ return symbol
76
+ if isinstance(node, ast.Name) and node.id in builtin_symbols and imports.builtin_is_unshadowed(node.id):
77
+ return node.id
78
+ return None
79
+
80
+
81
+ def unique_guard_function_names(context: PythonFileContext) -> frozenset[str]:
82
+ if context.tree is None:
83
+ return frozenset()
84
+ counts: Counter[str] = Counter()
85
+ pending: list[ast.AST] = list(context.tree.body)
86
+ while pending:
87
+ node = pending.pop()
88
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
89
+ counts[node.name] += 1
90
+ elif not isinstance(node, (ast.ClassDef, ast.Lambda)):
91
+ pending.extend(ast.iter_child_nodes(node))
92
+ return frozenset(name for name, count in counts.items() if count == 1)
93
+
94
+
95
+ def guard_symbols_ambiguous(
96
+ context: PythonFileContext,
97
+ builtin_symbols: frozenset[str],
98
+ *,
99
+ typing_symbols: frozenset[str] = frozenset({"TypeGuard", "TypeIs"}),
100
+ ) -> bool:
101
+ namespace_names = _namespace_import_names(context)
102
+ if _namespace_escapes(context, namespace_names):
103
+ return True
104
+ mutation_targets = (
105
+ {f"builtins.{symbol}" for symbol in builtin_symbols}
106
+ | {f"{module}.{symbol}" for module in TYPING_SOURCES for symbol in typing_symbols}
107
+ | {f"{module}.__class__" for module in TYPING_SOURCES | {"builtins"}}
108
+ )
109
+ sensitive_names = (
110
+ builtin_symbols
111
+ | namespace_names
112
+ | {
113
+ name
114
+ for name, target in context.module_imports.bindings.items()
115
+ if target.module in TYPING_SOURCES | {"builtins"}
116
+ and (target.symbol is None or target.symbol in builtin_symbols | typing_symbols)
117
+ }
118
+ )
119
+ return any(
120
+ _ambiguous_node(node, context, mutation_targets, sensitive_names)
121
+ for node in context.nodes(
122
+ ast.Import,
123
+ ast.ImportFrom,
124
+ ast.Attribute,
125
+ ast.ExceptHandler,
126
+ ast.MatchAs,
127
+ ast.MatchStar,
128
+ ast.MatchMapping,
129
+ ast.NamedExpr,
130
+ )
131
+ )
132
+
133
+
134
+ def _namespace_import_names(context: PythonFileContext) -> frozenset[str]:
135
+ return frozenset(
136
+ alias.asname or alias.name
137
+ for node in context.nodes(ast.Import)
138
+ for alias in node.names
139
+ if alias.name in TYPING_SOURCES | {"builtins"}
140
+ )
141
+
142
+
143
+ def _namespace_escapes(context: PythonFileContext, namespace_names: frozenset[str]) -> bool:
144
+ if context.dynamic_namespace or any(node.id == "__builtins__" for node in context.nodes(ast.Name)):
145
+ return True
146
+ if any(
147
+ node.level == 0
148
+ and node.module in TYPING_SOURCES | {"builtins"}
149
+ and any(alias.name in _NAMESPACE_IMPORT_CAPABILITIES for alias in node.names)
150
+ for node in context.nodes(ast.ImportFrom)
151
+ ):
152
+ return True
153
+ if _native_function_escapes(context):
154
+ return True
155
+ for name in context.nodes(ast.Name):
156
+ if name.id not in namespace_names:
157
+ continue
158
+ parent = context.parents.get(name)
159
+ if not isinstance(parent, ast.Attribute) or parent.value is not name or parent.attr == "__dict__":
160
+ return True
161
+ return False
162
+
163
+
164
+ def _native_function_escapes(context: PythonFileContext) -> bool:
165
+ function_names = _possible_native_function_names(context)
166
+ namespace_function_names = _possible_native_function_names(context, symbols=_NAMESPACE_RETURNING_FUNCTIONS)
167
+ module_names = frozenset(
168
+ alias.asname or alias.name
169
+ for node in context.nodes(ast.Import)
170
+ for alias in node.names
171
+ if alias.name == "builtins"
172
+ )
173
+ for node in context.nodes(ast.Name, ast.Attribute):
174
+ if isinstance(node, ast.Attribute) and (
175
+ not isinstance(node.value, ast.Name) or node.attr not in _BUILTIN_NAMESPACE_FUNCTIONS
176
+ ):
177
+ continue
178
+ if not isinstance(node.ctx, ast.Load) or not _possible_native_function(node, function_names, module_names):
179
+ continue
180
+ parent = context.parents.get(node)
181
+ if (
182
+ not isinstance(parent, ast.Call)
183
+ or parent.func is not node
184
+ or _namespace_returning_function(node, namespace_function_names)
185
+ ):
186
+ return True
187
+ return False
188
+
189
+
190
+ def _possible_native_function_names(
191
+ context: PythonFileContext, *, symbols: frozenset[str] = _BUILTIN_NAMESPACE_FUNCTIONS
192
+ ) -> frozenset[str]:
193
+ return symbols | frozenset(
194
+ alias.asname or alias.name
195
+ for node in context.nodes(ast.ImportFrom)
196
+ if node.level == 0 and node.module == "builtins"
197
+ for alias in node.names
198
+ if alias.name in symbols
199
+ )
200
+
201
+
202
+ def _namespace_returning_function(node: ast.Name | ast.Attribute, function_names: frozenset[str]) -> bool:
203
+ if isinstance(node, ast.Name):
204
+ return node.id in function_names
205
+ return node.attr in _NAMESPACE_RETURNING_FUNCTIONS
206
+
207
+
208
+ def _possible_native_function(
209
+ node: ast.Name | ast.Attribute,
210
+ function_names: frozenset[str],
211
+ module_names: frozenset[str],
212
+ ) -> bool:
213
+ if isinstance(node, ast.Name):
214
+ return node.id in function_names
215
+ return (
216
+ isinstance(node.value, ast.Name) and node.value.id in module_names and node.attr in _BUILTIN_NAMESPACE_FUNCTIONS
217
+ )
218
+
219
+
220
+ def _ambiguous_node(
221
+ node: ast.AST, context: PythonFileContext, mutation_targets: set[str], sensitive_names: frozenset[str]
222
+ ) -> bool:
223
+ match node:
224
+ case ast.Import() | ast.ImportFrom():
225
+ return _ambiguous_import(node, context.module_imports, sensitive_names)
226
+ case ast.Attribute(ctx=(ast.Store() | ast.Del())):
227
+ return context.module_imports.resolved_qualified_name(node) in mutation_targets
228
+ case (
229
+ ast.ExceptHandler(name=name)
230
+ | ast.MatchAs(name=name)
231
+ | ast.MatchStar(name=name)
232
+ | ast.MatchMapping(rest=name)
233
+ | ast.NamedExpr(target=ast.Name(id=name))
234
+ ):
235
+ return name in sensitive_names
236
+ case _:
237
+ return False
238
+
239
+
240
+ def _ambiguous_import(node: ast.Import | ast.ImportFrom, imports: ImportIndex, sensitive_names: frozenset[str]) -> bool:
241
+ if isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names):
242
+ return True
243
+ for alias in node.names:
244
+ name = alias.asname or alias.name.partition(".")[0]
245
+ if name not in sensitive_names:
246
+ continue
247
+ if not _preserves_canonical_import(node, alias, imports):
248
+ return True
249
+ return False
250
+
251
+
252
+ def _preserves_canonical_import(node: ast.Import | ast.ImportFrom, alias: ast.alias, imports: ImportIndex) -> bool:
253
+ if isinstance(node, ast.ImportFrom):
254
+ if node.level:
255
+ return False
256
+ module, symbol = node.module, alias.name
257
+ else:
258
+ module, symbol = alias.name, None
259
+ name = alias.asname or alias.name.partition(".")[0]
260
+ target = imports.bindings.get(name)
261
+ if target is None:
262
+ return module == "builtins" and symbol == name
263
+ return target.module == module and target.symbol == symbol
@@ -17,7 +17,11 @@ from sarj_python_lint.rule_base import (
17
17
  is_suppressed,
18
18
  )
19
19
  from sarj_python_lint.rules._imports import TYPING_SOURCES
20
- from sarj_python_lint.rules._type_guard_symbols import builtin_guard_symbol, guard_symbols_ambiguous
20
+ from sarj_python_lint.rules._type_guard_symbols import (
21
+ builtin_guard_symbol,
22
+ guard_symbols_ambiguous,
23
+ unique_guard_function_names,
24
+ )
21
25
 
22
26
 
23
27
  if TYPE_CHECKING:
@@ -33,7 +37,7 @@ _WITNESSES: Final = {
33
37
  "str": frozenset({"str"}),
34
38
  "bytes": frozenset({"bytes"}),
35
39
  }
36
- _ALL_WITNESSES = frozenset({"int", "bool", "str", "bytes"})
40
+ _ALL_WITNESSES = frozenset({"int", "bool", "str", "bytes", "none"})
37
41
  _MAX_INPUT_NODES = 4096
38
42
  _BAD = (
39
43
  "from typing import TypeGuard\ndef is_text(value: object) -> TypeGuard[str]:\n return isinstance(value, int)\n"
@@ -47,9 +51,9 @@ class NoContradictoryTypeGuard(Rule):
47
51
  code = "SARJ513"
48
52
  documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
49
53
  default_level=Severity.WARNING,
50
- summary="Primitive type guards must agree with the isinstance check they return.",
54
+ summary="Primitive type guards must agree with the builtin predicate they return.",
51
55
  rationale=(
52
- "A guard annotation does not validate its implementation. Returning an incompatible isinstance "
56
+ "A guard annotation does not validate its implementation. Returning an incompatible primitive predicate "
53
57
  "check makes callers trust the wrong primitive; TypeIs also incorrectly excludes rejected target "
54
58
  "values in the false branch."
55
59
  ),
@@ -61,12 +65,14 @@ class NoContradictoryTypeGuard(Rule):
61
65
  category=RuleCategory.CORRECTNESS,
62
66
  autofix=AutofixPolicy.NONE,
63
67
  limitations=(
64
- "Checks undecorated, non-generic module functions with one parameter and a single returned direct builtin isinstance call, optionally after a docstring.",
68
+ "Checks undecorated, non-generic module functions with one parameter and a single returned direct builtin isinstance call, its unary not, or a bool constant, optionally after a docstring.",
65
69
  "Guard symbols must resolve to typing or typing_extensions TypeGuard or TypeIs. Targets and checked classes are builtin int, bool, str or bytes; bool is treated as an int subtype.",
66
- "Inputs must be object or fully classified plain unions of those primitive types and None. Impossible isinstance tests and compiler-invalid TypeIs target/input relationships are excluded.",
70
+ "Inputs must be object or fully classified plain unions of those primitive types and None. Direct positive impossible isinstance tests and compiler-invalid TypeIs target/input relationships are excluded.",
67
71
  "Input unions are traversed iteratively with a bounded node budget; overly complex annotations are excluded.",
68
72
  "Literal and refined targets, aliases, custom classes, float numeric promotion, tuple isinstance tests, helper predicates, nested functions and complex bodies are not inferred.",
69
73
  "TypeGuard may intentionally recognize a subset of its target, so TypeGuard[int] with an isinstance bool test remains legal. Visible namespace mutation and ambiguous or shadowed symbols abstain conservatively.",
74
+ "Constant false remains legal for TypeGuard. Constant true is reported only with a proven incompatible primitive or None input; false TypeIs needs a proven inhabited target. Negated isinstance uses the same finite witness domains.",
75
+ "Repeated top-level function definition names abstain because overloads can constrain the public input more narrowly than the implementation input.",
70
76
  "Generated files are excluded; maintained tests remain checked. No autofix guesses a domain or changes false-branch behavior.",
71
77
  ),
72
78
  examples=(
@@ -88,6 +94,36 @@ class NoContradictoryTypeGuard(Rule):
88
94
  expected_count=0,
89
95
  public=True,
90
96
  ),
97
+ RuleExample(
98
+ example_id="constant-true-claims-text",
99
+ scenario="constant-predicate",
100
+ title="A constant result does not prove a primitive type",
101
+ outcome=ExampleOutcome.MATCH,
102
+ files=(
103
+ ExampleFile.python(
104
+ "app/guards.py",
105
+ "from typing import TypeGuard\ndef is_text(value: object) -> TypeGuard[str]:\n return True\n",
106
+ ),
107
+ ),
108
+ focus_path=PurePosixPath("app/guards.py"),
109
+ expected_count=1,
110
+ public=True,
111
+ ),
112
+ RuleExample(
113
+ example_id="constant-false-is-legal-partial-guard",
114
+ scenario="constant-predicate",
115
+ title="TypeGuard can intentionally recognize no inputs",
116
+ outcome=ExampleOutcome.NO_MATCH,
117
+ files=(
118
+ ExampleFile.python(
119
+ "app/guards.py",
120
+ "from typing import TypeGuard\ndef is_text(value: object) -> TypeGuard[str]:\n return False\n",
121
+ ),
122
+ ),
123
+ focus_path=PurePosixPath("app/guards.py"),
124
+ expected_count=0,
125
+ public=True,
126
+ ),
91
127
  ),
92
128
  )
93
129
  description = documentation.summary
@@ -99,8 +135,9 @@ class NoContradictoryTypeGuard(Rule):
99
135
  if context.tree is None or guard_symbols_ambiguous(context, _BUILTINS):
100
136
  return []
101
137
  findings: list[Diagnostic] = []
138
+ unique_names = unique_guard_function_names(context)
102
139
  for function in context.tree.body:
103
- if not isinstance(function, ast.FunctionDef):
140
+ if not isinstance(function, ast.FunctionDef) or function.name not in unique_names:
104
141
  continue
105
142
  statement = _contradictory_return(function, context)
106
143
  if statement is None or is_suppressed(context.source_lines, statement.lineno, self.code):
@@ -111,7 +148,7 @@ class NoContradictoryTypeGuard(Rule):
111
148
  statement.lineno,
112
149
  statement.col_offset + 1,
113
150
  self.code,
114
- "This guard's isinstance test contradicts its primitive narrowing promise; align the target "
151
+ "This guard's builtin predicate contradicts its primitive narrowing promise; align the target "
115
152
  "and test, preserving TypeIs false-branch behavior and intentional TypeGuard partial predicates.",
116
153
  severity=Severity.WARNING,
117
154
  )
@@ -133,12 +170,11 @@ def _contradictory_return(function: ast.FunctionDef, context: PythonFileContext)
133
170
  if target is None or inputs is None or (guard == "TypeIs" and not target <= inputs):
134
171
  return None
135
172
  statement = _single_return(function)
136
- checked_kind = _checked_kind(statement.value, parameter.arg, context) if statement is not None else None
137
- checked = _WITNESSES.get(checked_kind) if checked_kind is not None else None
138
- if checked is None or not inputs & checked:
173
+ accepted = _accepted_witnesses(statement.value, parameter.arg, inputs, context) if statement is not None else None
174
+ if accepted is None:
139
175
  return None
140
- accepts_wrong = bool((inputs & checked) - target)
141
- rejects_target = guard == "TypeIs" and bool((inputs & target) - checked)
176
+ accepts_wrong = bool(accepted - target)
177
+ rejects_target = guard == "TypeIs" and bool((inputs & target) - accepted)
142
178
  return statement if accepts_wrong or rejects_target else None
143
179
 
144
180
 
@@ -174,6 +210,7 @@ def _input_witnesses(annotation: ast.expr | None, imports: ImportIndex) -> froze
174
210
  if visited > _MAX_INPUT_NODES:
175
211
  return None
176
212
  if isinstance(member, ast.Constant) and member.value is None:
213
+ witnesses.add("none")
177
214
  continue
178
215
  if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
179
216
  pending.extend((member.left, member.right))
@@ -188,6 +225,20 @@ def _input_witnesses(annotation: ast.expr | None, imports: ImportIndex) -> froze
188
225
  return frozenset(witnesses)
189
226
 
190
227
 
228
+ def _accepted_witnesses(
229
+ node: ast.expr | None, parameter: str, inputs: frozenset[str], context: PythonFileContext
230
+ ) -> frozenset[str] | None:
231
+ if isinstance(node, ast.Constant) and isinstance(node.value, bool):
232
+ return inputs if node.value else frozenset()
233
+ negate = isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not)
234
+ expression = node.operand if negate and isinstance(node, ast.UnaryOp) else node
235
+ checked_kind = _checked_kind(expression, parameter, context)
236
+ checked = _WITNESSES.get(checked_kind) if checked_kind is not None else None
237
+ if checked is None or (not negate and not inputs & checked):
238
+ return None
239
+ return inputs - checked if negate else inputs & checked
240
+
241
+
191
242
  def _checked_kind(node: ast.expr | None, parameter: str, context: PythonFileContext) -> str | None:
192
243
  match node:
193
244
  case ast.Call(func=func, args=[ast.Name(id=name), target], keywords=[]) if name == parameter: