sarj-python-lint 1.25.3__tar.gz → 1.25.5__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.25.3 → sarj_python_lint-1.25.5}/PKG-INFO +1 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/pyproject.toml +1 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/__main__.py +30 -21
- sarj_python_lint-1.25.5/src/sarj_python_lint/_source.py +14 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/ratchet.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_fastapi.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_local_source.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_nominal_project.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_project_index.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +2 -1
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +3 -2
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_tautological_expect.py +118 -2
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/.gitignore +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/LICENSE +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/README.md +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/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.25.
|
|
3
|
+
Version: 1.25.5
|
|
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/
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from collections import Counter
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from fnmatch import fnmatch
|
|
7
8
|
import json
|
|
@@ -16,6 +17,7 @@ from sarj_python_lint import __version__
|
|
|
16
17
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
17
18
|
from sarj_python_lint._file_context import PythonFileContext
|
|
18
19
|
from sarj_python_lint._filesystem import atomic_write_text
|
|
20
|
+
from sarj_python_lint._source import read_python_source
|
|
19
21
|
from sarj_python_lint.json_boundary import is_object_mapping, parse_json
|
|
20
22
|
from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity, is_suppressed
|
|
21
23
|
from sarj_python_lint.rules import REGISTRY
|
|
@@ -110,7 +112,7 @@ def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
|
|
|
110
112
|
loaded: dict[Path, str] = {}
|
|
111
113
|
for path in expanded:
|
|
112
114
|
try:
|
|
113
|
-
loaded[path] = path
|
|
115
|
+
loaded[path] = read_python_source(path)
|
|
114
116
|
except OSError:
|
|
115
117
|
continue
|
|
116
118
|
project_rules = [rule for rule in rules if isinstance(rule, ProjectRule)]
|
|
@@ -138,6 +140,7 @@ def check_source(
|
|
|
138
140
|
if diagnostic.code == "SARJ419" or not is_suppressed(source_lines, diagnostic.line, diagnostic.code)
|
|
139
141
|
],
|
|
140
142
|
source=source,
|
|
143
|
+
context=context,
|
|
141
144
|
)
|
|
142
145
|
|
|
143
146
|
|
|
@@ -187,21 +190,27 @@ class _OwnerLocation(NamedTuple):
|
|
|
187
190
|
column: int
|
|
188
191
|
|
|
189
192
|
|
|
190
|
-
def deduplicate_diagnostics(
|
|
191
|
-
diags =
|
|
193
|
+
def deduplicate_diagnostics(
|
|
194
|
+
diags: list[Diagnostic], *, source: str | None = None, context: PythonFileContext | None = None
|
|
195
|
+
) -> list[Diagnostic]:
|
|
192
196
|
codes = frozenset(diagnostic.code for diagnostic in diags)
|
|
193
197
|
needs_docstring_owners = ("SARJ092" in codes and not codes.isdisjoint(_DIAGNOSTIC_PRECEDENCE["SARJ092"])) or (
|
|
194
198
|
"SARJ420" in codes and not codes.isdisjoint(_DOCSTRING_PRECEDENCE_CODES)
|
|
195
199
|
)
|
|
196
|
-
docstring_owners = _docstring_owner_locations(source) if source is not None and needs_docstring_owners else {}
|
|
197
200
|
needs_signature_owners = (
|
|
198
201
|
("SARJ093" in codes and "SARJ034" in codes)
|
|
199
202
|
or ("SARJ447" in codes and "SARJ008" in codes)
|
|
200
203
|
or ("SARJ094" in codes and not codes.isdisjoint(_DIAGNOSTIC_PRECEDENCE["SARJ094"]))
|
|
201
204
|
)
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
needs_composition_owners = {"SARJ066", "SARJ457"} <= codes
|
|
206
|
+
tree = (
|
|
207
|
+
_diagnostic_owner_tree(source, context)
|
|
208
|
+
if needs_docstring_owners or needs_signature_owners or needs_composition_owners
|
|
209
|
+
else None
|
|
204
210
|
)
|
|
211
|
+
diags = _deduplicate_test_composition(diags, tree)
|
|
212
|
+
docstring_owners = _docstring_owner_locations(tree) if needs_docstring_owners else {}
|
|
213
|
+
signature_owners = _function_signature_owner_locations(tree) if needs_signature_owners else {}
|
|
205
214
|
|
|
206
215
|
def owner_location(diagnostic: Diagnostic) -> _OwnerLocation:
|
|
207
216
|
if diagnostic.code in {"SARJ008", "SARJ034", "SARJ093", "SARJ094", "SARJ447"}:
|
|
@@ -236,13 +245,19 @@ def deduplicate_diagnostics(diags: list[Diagnostic], *, source: str | None = Non
|
|
|
236
245
|
]
|
|
237
246
|
|
|
238
247
|
|
|
239
|
-
def
|
|
248
|
+
def _diagnostic_owner_tree(source: str | None, context: PythonFileContext | None) -> ast.Module | None:
|
|
249
|
+
if source is None:
|
|
250
|
+
return None
|
|
251
|
+
if context is not None and context.source == source:
|
|
252
|
+
return context.tree
|
|
253
|
+
with suppress(SyntaxError):
|
|
254
|
+
return ast.parse(source)
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _deduplicate_test_composition(diags: list[Diagnostic], tree: ast.Module | None) -> list[Diagnostic]:
|
|
240
259
|
codes = {diagnostic.code for diagnostic in diags}
|
|
241
|
-
if
|
|
242
|
-
return diags
|
|
243
|
-
try:
|
|
244
|
-
tree = ast.parse(source)
|
|
245
|
-
except SyntaxError:
|
|
260
|
+
if tree is None or not {"SARJ066", "SARJ457"} <= codes:
|
|
246
261
|
return diags
|
|
247
262
|
whole_tests = {
|
|
248
263
|
(diagnostic.path, diagnostic.line): diagnostic.severity for diagnostic in diags if diagnostic.code == "SARJ066"
|
|
@@ -312,10 +327,8 @@ def _typed_docstring_warning_is_superseded(codes: dict[str, set[Severity]]) -> b
|
|
|
312
327
|
return any(Severity.ERROR in codes.get(generic, set()) for generic in ("SARJ086", "SARJ087"))
|
|
313
328
|
|
|
314
329
|
|
|
315
|
-
def _function_signature_owner_locations(
|
|
316
|
-
|
|
317
|
-
tree = ast.parse(source)
|
|
318
|
-
except SyntaxError:
|
|
330
|
+
def _function_signature_owner_locations(tree: ast.Module | None) -> dict[int, _OwnerLocation]:
|
|
331
|
+
if tree is None:
|
|
319
332
|
return {}
|
|
320
333
|
owners: dict[int, _OwnerLocation] = {}
|
|
321
334
|
for node in ast.walk(tree):
|
|
@@ -327,11 +340,7 @@ def _function_signature_owner_locations(source: str) -> dict[int, _OwnerLocation
|
|
|
327
340
|
return owners
|
|
328
341
|
|
|
329
342
|
|
|
330
|
-
def _docstring_owner_locations(
|
|
331
|
-
try:
|
|
332
|
-
tree = ast.parse(source)
|
|
333
|
-
except SyntaxError:
|
|
334
|
-
tree = None
|
|
343
|
+
def _docstring_owner_locations(tree: ast.Module | None) -> dict[int, _OwnerLocation]:
|
|
335
344
|
if tree is None:
|
|
336
345
|
return {}
|
|
337
346
|
owners: dict[int, _OwnerLocation] = {}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Final
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PYTHON_SOURCE_ENCODING: Final = "utf-8-sig"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def read_python_source(path: Path) -> str:
|
|
14
|
+
return path.read_text(encoding=PYTHON_SOURCE_ENCODING, errors="replace")
|
|
@@ -9,6 +9,7 @@ import tokenize
|
|
|
9
9
|
from types import MappingProxyType
|
|
10
10
|
from typing import TYPE_CHECKING, Final, NamedTuple, TypeGuard
|
|
11
11
|
|
|
12
|
+
from sarj_python_lint._source import PYTHON_SOURCE_ENCODING
|
|
12
13
|
from sarj_python_lint.json_boundary import is_object_mapping, parse_json
|
|
13
14
|
|
|
14
15
|
|
|
@@ -329,7 +330,7 @@ def _python_files(root: Path, excluded_dir_names: frozenset[str]) -> Iterator[Pa
|
|
|
329
330
|
|
|
330
331
|
def _read(path: Path) -> str:
|
|
331
332
|
try:
|
|
332
|
-
return path.read_text(encoding=
|
|
333
|
+
return path.read_text(encoding=PYTHON_SOURCE_ENCODING)
|
|
333
334
|
except UnicodeDecodeError, OSError:
|
|
334
335
|
return ""
|
|
335
336
|
|
|
@@ -5,6 +5,7 @@ from collections import Counter
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING, Literal, NamedTuple
|
|
7
7
|
|
|
8
|
+
from sarj_python_lint._source import read_python_source
|
|
8
9
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
9
10
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
10
11
|
|
|
@@ -839,7 +840,7 @@ def _read_module(path: Path, cache: dict[Path, ast.Module | None]) -> ast.Module
|
|
|
839
840
|
if path.stat().st_size > _MAX_IMPORTED_MODULE_BYTES:
|
|
840
841
|
tree = None
|
|
841
842
|
else:
|
|
842
|
-
tree = ast.parse(path
|
|
843
|
+
tree = ast.parse(read_python_source(path), filename=str(path))
|
|
843
844
|
except OSError, SyntaxError:
|
|
844
845
|
tree = None
|
|
845
846
|
cache[path] = tree
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import TYPE_CHECKING, final
|
|
7
7
|
|
|
8
|
+
from sarj_python_lint._source import PYTHON_SOURCE_ENCODING
|
|
8
9
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
9
10
|
|
|
10
11
|
|
|
@@ -113,7 +114,7 @@ def _read_module(path: Path) -> LocalModule | None:
|
|
|
113
114
|
data = stream.read(_MAX_MODULE_BYTES + 1)
|
|
114
115
|
if len(data) > _MAX_MODULE_BYTES:
|
|
115
116
|
return None
|
|
116
|
-
source = data.decode(
|
|
117
|
+
source = data.decode(PYTHON_SOURCE_ENCODING)
|
|
117
118
|
tree = ast.parse(source, filename=str(path))
|
|
118
119
|
except OSError, UnicodeError, SyntaxError:
|
|
119
120
|
return None
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, TypeGuard, final
|
|
|
11
11
|
from packaging.requirements import InvalidRequirement, Requirement
|
|
12
12
|
from packaging.utils import canonicalize_name
|
|
13
13
|
|
|
14
|
+
from sarj_python_lint._source import PYTHON_SOURCE_ENCODING
|
|
14
15
|
from sarj_python_lint.rules._first_party import distribution_root
|
|
15
16
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
16
17
|
from sarj_python_lint.rules._paths import is_generated
|
|
@@ -264,7 +265,7 @@ def _source(path: Path) -> NominalSource | None:
|
|
|
264
265
|
try:
|
|
265
266
|
if path.is_symlink() or path.stat().st_size > _MAX_BYTES:
|
|
266
267
|
return None
|
|
267
|
-
text = path.read_text(encoding=
|
|
268
|
+
text = path.read_text(encoding=PYTHON_SOURCE_ENCODING)
|
|
268
269
|
if is_generated(path, text):
|
|
269
270
|
return None
|
|
270
271
|
except OSError, UnicodeError:
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
@@ -11,6 +11,7 @@ import re
|
|
|
11
11
|
from types import MappingProxyType
|
|
12
12
|
from typing import TYPE_CHECKING, final
|
|
13
13
|
|
|
14
|
+
from sarj_python_lint._source import read_python_source
|
|
14
15
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
15
16
|
from sarj_python_lint.rules._first_party import FirstPartyFacts, project_root
|
|
16
17
|
from sarj_python_lint.rules._paths import is_generated
|
|
@@ -532,7 +533,7 @@ def _read_bounded_source(root: Path, path: Path) -> LoadedSource | None:
|
|
|
532
533
|
return None
|
|
533
534
|
resolved = path.resolve()
|
|
534
535
|
resolved.relative_to(root.resolve())
|
|
535
|
-
return LoadedSource(resolved, resolved
|
|
536
|
+
return LoadedSource(resolved, read_python_source(resolved))
|
|
536
537
|
except OSError, ValueError:
|
|
537
538
|
return None
|
|
538
539
|
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
from pathlib import Path, PurePosixPath
|
|
6
6
|
from typing import TYPE_CHECKING, NamedTuple, final, override
|
|
7
7
|
|
|
8
|
+
from sarj_python_lint._source import read_python_source
|
|
8
9
|
from sarj_python_lint.rule_base import (
|
|
9
10
|
AutofixPolicy,
|
|
10
11
|
Diagnostic,
|
|
@@ -688,7 +689,7 @@ def _read_module(path: Path) -> ast.Module | None:
|
|
|
688
689
|
try:
|
|
689
690
|
if path.stat().st_size > _MAX_IMPORTED_MODULE_BYTES:
|
|
690
691
|
return None
|
|
691
|
-
tree = ast.parse(path
|
|
692
|
+
tree = ast.parse(read_python_source(path), filename=str(path))
|
|
692
693
|
except OSError, SyntaxError:
|
|
693
694
|
return None
|
|
694
695
|
return tree
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
from pathlib import Path, PurePosixPath
|
|
8
8
|
from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
|
|
9
9
|
|
|
10
|
+
from sarj_python_lint._source import read_python_source
|
|
10
11
|
from sarj_python_lint.rule_base import (
|
|
11
12
|
Diagnostic,
|
|
12
13
|
ExampleFile,
|
|
@@ -718,7 +719,7 @@ def _read_module(path: Path, facts: RuntimeConfigFacts) -> ast.Module:
|
|
|
718
719
|
if cached is not None:
|
|
719
720
|
return cached
|
|
720
721
|
try:
|
|
721
|
-
tree = ast.parse(path
|
|
722
|
+
tree = ast.parse(read_python_source(path), filename=str(path))
|
|
722
723
|
except OSError, SyntaxError:
|
|
723
724
|
tree = ast.Module(body=[], type_ignores=[])
|
|
724
725
|
facts.modules[path] = tree
|
|
@@ -781,7 +782,7 @@ def _candidate_calls_class(
|
|
|
781
782
|
if is_test_path(candidate):
|
|
782
783
|
return False
|
|
783
784
|
try:
|
|
784
|
-
source = candidate
|
|
785
|
+
source = read_python_source(candidate)
|
|
785
786
|
except OSError:
|
|
786
787
|
return False
|
|
787
788
|
if class_name not in source or is_generated(candidate, source):
|
|
@@ -15,6 +15,7 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
Severity,
|
|
16
16
|
)
|
|
17
17
|
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
18
|
+
from sarj_python_lint.rules._paths import is_test_path
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
@@ -32,13 +33,14 @@ _FAIL = "fail"
|
|
|
32
33
|
|
|
33
34
|
# Enough of the operand to identify it in the message without pasting a screenful.
|
|
34
35
|
_OPERAND_PREVIEW_CHARS = 40
|
|
36
|
+
_UNKNOWN = object()
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
class NoTautologicalExpect(Rule):
|
|
38
40
|
id: str = "no-statically-truthy-assertion"
|
|
39
41
|
code: str = "SARJ057"
|
|
40
42
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
41
|
-
summary="
|
|
43
|
+
summary="An assertion condition is statically truthy, including test-local scalar setup.",
|
|
42
44
|
rationale=(
|
|
43
45
|
"A condition that stays truthy whenever evaluation succeeds cannot reject an incorrect result and often "
|
|
44
46
|
"means the intended condition was wrapped in a container or placed in the message slot."
|
|
@@ -51,10 +53,40 @@ class NoTautologicalExpect(Rule):
|
|
|
51
53
|
aliases=("no-tautological-expect",),
|
|
52
54
|
limitations=(
|
|
53
55
|
"Detection covers truthy scalar constants and definitely non-empty list, set, and dict displays.",
|
|
56
|
+
"Test-local immutable scalar bindings and their aliases are tracked only through straight-line assignments; calls, control flow, mutable values, and global/nonlocal writes invalidate tracking.",
|
|
54
57
|
"Ruff owns asserted strings and tuples, literal comparisons and identity, and unittest-style assertion methods.",
|
|
55
58
|
"Generated files, always-failing assertions, deliberate match-arm success markers, and runtime-value comparisons are excluded.",
|
|
56
59
|
),
|
|
57
60
|
examples=(
|
|
61
|
+
RuleExample(
|
|
62
|
+
example_id="setup-only-oracle",
|
|
63
|
+
scenario="setup-only",
|
|
64
|
+
title="A test asserts only on its own fixed setup",
|
|
65
|
+
outcome=ExampleOutcome.MATCH,
|
|
66
|
+
files=(
|
|
67
|
+
ExampleFile.python(
|
|
68
|
+
"tests/test_service.py", "def test_service():\n status = 'ok'\n assert status == 'ok'\n"
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
focus_path=PurePosixPath("tests/test_service.py"),
|
|
72
|
+
expected_count=1,
|
|
73
|
+
public=True,
|
|
74
|
+
),
|
|
75
|
+
RuleExample(
|
|
76
|
+
example_id="produced-scalar-oracle",
|
|
77
|
+
scenario="setup-only",
|
|
78
|
+
title="Assert on a value returned by the application",
|
|
79
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
80
|
+
files=(
|
|
81
|
+
ExampleFile.python(
|
|
82
|
+
"tests/test_service.py",
|
|
83
|
+
"def test_service():\n status = service_status()\n assert status == 'ok'\n",
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
focus_path=PurePosixPath("tests/test_service.py"),
|
|
87
|
+
expected_count=0,
|
|
88
|
+
public=True,
|
|
89
|
+
),
|
|
58
90
|
RuleExample(
|
|
59
91
|
example_id="condition-in-message-slot",
|
|
60
92
|
title="The intended condition became an assertion message",
|
|
@@ -96,6 +128,9 @@ class NoTautologicalExpect(Rule):
|
|
|
96
128
|
if tree is None:
|
|
97
129
|
return []
|
|
98
130
|
exempt = _exempt_nodes(tree, node_index=context.node_index)
|
|
131
|
+
tautologies = _tautologies(tree, exempt, node_index=context.node_index)
|
|
132
|
+
if is_test_path(path) and path.name != "conftest.py":
|
|
133
|
+
tautologies.extend(_bound_tautologies(context))
|
|
99
134
|
diags = [
|
|
100
135
|
Diagnostic(
|
|
101
136
|
path=path,
|
|
@@ -105,12 +140,93 @@ class NoTautologicalExpect(Rule):
|
|
|
105
140
|
message=_message(node, reason),
|
|
106
141
|
severity=Severity.ERROR,
|
|
107
142
|
)
|
|
108
|
-
for node, reason in
|
|
143
|
+
for node, reason in tautologies
|
|
109
144
|
]
|
|
110
145
|
diags.sort(key=lambda d: (d.line, d.col))
|
|
111
146
|
return diags
|
|
112
147
|
|
|
113
148
|
|
|
149
|
+
def _bound_tautologies(context: PythonFileContext) -> list[_Tautology]:
|
|
150
|
+
found: list[_Tautology] = []
|
|
151
|
+
external_writes = context.nodes(ast.Global, ast.Nonlocal)
|
|
152
|
+
for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
|
|
153
|
+
if not function.name.startswith("test_"):
|
|
154
|
+
continue
|
|
155
|
+
if any(function.lineno <= node.lineno <= (function.end_lineno or function.lineno) for node in external_writes):
|
|
156
|
+
continue
|
|
157
|
+
found.extend(_function_bound_tautologies(function))
|
|
158
|
+
return found
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _function_bound_tautologies(function: ast.FunctionDef | ast.AsyncFunctionDef) -> list[_Tautology]:
|
|
162
|
+
found: list[_Tautology] = []
|
|
163
|
+
bindings: dict[str, object] = {}
|
|
164
|
+
for statement in function.body:
|
|
165
|
+
if _record_scalar_assignment(statement, bindings):
|
|
166
|
+
continue
|
|
167
|
+
if isinstance(statement, ast.Assert):
|
|
168
|
+
references_setup = any(
|
|
169
|
+
isinstance(node, ast.Name) and node.id in bindings for node in ast.walk(statement.test)
|
|
170
|
+
)
|
|
171
|
+
if references_setup and _scalar_condition(statement.test, bindings) is True:
|
|
172
|
+
found.append(_Tautology(statement, "the condition follows only from unchanged scalar setup"))
|
|
173
|
+
bindings.clear()
|
|
174
|
+
return found
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _record_scalar_assignment(statement: ast.stmt, bindings: dict[str, object]) -> bool:
|
|
178
|
+
match statement:
|
|
179
|
+
case (
|
|
180
|
+
ast.Assign(targets=[ast.Name(id=name)], value=value) | ast.AnnAssign(target=ast.Name(id=name), value=value)
|
|
181
|
+
) if value is not None:
|
|
182
|
+
scalar = _scalar_value(value, bindings)
|
|
183
|
+
if scalar is not _UNKNOWN:
|
|
184
|
+
bindings[name] = scalar
|
|
185
|
+
return True
|
|
186
|
+
case _:
|
|
187
|
+
pass
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _scalar_value(node: ast.expr, bindings: dict[str, object]) -> object:
|
|
192
|
+
if isinstance(node, ast.Name):
|
|
193
|
+
return bindings.get(node.id, _UNKNOWN)
|
|
194
|
+
if isinstance(node, ast.Constant):
|
|
195
|
+
value: object = node.value
|
|
196
|
+
return value if value is None or isinstance(value, (bool, int, float, str, bytes)) else _UNKNOWN
|
|
197
|
+
if isinstance(node, ast.UnaryOp):
|
|
198
|
+
value = _scalar_value(node.operand, bindings)
|
|
199
|
+
if isinstance(node.op, ast.Not) and value is not _UNKNOWN:
|
|
200
|
+
return not value
|
|
201
|
+
if isinstance(value, (int, float)):
|
|
202
|
+
if isinstance(node.op, ast.USub):
|
|
203
|
+
return -value
|
|
204
|
+
if isinstance(node.op, ast.UAdd):
|
|
205
|
+
return +value
|
|
206
|
+
return _UNKNOWN
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _scalar_condition(node: ast.expr, bindings: dict[str, object]) -> bool | None:
|
|
210
|
+
if isinstance(node, ast.Compare) and len(node.ops) == 1:
|
|
211
|
+
left = _scalar_value(node.left, bindings)
|
|
212
|
+
right = _scalar_value(node.comparators[0], bindings)
|
|
213
|
+
if left is _UNKNOWN or right is _UNKNOWN:
|
|
214
|
+
return None
|
|
215
|
+
match node.ops[0]:
|
|
216
|
+
case ast.Eq():
|
|
217
|
+
return left == right
|
|
218
|
+
case ast.NotEq():
|
|
219
|
+
return left != right
|
|
220
|
+
case ast.Is() if left is None or isinstance(left, bool):
|
|
221
|
+
return left is right
|
|
222
|
+
case ast.IsNot() if left is None or isinstance(left, bool):
|
|
223
|
+
return left is not right
|
|
224
|
+
case _:
|
|
225
|
+
return None
|
|
226
|
+
value = _scalar_value(node, bindings)
|
|
227
|
+
return None if value is _UNKNOWN else bool(value)
|
|
228
|
+
|
|
229
|
+
|
|
114
230
|
def _exempt_nodes(tree: ast.Module, *, node_index: NodeIndex | None = None) -> set[ast.AST]:
|
|
115
231
|
exempt: set[ast.AST] = set()
|
|
116
232
|
for node in walk_ast(tree, index=node_index):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_class_row.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/prefer_str_enum.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.3 → sarj_python_lint-1.25.5}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|