sarj-python-lint 1.42.2__tar.gz → 1.43.0__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.
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_registry.py +6 -0
- sarj_python_lint-1.43.0/src/sarj_python_lint/rules/_type_guard_symbols.py +112 -0
- sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_contradictory_type_guard.py +198 -0
- sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +501 -0
- sarj_python_lint-1.43.0/src/sarj_python_lint/rules/no_partial_type_is.py +270 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +4 -78
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +16 -4
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/.gitignore +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/LICENSE +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/README.md +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.42.2 → sarj_python_lint-1.43.0}/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.
|
|
3
|
+
Version: 1.43.0
|
|
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/
|
|
@@ -214,7 +214,10 @@ if TYPE_CHECKING:
|
|
|
214
214
|
from sarj_python_lint.rules.duplicate_adjacent_exception_handler import DuplicateAdjacentExceptionHandler
|
|
215
215
|
from sarj_python_lint.rules.duplicate_closed_pure_helper import DuplicateClosedPureHelper
|
|
216
216
|
from sarj_python_lint.rules.no_conflicting_pydantic_alias_validation import NoConflictingPydanticAliasValidation
|
|
217
|
+
from sarj_python_lint.rules.no_contradictory_type_guard import NoContradictoryTypeGuard
|
|
217
218
|
from sarj_python_lint.rules.no_impossible_pydantic_field_bounds import NoImpossiblePydanticFieldBounds
|
|
219
|
+
from sarj_python_lint.rules.no_invalid_pydantic_bypass import NoInvalidPydanticBypass
|
|
220
|
+
from sarj_python_lint.rules.no_partial_type_is import NoPartialTypeIs
|
|
218
221
|
from sarj_python_lint.rules.no_unknown_pydantic_validator_field import NoUnknownPydanticValidatorField
|
|
219
222
|
from sarj_python_lint.rules.prefer_discriminated_model_union import PreferDiscriminatedModelUnion
|
|
220
223
|
from sarj_python_lint.rules.redundant_test_constructor_forwarder import RedundantTestConstructorForwarder
|
|
@@ -226,6 +229,9 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
226
229
|
{
|
|
227
230
|
NoExplicitTestOracle.id: NoExplicitTestOracle,
|
|
228
231
|
PreferDiscriminatedModelUnion.id: PreferDiscriminatedModelUnion,
|
|
232
|
+
NoInvalidPydanticBypass.id: NoInvalidPydanticBypass,
|
|
233
|
+
NoPartialTypeIs.id: NoPartialTypeIs,
|
|
234
|
+
NoContradictoryTypeGuard.id: NoContradictoryTypeGuard,
|
|
229
235
|
NoConflictingPydanticAliasValidation.id: NoConflictingPydanticAliasValidation,
|
|
230
236
|
NoUnknownPydanticValidatorField.id: NoUnknownPydanticValidatorField,
|
|
231
237
|
NoImpossiblePydanticFieldBounds.id: NoImpossiblePydanticFieldBounds,
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from sarj_python_lint.rules._imports import TYPING_SOURCES
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
11
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def builtin_guard_symbol(node: ast.expr | None, imports: ImportIndex, builtin_symbols: frozenset[str]) -> str | None:
|
|
15
|
+
if node is None:
|
|
16
|
+
return None
|
|
17
|
+
symbol = imports.resolved_symbol(node, sources=frozenset({"builtins"}))
|
|
18
|
+
if symbol in builtin_symbols:
|
|
19
|
+
return symbol
|
|
20
|
+
if isinstance(node, ast.Name) and node.id in builtin_symbols and imports.builtin_is_unshadowed(node.id):
|
|
21
|
+
return node.id
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def guard_symbols_ambiguous(context: PythonFileContext, builtin_symbols: frozenset[str]) -> bool:
|
|
26
|
+
if _namespace_escapes(context):
|
|
27
|
+
return True
|
|
28
|
+
mutation_targets = {f"builtins.{symbol}" for symbol in builtin_symbols} | {
|
|
29
|
+
f"{module}.{symbol}" for module in TYPING_SOURCES for symbol in ("TypeGuard", "TypeIs")
|
|
30
|
+
}
|
|
31
|
+
sensitive_names = builtin_symbols | {
|
|
32
|
+
name
|
|
33
|
+
for name, target in context.module_imports.bindings.items()
|
|
34
|
+
if target.module in TYPING_SOURCES | {"builtins"}
|
|
35
|
+
and (target.symbol is None or target.symbol in builtin_symbols | {"TypeGuard", "TypeIs"})
|
|
36
|
+
}
|
|
37
|
+
return any(
|
|
38
|
+
_ambiguous_node(node, context, mutation_targets, sensitive_names)
|
|
39
|
+
for node in context.nodes(
|
|
40
|
+
ast.Import,
|
|
41
|
+
ast.ImportFrom,
|
|
42
|
+
ast.Attribute,
|
|
43
|
+
ast.ExceptHandler,
|
|
44
|
+
ast.MatchAs,
|
|
45
|
+
ast.MatchStar,
|
|
46
|
+
ast.MatchMapping,
|
|
47
|
+
ast.NamedExpr,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _namespace_escapes(context: PythonFileContext) -> bool:
|
|
53
|
+
if context.dynamic_namespace or any(node.id == "__builtins__" for node in context.nodes(ast.Name)):
|
|
54
|
+
return True
|
|
55
|
+
namespace_names = {
|
|
56
|
+
name
|
|
57
|
+
for name, target in context.module_imports.bindings.items()
|
|
58
|
+
if target.symbol is None and target.module in TYPING_SOURCES | {"builtins"}
|
|
59
|
+
}
|
|
60
|
+
for name in context.nodes(ast.Name):
|
|
61
|
+
if name.id not in namespace_names:
|
|
62
|
+
continue
|
|
63
|
+
parent = context.parents.get(name)
|
|
64
|
+
if not isinstance(parent, ast.Attribute) or parent.value is not name or parent.attr == "__dict__":
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _ambiguous_node(
|
|
70
|
+
node: ast.AST, context: PythonFileContext, mutation_targets: set[str], sensitive_names: frozenset[str]
|
|
71
|
+
) -> bool:
|
|
72
|
+
match node:
|
|
73
|
+
case ast.Import() | ast.ImportFrom():
|
|
74
|
+
return _ambiguous_import(node, context.module_imports, sensitive_names)
|
|
75
|
+
case ast.Attribute(ctx=(ast.Store() | ast.Del())):
|
|
76
|
+
return context.module_imports.resolved_qualified_name(node) in mutation_targets
|
|
77
|
+
case (
|
|
78
|
+
ast.ExceptHandler(name=name)
|
|
79
|
+
| ast.MatchAs(name=name)
|
|
80
|
+
| ast.MatchStar(name=name)
|
|
81
|
+
| ast.MatchMapping(rest=name)
|
|
82
|
+
| ast.NamedExpr(target=ast.Name(id=name))
|
|
83
|
+
):
|
|
84
|
+
return name in sensitive_names
|
|
85
|
+
case _:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _ambiguous_import(node: ast.Import | ast.ImportFrom, imports: ImportIndex, sensitive_names: frozenset[str]) -> bool:
|
|
90
|
+
if isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names):
|
|
91
|
+
return True
|
|
92
|
+
for alias in node.names:
|
|
93
|
+
name = alias.asname or alias.name.partition(".")[0]
|
|
94
|
+
if name not in sensitive_names:
|
|
95
|
+
continue
|
|
96
|
+
if not _preserves_canonical_import(node, alias, imports):
|
|
97
|
+
return True
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _preserves_canonical_import(node: ast.Import | ast.ImportFrom, alias: ast.alias, imports: ImportIndex) -> bool:
|
|
102
|
+
if isinstance(node, ast.ImportFrom):
|
|
103
|
+
if node.level:
|
|
104
|
+
return False
|
|
105
|
+
module, symbol = node.module, alias.name
|
|
106
|
+
else:
|
|
107
|
+
module, symbol = alias.name, None
|
|
108
|
+
name = alias.asname or alias.name.partition(".")[0]
|
|
109
|
+
target = imports.bindings.get(name)
|
|
110
|
+
if target is None:
|
|
111
|
+
return module == "builtins" and symbol == name
|
|
112
|
+
return target.module == module and target.symbol == symbol
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar, Final, final, override
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import (
|
|
8
|
+
AutofixPolicy,
|
|
9
|
+
Diagnostic,
|
|
10
|
+
ExampleFile,
|
|
11
|
+
ExampleOutcome,
|
|
12
|
+
Rule,
|
|
13
|
+
RuleCategory,
|
|
14
|
+
RuleDocumentation,
|
|
15
|
+
RuleExample,
|
|
16
|
+
Severity,
|
|
17
|
+
is_suppressed,
|
|
18
|
+
)
|
|
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
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
25
|
+
from sarj_python_lint.rules._imports import ImportIndex
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_SCALARS = frozenset({"int", "bool", "str", "bytes"})
|
|
29
|
+
_BUILTINS = _SCALARS | {"object", "isinstance"}
|
|
30
|
+
_WITNESSES: Final = {
|
|
31
|
+
"int": frozenset({"int", "bool"}),
|
|
32
|
+
"bool": frozenset({"bool"}),
|
|
33
|
+
"str": frozenset({"str"}),
|
|
34
|
+
"bytes": frozenset({"bytes"}),
|
|
35
|
+
}
|
|
36
|
+
_ALL_WITNESSES = frozenset({"int", "bool", "str", "bytes"})
|
|
37
|
+
_MAX_INPUT_NODES = 4096
|
|
38
|
+
_BAD = (
|
|
39
|
+
"from typing import TypeGuard\ndef is_text(value: object) -> TypeGuard[str]:\n return isinstance(value, int)\n"
|
|
40
|
+
)
|
|
41
|
+
_GOOD = _BAD.replace("isinstance(value, int)", "isinstance(value, str)")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@final
|
|
45
|
+
class NoContradictoryTypeGuard(Rule):
|
|
46
|
+
id = "no-contradictory-type-guard"
|
|
47
|
+
code = "SARJ513"
|
|
48
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
49
|
+
default_level=Severity.WARNING,
|
|
50
|
+
summary="Primitive type guards must agree with the isinstance check they return.",
|
|
51
|
+
rationale=(
|
|
52
|
+
"A guard annotation does not validate its implementation. Returning an incompatible isinstance "
|
|
53
|
+
"check makes callers trust the wrong primitive; TypeIs also incorrectly excludes rejected target "
|
|
54
|
+
"values in the false branch."
|
|
55
|
+
),
|
|
56
|
+
remediation=(
|
|
57
|
+
"Align the declared target and runtime test. For an intentional partial predicate, TypeGuard may "
|
|
58
|
+
"narrow only the true branch; TypeIs must recognize every target value admitted by its input. "
|
|
59
|
+
"Review callers before changing the narrowing contract."
|
|
60
|
+
),
|
|
61
|
+
category=RuleCategory.CORRECTNESS,
|
|
62
|
+
autofix=AutofixPolicy.NONE,
|
|
63
|
+
limitations=(
|
|
64
|
+
"Checks undecorated, non-generic module functions with one parameter and a single returned direct builtin isinstance call, optionally after a docstring.",
|
|
65
|
+
"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.",
|
|
67
|
+
"Input unions are traversed iteratively with a bounded node budget; overly complex annotations are excluded.",
|
|
68
|
+
"Literal and refined targets, aliases, custom classes, float numeric promotion, tuple isinstance tests, helper predicates, nested functions and complex bodies are not inferred.",
|
|
69
|
+
"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.",
|
|
70
|
+
"Generated files are excluded; maintained tests remain checked. No autofix guesses a domain or changes false-branch behavior.",
|
|
71
|
+
),
|
|
72
|
+
examples=(
|
|
73
|
+
RuleExample(
|
|
74
|
+
example_id="wrong-primitive-promise",
|
|
75
|
+
title="An integer test does not prove a string",
|
|
76
|
+
outcome=ExampleOutcome.MATCH,
|
|
77
|
+
files=(ExampleFile.python("app/guards.py", _BAD),),
|
|
78
|
+
focus_path=PurePosixPath("app/guards.py"),
|
|
79
|
+
expected_count=1,
|
|
80
|
+
public=True,
|
|
81
|
+
),
|
|
82
|
+
RuleExample(
|
|
83
|
+
example_id="consistent-primitive-promise",
|
|
84
|
+
title="Recognize the primitive promised to callers",
|
|
85
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
86
|
+
files=(ExampleFile.python("app/guards.py", _GOOD),),
|
|
87
|
+
focus_path=PurePosixPath("app/guards.py"),
|
|
88
|
+
expected_count=0,
|
|
89
|
+
public=True,
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
description = documentation.summary
|
|
94
|
+
|
|
95
|
+
@override
|
|
96
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
97
|
+
if context.generated or not any(symbol in context.symbol_source for symbol in ("TypeGuard", "TypeIs")):
|
|
98
|
+
return []
|
|
99
|
+
if context.tree is None or guard_symbols_ambiguous(context, _BUILTINS):
|
|
100
|
+
return []
|
|
101
|
+
findings: list[Diagnostic] = []
|
|
102
|
+
for function in context.tree.body:
|
|
103
|
+
if not isinstance(function, ast.FunctionDef):
|
|
104
|
+
continue
|
|
105
|
+
statement = _contradictory_return(function, context)
|
|
106
|
+
if statement is None or is_suppressed(context.source_lines, statement.lineno, self.code):
|
|
107
|
+
continue
|
|
108
|
+
findings.append(
|
|
109
|
+
Diagnostic(
|
|
110
|
+
context.path,
|
|
111
|
+
statement.lineno,
|
|
112
|
+
statement.col_offset + 1,
|
|
113
|
+
self.code,
|
|
114
|
+
"This guard's isinstance test contradicts its primitive narrowing promise; align the target "
|
|
115
|
+
"and test, preserving TypeIs false-branch behavior and intentional TypeGuard partial predicates.",
|
|
116
|
+
severity=Severity.WARNING,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
return findings
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _contradictory_return(function: ast.FunctionDef, context: PythonFileContext) -> ast.Return | None:
|
|
123
|
+
parameter = _sole_parameter(function)
|
|
124
|
+
annotation = function.returns
|
|
125
|
+
if parameter is None or not isinstance(annotation, ast.Subscript):
|
|
126
|
+
return None
|
|
127
|
+
guard = context.module_imports.resolved_symbol(annotation.value, sources=TYPING_SOURCES)
|
|
128
|
+
if guard not in {"TypeGuard", "TypeIs"}:
|
|
129
|
+
return None
|
|
130
|
+
target_kind = builtin_guard_symbol(annotation.slice, context.module_imports, _BUILTINS)
|
|
131
|
+
target = _WITNESSES.get(target_kind) if target_kind is not None else None
|
|
132
|
+
inputs = _input_witnesses(parameter.annotation, context.module_imports)
|
|
133
|
+
if target is None or inputs is None or (guard == "TypeIs" and not target <= inputs):
|
|
134
|
+
return None
|
|
135
|
+
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:
|
|
139
|
+
return None
|
|
140
|
+
accepts_wrong = bool((inputs & checked) - target)
|
|
141
|
+
rejects_target = guard == "TypeIs" and bool((inputs & target) - checked)
|
|
142
|
+
return statement if accepts_wrong or rejects_target else None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _sole_parameter(function: ast.FunctionDef) -> ast.arg | None:
|
|
146
|
+
args = function.args
|
|
147
|
+
positional = [*args.posonlyargs, *args.args]
|
|
148
|
+
if function.decorator_list or function.type_params or len(positional) != 1:
|
|
149
|
+
return None
|
|
150
|
+
if args.kwonlyargs or args.vararg is not None or args.kwarg is not None:
|
|
151
|
+
return None
|
|
152
|
+
return positional[0]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _single_return(function: ast.FunctionDef) -> ast.Return | None:
|
|
156
|
+
body = function.body
|
|
157
|
+
if (
|
|
158
|
+
body
|
|
159
|
+
and isinstance(body[0], ast.Expr)
|
|
160
|
+
and isinstance(body[0].value, ast.Constant)
|
|
161
|
+
and isinstance(body[0].value.value, str)
|
|
162
|
+
):
|
|
163
|
+
body = body[1:]
|
|
164
|
+
return body[0] if len(body) == 1 and isinstance(body[0], ast.Return) else None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _input_witnesses(annotation: ast.expr | None, imports: ImportIndex) -> frozenset[str] | None:
|
|
168
|
+
pending = [annotation]
|
|
169
|
+
visited = 0
|
|
170
|
+
witnesses: set[str] = set()
|
|
171
|
+
while pending:
|
|
172
|
+
member = pending.pop()
|
|
173
|
+
visited += 1
|
|
174
|
+
if visited > _MAX_INPUT_NODES:
|
|
175
|
+
return None
|
|
176
|
+
if isinstance(member, ast.Constant) and member.value is None:
|
|
177
|
+
continue
|
|
178
|
+
if isinstance(member, ast.BinOp) and isinstance(member.op, ast.BitOr):
|
|
179
|
+
pending.extend((member.left, member.right))
|
|
180
|
+
continue
|
|
181
|
+
kind = builtin_guard_symbol(member, imports, _BUILTINS)
|
|
182
|
+
if kind == "object":
|
|
183
|
+
witnesses.update(_ALL_WITNESSES)
|
|
184
|
+
elif kind in _WITNESSES:
|
|
185
|
+
witnesses.update(_WITNESSES[kind])
|
|
186
|
+
else:
|
|
187
|
+
return None
|
|
188
|
+
return frozenset(witnesses)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _checked_kind(node: ast.expr | None, parameter: str, context: PythonFileContext) -> str | None:
|
|
192
|
+
match node:
|
|
193
|
+
case ast.Call(func=func, args=[ast.Name(id=name), target], keywords=[]) if name == parameter:
|
|
194
|
+
if builtin_guard_symbol(func, context.imports, _BUILTINS) == "isinstance":
|
|
195
|
+
return builtin_guard_symbol(target, context.imports, _BUILTINS)
|
|
196
|
+
case _:
|
|
197
|
+
pass
|
|
198
|
+
return None
|