sarj-python-lint 1.8.3__tar.gz → 1.8.4__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.8.3 → sarj_python_lint-1.8.4}/PKG-INFO +1 -1
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/pyproject.toml +1 -1
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/iac_source_coupled_test.py +10 -3
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +191 -45
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/.gitignore +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/LICENSE +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/README.md +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/discourage_nullable_constructor_parameters.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/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.8.
|
|
3
|
+
Version: 1.8.4
|
|
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/
|
|
@@ -15,14 +15,19 @@ from sarj_python_lint.rule_base import (
|
|
|
15
15
|
Severity,
|
|
16
16
|
)
|
|
17
17
|
from sarj_python_lint.rules._paths import is_test_path
|
|
18
|
-
from sarj_python_lint.rules.no_raw_source_text_test_oracle import
|
|
18
|
+
from sarj_python_lint.rules.no_raw_source_text_test_oracle import (
|
|
19
|
+
IAC_JSON_SOURCE_SUFFIXES,
|
|
20
|
+
FunctionAnalyzer,
|
|
21
|
+
module_source_path_tuples,
|
|
22
|
+
top_level_test_functions,
|
|
23
|
+
)
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
if TYPE_CHECKING:
|
|
22
27
|
from sarj_python_lint._file_context import PythonFileContext
|
|
23
28
|
|
|
24
29
|
|
|
25
|
-
IAC_SOURCE_SUFFIXES = (".hcl", ".tf", ".
|
|
30
|
+
IAC_SOURCE_SUFFIXES = (".hcl", ".tf", ".tfvars", ".tftest.hcl", *IAC_JSON_SOURCE_SUFFIXES)
|
|
26
31
|
|
|
27
32
|
|
|
28
33
|
@final
|
|
@@ -42,7 +47,7 @@ class IacSourceCoupledTest(Rule):
|
|
|
42
47
|
),
|
|
43
48
|
category=RuleCategory.TESTING,
|
|
44
49
|
limitations=(
|
|
45
|
-
"The rule follows local aliases, path collections, context-managed reads, and
|
|
50
|
+
"The rule follows local aliases, path collections, context-managed reads, common normalization, and direct comprehension reads unpacked from stable module-level path tuples; interprocedural flows remain unreported.",
|
|
46
51
|
"Files produced beneath recognized temporary-directory fixtures are generated outputs and remain unreported.",
|
|
47
52
|
"The Python detector currently owns Terraform and HCL suffixes; YAML remains with the general source-coupled rule.",
|
|
48
53
|
"Fixture, golden, and snapshot paths are treated as deliberate representation contracts; other packaging, formatter, and compatibility contracts require an exact suppression.",
|
|
@@ -93,11 +98,13 @@ class IacSourceCoupledTest(Rule):
|
|
|
93
98
|
return []
|
|
94
99
|
imports = context.module_imports
|
|
95
100
|
source_lines = context.source_lines
|
|
101
|
+
module_tuples = module_source_path_tuples(tree, imports, IAC_SOURCE_SUFFIXES)
|
|
96
102
|
assertions = [
|
|
97
103
|
assertion
|
|
98
104
|
for function, unittest_style in top_level_test_functions(tree, imports)
|
|
99
105
|
for assertion in FunctionAnalyzer(
|
|
100
106
|
IAC_SOURCE_SUFFIXES,
|
|
107
|
+
module_path_tuples=module_tuples,
|
|
101
108
|
imports=imports,
|
|
102
109
|
suppression_code=self.code,
|
|
103
110
|
suppression_lines=source_lines,
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
+
from collections import Counter
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from pathlib import PurePosixPath
|
|
7
|
+
from types import MappingProxyType
|
|
6
8
|
from typing import TYPE_CHECKING, ClassVar, NamedTuple, final, override
|
|
7
9
|
|
|
8
10
|
from sarj_python_lint.rule_base import (
|
|
@@ -23,6 +25,8 @@ from sarj_python_lint.rules._paths import is_test_path
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import Iterator, Mapping
|
|
29
|
+
|
|
26
30
|
from sarj_python_lint._file_context import PythonFileContext
|
|
27
31
|
|
|
28
32
|
|
|
@@ -35,6 +39,7 @@ GENERAL_SOURCE_SUFFIXES = (
|
|
|
35
39
|
".mjs",
|
|
36
40
|
".py",
|
|
37
41
|
".sh",
|
|
42
|
+
".sh.tftpl",
|
|
38
43
|
".sql",
|
|
39
44
|
".toml",
|
|
40
45
|
".ts",
|
|
@@ -43,6 +48,11 @@ GENERAL_SOURCE_SUFFIXES = (
|
|
|
43
48
|
".yml",
|
|
44
49
|
)
|
|
45
50
|
|
|
51
|
+
GENERAL_SOURCE_BASENAMES = frozenset({"Dockerfile"})
|
|
52
|
+
IAC_JSON_SOURCE_SUFFIXES = (".tf.json", ".tftest.json")
|
|
53
|
+
|
|
54
|
+
_EMPTY_PATH_TUPLES: Mapping[str, int] = MappingProxyType({})
|
|
55
|
+
|
|
46
56
|
_REPRESENTATION_DIRS = frozenset({"fixture", "fixtures", "golden", "goldens", "snapshot", "snapshots"})
|
|
47
57
|
_PYTEST = frozenset({"pytest"})
|
|
48
58
|
_UNITTEST = frozenset({"unittest"})
|
|
@@ -59,12 +69,25 @@ class _ApiProvenance:
|
|
|
59
69
|
shadowed: frozenset[str]
|
|
60
70
|
|
|
61
71
|
|
|
72
|
+
@dataclass(frozen=True, slots=True)
|
|
73
|
+
class _SourceKinds:
|
|
74
|
+
suffixes: tuple[str, ...]
|
|
75
|
+
basenames: frozenset[str] = frozenset()
|
|
76
|
+
|
|
77
|
+
def matches(self, value: str) -> bool:
|
|
78
|
+
lowered = value.lower()
|
|
79
|
+
for suffix in IAC_JSON_SOURCE_SUFFIXES:
|
|
80
|
+
if lowered.endswith(suffix):
|
|
81
|
+
return suffix in self.suffixes
|
|
82
|
+
return lowered.endswith(self.suffixes) or value.replace("\\", "/").rsplit("/", 1)[-1] in self.basenames
|
|
83
|
+
|
|
84
|
+
|
|
62
85
|
@dataclass(frozen=True, slots=True)
|
|
63
86
|
class _TextFlow:
|
|
64
87
|
raw_names: set[str]
|
|
65
88
|
path_names: set[str]
|
|
66
89
|
ephemeral_path_names: set[str]
|
|
67
|
-
|
|
90
|
+
sources: _SourceKinds
|
|
68
91
|
api: _ApiProvenance
|
|
69
92
|
|
|
70
93
|
|
|
@@ -123,7 +146,9 @@ class NoRawSourceTextTestOracle(Rule):
|
|
|
123
146
|
autofix=AutofixPolicy.NONE,
|
|
124
147
|
aliases=("source-coupled-test",),
|
|
125
148
|
limitations=(
|
|
126
|
-
"The rule
|
|
149
|
+
"The rule recognizes source-like suffixes, shell templates ending in .sh.tftpl, and the exact Dockerfile basename; it follows local aliases, context-managed reads, and common normalization.",
|
|
150
|
+
"Direct generator/list-comprehension reads unpacked from stable module-level path tuples are followed; mutable collections, rebinding, filtered comprehensions, and interprocedural flows remain unreported.",
|
|
151
|
+
"Literal shebang startswith checks are representation contracts and remain unreported; other text compatibility sentinels require an exact suppression.",
|
|
127
152
|
"Files produced beneath recognized temporary-directory fixtures are generated artifacts, not repository source, and remain unreported.",
|
|
128
153
|
"Paths beneath fixture, golden, and snapshot directories are treated as deliberate representation contracts and remain unreported.",
|
|
129
154
|
"When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason.",
|
|
@@ -171,11 +196,16 @@ class NoRawSourceTextTestOracle(Rule):
|
|
|
171
196
|
return []
|
|
172
197
|
imports = context.module_imports
|
|
173
198
|
source_lines = context.source_lines
|
|
199
|
+
module_tuples = module_source_path_tuples(
|
|
200
|
+
tree, imports, GENERAL_SOURCE_SUFFIXES, source_basenames=GENERAL_SOURCE_BASENAMES
|
|
201
|
+
)
|
|
174
202
|
assertions = [
|
|
175
203
|
assertion
|
|
176
204
|
for function, unittest_style in top_level_test_functions(tree, imports)
|
|
177
205
|
for assertion in FunctionAnalyzer(
|
|
178
206
|
GENERAL_SOURCE_SUFFIXES,
|
|
207
|
+
source_basenames=GENERAL_SOURCE_BASENAMES,
|
|
208
|
+
module_path_tuples=module_tuples,
|
|
179
209
|
imports=imports,
|
|
180
210
|
suppression_code=self.code,
|
|
181
211
|
suppression_lines=source_lines,
|
|
@@ -361,7 +391,8 @@ def _node_is_suppressed(source_lines: list[str], node: ast.AST, code: str) -> bo
|
|
|
361
391
|
|
|
362
392
|
class FunctionAnalyzer(ast.NodeVisitor):
|
|
363
393
|
_imports: ImportIndex | None
|
|
364
|
-
|
|
394
|
+
_module_path_tuples: Mapping[str, int]
|
|
395
|
+
_sources: _SourceKinds
|
|
365
396
|
_suppression_code: str | None
|
|
366
397
|
_suppression_lines: list[str] | None
|
|
367
398
|
_unittest_style: bool
|
|
@@ -370,12 +401,15 @@ class FunctionAnalyzer(ast.NodeVisitor):
|
|
|
370
401
|
self,
|
|
371
402
|
source_suffixes: tuple[str, ...],
|
|
372
403
|
*,
|
|
404
|
+
source_basenames: frozenset[str] = frozenset(),
|
|
405
|
+
module_path_tuples: Mapping[str, int] = _EMPTY_PATH_TUPLES,
|
|
373
406
|
imports: ImportIndex | None = None,
|
|
374
407
|
suppression_code: str | None = None,
|
|
375
408
|
suppression_lines: list[str] | None = None,
|
|
376
409
|
unittest_style: bool = False,
|
|
377
410
|
) -> None:
|
|
378
|
-
self.
|
|
411
|
+
self._sources = _SourceKinds(source_suffixes, source_basenames)
|
|
412
|
+
self._module_path_tuples = module_path_tuples
|
|
379
413
|
self._unittest_style = unittest_style
|
|
380
414
|
self._imports = imports
|
|
381
415
|
self._suppression_code = suppression_code
|
|
@@ -439,7 +473,7 @@ class FunctionAnalyzer(ast.NodeVisitor):
|
|
|
439
473
|
if item.optional_vars is not None:
|
|
440
474
|
self._clear_target(item.optional_vars)
|
|
441
475
|
if isinstance(item.optional_vars, ast.Name) and _is_source_open(
|
|
442
|
-
item.context_expr, self._paths, self._ephemeral_paths, self.
|
|
476
|
+
item.context_expr, self._paths, self._ephemeral_paths, self._sources, self._provenance
|
|
443
477
|
):
|
|
444
478
|
self._raw.add(item.optional_vars.id)
|
|
445
479
|
self._raw_origins[item.optional_vars.id] = {item.context_expr.lineno}
|
|
@@ -569,19 +603,56 @@ class FunctionAnalyzer(ast.NodeVisitor):
|
|
|
569
603
|
|
|
570
604
|
def _record_target(self, target: ast.expr, value: ast.expr) -> None:
|
|
571
605
|
self._clear_target(target)
|
|
606
|
+
if isinstance(target, ast.Tuple | ast.List):
|
|
607
|
+
self._record_unpacked_read(target, value)
|
|
608
|
+
return
|
|
572
609
|
if not isinstance(target, ast.Name):
|
|
573
610
|
return
|
|
574
611
|
raw_origins = _expression_origins(value, self._raw_origins, self._flow)
|
|
575
612
|
if _ephemeral_path_expression(value, self._ephemeral_paths):
|
|
576
613
|
self._ephemeral_paths.add(target.id)
|
|
577
|
-
if _source_path_collection(value, self._paths, self.
|
|
614
|
+
if _source_path_collection(value, self._paths, self._sources, self._provenance):
|
|
578
615
|
self._collections.add(target.id)
|
|
579
|
-
if _source_path_expression(value, self._paths, self.
|
|
616
|
+
if _source_path_expression(value, self._paths, self._sources, self._provenance):
|
|
580
617
|
self._paths.add(target.id)
|
|
581
618
|
if _raw_text_expression(value, self._flow):
|
|
582
619
|
self._raw.add(target.id)
|
|
583
620
|
self._raw_origins[target.id] = raw_origins or {value.lineno}
|
|
584
621
|
|
|
622
|
+
def _record_unpacked_read(self, target: ast.Tuple | ast.List, value: ast.expr) -> None:
|
|
623
|
+
if not isinstance(value, ast.GeneratorExp | ast.ListComp) or len(value.generators) != 1:
|
|
624
|
+
return
|
|
625
|
+
generator = value.generators[0]
|
|
626
|
+
if (
|
|
627
|
+
generator.is_async
|
|
628
|
+
or generator.ifs
|
|
629
|
+
or not isinstance(generator.target, ast.Name)
|
|
630
|
+
or not isinstance(generator.iter, ast.Name)
|
|
631
|
+
):
|
|
632
|
+
return
|
|
633
|
+
if (
|
|
634
|
+
generator.iter.id in self._provenance.shadowed
|
|
635
|
+
or self._module_path_tuples.get(generator.iter.id) != len(target.elts)
|
|
636
|
+
or not all(isinstance(element, ast.Name) for element in target.elts)
|
|
637
|
+
or any(isinstance(child, ast.NamedExpr) for child in walk_ast(value.elt))
|
|
638
|
+
):
|
|
639
|
+
return
|
|
640
|
+
flow = _TextFlow(
|
|
641
|
+
self._raw - {generator.target.id},
|
|
642
|
+
self._paths | {generator.target.id},
|
|
643
|
+
self._ephemeral_paths - {generator.target.id},
|
|
644
|
+
self._sources,
|
|
645
|
+
_ApiProvenance(self._provenance.imports, self._provenance.shadowed | {generator.target.id}),
|
|
646
|
+
)
|
|
647
|
+
if not _raw_text_expression(value.elt, flow):
|
|
648
|
+
return
|
|
649
|
+
raw_origins = {name: origins for name, origins in self._raw_origins.items() if name != generator.target.id}
|
|
650
|
+
origins = _expression_origins(value.elt, raw_origins, flow) or {value.elt.lineno}
|
|
651
|
+
for element in target.elts:
|
|
652
|
+
if isinstance(element, ast.Name):
|
|
653
|
+
self._raw.add(element.id)
|
|
654
|
+
self._raw_origins[element.id] = set(origins)
|
|
655
|
+
|
|
585
656
|
def _clear_target(self, target: ast.expr) -> None:
|
|
586
657
|
for child in walk_ast(target):
|
|
587
658
|
if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Store):
|
|
@@ -630,7 +701,7 @@ class FunctionAnalyzer(ast.NodeVisitor):
|
|
|
630
701
|
self._raw,
|
|
631
702
|
self._paths,
|
|
632
703
|
self._ephemeral_paths,
|
|
633
|
-
self.
|
|
704
|
+
self._sources,
|
|
634
705
|
self._provenance,
|
|
635
706
|
)
|
|
636
707
|
|
|
@@ -646,30 +717,90 @@ def _intersect_flow_states(left: _FlowState, right: _FlowState) -> _FlowState:
|
|
|
646
717
|
)
|
|
647
718
|
|
|
648
719
|
|
|
649
|
-
def
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
720
|
+
def module_source_path_tuples(
|
|
721
|
+
tree: ast.Module,
|
|
722
|
+
imports: ImportIndex,
|
|
723
|
+
source_suffixes: tuple[str, ...],
|
|
724
|
+
*,
|
|
725
|
+
source_basenames: frozenset[str] = frozenset(),
|
|
726
|
+
) -> dict[str, int]:
|
|
727
|
+
sources = _SourceKinds(source_suffixes, source_basenames)
|
|
728
|
+
api = _ApiProvenance(imports, frozenset())
|
|
729
|
+
tuples: dict[str, int] = {}
|
|
730
|
+
for statement in tree.body:
|
|
731
|
+
match statement:
|
|
732
|
+
case ast.Assign(targets=[target], value=value) | ast.AnnAssign(target=target, value=value):
|
|
733
|
+
pass
|
|
734
|
+
case _:
|
|
735
|
+
continue
|
|
736
|
+
if (
|
|
737
|
+
isinstance(target, ast.Name)
|
|
738
|
+
and isinstance(value, ast.Tuple)
|
|
739
|
+
and _source_path_collection(value, set(), sources, api)
|
|
740
|
+
):
|
|
741
|
+
tuples[target.id] = len(value.elts)
|
|
742
|
+
if not tuples:
|
|
743
|
+
return {}
|
|
744
|
+
bindings = Counter(_enclosing_scope_bindings(tree.body))
|
|
745
|
+
if bindings["*"]:
|
|
746
|
+
return {}
|
|
747
|
+
global_names = {name for node in walk_ast(tree) if isinstance(node, ast.Global) for name in node.names}
|
|
748
|
+
return {name: size for name, size in tuples.items() if bindings[name] == 1 and name not in global_names}
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def _enclosing_scope_bindings(nodes: list[ast.stmt]) -> Iterator[str]:
|
|
752
|
+
pending: list[ast.AST] = list(nodes)
|
|
657
753
|
while pending:
|
|
658
754
|
current = pending.pop()
|
|
659
755
|
match current:
|
|
660
756
|
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
661
|
-
|
|
757
|
+
yield current.name
|
|
758
|
+
pending.extend(_definition_enclosing_expressions(current))
|
|
662
759
|
continue
|
|
663
|
-
case ast.Lambda()
|
|
760
|
+
case ast.Lambda():
|
|
761
|
+
pending.extend(_definition_enclosing_expressions(current))
|
|
762
|
+
continue
|
|
763
|
+
case ast.ListComp() | ast.SetComp() | ast.DictComp() | ast.GeneratorExp():
|
|
764
|
+
pending.extend(child.target for child in walk_ast(current) if isinstance(child, ast.NamedExpr))
|
|
664
765
|
continue
|
|
665
766
|
case ast.Import(names=aliases) | ast.ImportFrom(names=aliases):
|
|
666
|
-
|
|
767
|
+
yield from (alias.asname or alias.name.partition(".")[0] for alias in aliases)
|
|
667
768
|
continue
|
|
668
|
-
case
|
|
669
|
-
|
|
769
|
+
case (
|
|
770
|
+
ast.Name(id=name, ctx=(ast.Store() | ast.Del()))
|
|
771
|
+
| ast.ExceptHandler(name=str(name))
|
|
772
|
+
| ast.MatchAs(name=str(name))
|
|
773
|
+
| ast.MatchStar(name=str(name))
|
|
774
|
+
| ast.MatchMapping(rest=str(name))
|
|
775
|
+
):
|
|
776
|
+
yield name
|
|
670
777
|
case _:
|
|
671
778
|
pass
|
|
672
779
|
pending.extend(ast.iter_child_nodes(current))
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def _definition_enclosing_expressions(
|
|
783
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef | ast.Lambda,
|
|
784
|
+
) -> list[ast.AST]:
|
|
785
|
+
if isinstance(node, ast.ClassDef):
|
|
786
|
+
return [*node.decorator_list, *node.bases, *node.keywords]
|
|
787
|
+
defaults: list[ast.AST] = [
|
|
788
|
+
*node.args.defaults,
|
|
789
|
+
*(default for default in node.args.kw_defaults if default is not None),
|
|
790
|
+
]
|
|
791
|
+
if isinstance(node, ast.Lambda):
|
|
792
|
+
return defaults
|
|
793
|
+
return [*node.decorator_list, *defaults]
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def _function_bound_names(function: ast.FunctionDef | ast.AsyncFunctionDef) -> frozenset[str]:
|
|
797
|
+
args = function.args
|
|
798
|
+
names = {
|
|
799
|
+
argument.arg
|
|
800
|
+
for argument in (*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg)
|
|
801
|
+
if argument is not None
|
|
802
|
+
}
|
|
803
|
+
names.update(_enclosing_scope_bindings(function.body))
|
|
673
804
|
return frozenset(names)
|
|
674
805
|
|
|
675
806
|
|
|
@@ -695,7 +826,7 @@ def _is_source_open(
|
|
|
695
826
|
node: ast.expr,
|
|
696
827
|
path_names: set[str],
|
|
697
828
|
ephemeral_path_names: set[str],
|
|
698
|
-
|
|
829
|
+
sources: _SourceKinds,
|
|
699
830
|
api: _ApiProvenance,
|
|
700
831
|
) -> bool:
|
|
701
832
|
if not isinstance(node, ast.Call):
|
|
@@ -715,7 +846,7 @@ def _is_source_open(
|
|
|
715
846
|
path = node.func.value
|
|
716
847
|
else:
|
|
717
848
|
return False
|
|
718
|
-
return _source_path_expression(path, path_names,
|
|
849
|
+
return _source_path_expression(path, path_names, sources, api) and not _ephemeral_path_expression(
|
|
719
850
|
path, ephemeral_path_names
|
|
720
851
|
)
|
|
721
852
|
|
|
@@ -723,7 +854,7 @@ def _is_source_open(
|
|
|
723
854
|
def _source_path_expression(
|
|
724
855
|
node: ast.AST,
|
|
725
856
|
path_names: set[str],
|
|
726
|
-
|
|
857
|
+
sources: _SourceKinds,
|
|
727
858
|
api: _ApiProvenance,
|
|
728
859
|
) -> bool:
|
|
729
860
|
if _representation_fixture_path(node):
|
|
@@ -732,31 +863,31 @@ def _source_path_expression(
|
|
|
732
863
|
case ast.Name(id=name):
|
|
733
864
|
return name in path_names
|
|
734
865
|
case ast.Constant(value=str(value)):
|
|
735
|
-
return
|
|
866
|
+
return sources.matches(value)
|
|
736
867
|
case ast.Attribute(attr="__file__"):
|
|
737
|
-
return ".py" in
|
|
868
|
+
return ".py" in sources.suffixes
|
|
738
869
|
case ast.Call(func=function, args=[path, *_]) if _api_resolves(
|
|
739
870
|
function, api, sources=frozenset({"pathlib"}), symbol="Path"
|
|
740
871
|
):
|
|
741
|
-
return _source_path_expression(path, path_names,
|
|
872
|
+
return _source_path_expression(path, path_names, sources, api)
|
|
742
873
|
case ast.Call(func=function) if _api_resolves(function, api, sources=frozenset({"inspect"}), symbol="getfile"):
|
|
743
|
-
return ".py" in
|
|
874
|
+
return ".py" in sources.suffixes
|
|
744
875
|
case ast.JoinedStr(values=values):
|
|
745
|
-
return any(_source_path_expression(value, path_names,
|
|
876
|
+
return any(_source_path_expression(value, path_names, sources, api) for value in values)
|
|
746
877
|
case ast.Call(
|
|
747
878
|
func=ast.Attribute(value=receiver, attr="with_suffix" | "with_name"),
|
|
748
879
|
args=[ast.Constant(value=str(value)), *_],
|
|
749
880
|
):
|
|
750
|
-
return
|
|
751
|
-
_source_path_expression(receiver, path_names,
|
|
881
|
+
return sources.matches(value) and (
|
|
882
|
+
_source_path_expression(receiver, path_names, sources, api)
|
|
752
883
|
or (
|
|
753
884
|
isinstance(receiver, ast.Call)
|
|
754
885
|
and _api_resolves(receiver.func, api, sources=frozenset({"pathlib"}), symbol="Path")
|
|
755
886
|
)
|
|
756
887
|
)
|
|
757
888
|
case ast.BinOp(left=left, right=right):
|
|
758
|
-
return _source_path_expression(left, path_names,
|
|
759
|
-
right, path_names,
|
|
889
|
+
return _source_path_expression(left, path_names, sources, api) or _source_path_expression(
|
|
890
|
+
right, path_names, sources, api
|
|
760
891
|
)
|
|
761
892
|
case ast.Subscript(value=ast.Name(id=name)):
|
|
762
893
|
return name in path_names
|
|
@@ -781,13 +912,13 @@ def _ephemeral_path_expression(node: ast.AST, ephemeral_path_names: set[str]) ->
|
|
|
781
912
|
def _source_path_collection(
|
|
782
913
|
node: ast.AST,
|
|
783
914
|
path_names: set[str],
|
|
784
|
-
|
|
915
|
+
sources: _SourceKinds,
|
|
785
916
|
api: _ApiProvenance,
|
|
786
917
|
) -> bool:
|
|
787
918
|
return (
|
|
788
919
|
isinstance(node, (ast.List, ast.Set, ast.Tuple))
|
|
789
920
|
and bool(node.elts)
|
|
790
|
-
and all(_source_path_expression(element, path_names,
|
|
921
|
+
and all(_source_path_expression(element, path_names, sources, api) for element in node.elts)
|
|
791
922
|
)
|
|
792
923
|
|
|
793
924
|
|
|
@@ -795,19 +926,17 @@ def _raw_source_read(
|
|
|
795
926
|
node: ast.expr,
|
|
796
927
|
path_names: set[str],
|
|
797
928
|
ephemeral_path_names: set[str],
|
|
798
|
-
|
|
929
|
+
sources: _SourceKinds,
|
|
799
930
|
api: _ApiProvenance,
|
|
800
931
|
) -> bool:
|
|
801
932
|
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
|
802
933
|
return False
|
|
803
934
|
supported_reads = {"read_bytes", "read_text"}
|
|
804
935
|
if node.func.attr in supported_reads:
|
|
805
|
-
return _source_path_expression(
|
|
806
|
-
node.func.value,
|
|
807
|
-
)
|
|
808
|
-
return node.func.attr == "read" and _is_source_open(
|
|
809
|
-
node.func.value, path_names, ephemeral_path_names, source_suffixes, api
|
|
810
|
-
)
|
|
936
|
+
return _source_path_expression(node.func.value, path_names, sources, api) and not _ephemeral_path_expression(
|
|
937
|
+
node.func.value, ephemeral_path_names
|
|
938
|
+
)
|
|
939
|
+
return node.func.attr == "read" and _is_source_open(node.func.value, path_names, ephemeral_path_names, sources, api)
|
|
811
940
|
|
|
812
941
|
|
|
813
942
|
def _raw_text_expression(
|
|
@@ -816,7 +945,7 @@ def _raw_text_expression(
|
|
|
816
945
|
) -> bool:
|
|
817
946
|
if isinstance(node, ast.Name):
|
|
818
947
|
return node.id in flow.raw_names
|
|
819
|
-
if _raw_source_read(node, flow.path_names, flow.ephemeral_path_names, flow.
|
|
948
|
+
if _raw_source_read(node, flow.path_names, flow.ephemeral_path_names, flow.sources, flow.api):
|
|
820
949
|
return True
|
|
821
950
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
|
822
951
|
handle_reads = {"read", "readlines"}
|
|
@@ -837,13 +966,30 @@ def _raw_text_oracle(
|
|
|
837
966
|
if _is_raw_text_iteration_call(node, flow):
|
|
838
967
|
return True
|
|
839
968
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
|
840
|
-
if
|
|
969
|
+
if (
|
|
970
|
+
node.func.attr in _TEXT_ASSERTIONS
|
|
971
|
+
and not _is_shebang_contract(node)
|
|
972
|
+
and _raw_text_expression(node.func.value, flow)
|
|
973
|
+
):
|
|
841
974
|
return True
|
|
842
975
|
if node.func.attr in _REGEX_ASSERTIONS and any(_raw_text_expression(argument, flow) for argument in node.args):
|
|
843
976
|
return True
|
|
844
977
|
return any(_raw_text_oracle(child, flow) for child in ast.iter_child_nodes(node) if isinstance(child, ast.expr))
|
|
845
978
|
|
|
846
979
|
|
|
980
|
+
def _is_shebang_contract(node: ast.Call) -> bool:
|
|
981
|
+
return (
|
|
982
|
+
isinstance(node.func, ast.Attribute)
|
|
983
|
+
and node.func.attr == "startswith"
|
|
984
|
+
and not node.keywords
|
|
985
|
+
and len(node.args) == 1
|
|
986
|
+
and isinstance(node.args[0], ast.Constant)
|
|
987
|
+
and isinstance(node.args[0].value, str)
|
|
988
|
+
and node.args[0].value.startswith("#!")
|
|
989
|
+
and len(node.args[0].value.splitlines()) == 1
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
|
|
847
993
|
def _raw_text_measurement(
|
|
848
994
|
node: ast.expr,
|
|
849
995
|
flow: _TextFlow,
|
|
@@ -910,7 +1056,7 @@ def _expression_origins(
|
|
|
910
1056
|
if isinstance(child, ast.Name):
|
|
911
1057
|
origins.update(raw_origins.get(child.id, set()))
|
|
912
1058
|
elif isinstance(child, ast.Call) and _raw_source_read(
|
|
913
|
-
child, flow.path_names, flow.ephemeral_path_names, flow.
|
|
1059
|
+
child, flow.path_names, flow.ephemeral_path_names, flow.sources, flow.api
|
|
914
1060
|
):
|
|
915
1061
|
origins.add(child.lineno)
|
|
916
1062
|
return origins
|
|
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.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_contract_annotation.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_resource_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/_test_composition.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.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/excessive_commentary.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_any_mapping_types.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_delete_statement.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_dunder_all.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_fastapi_on_event.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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_offset_pagination.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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_restated_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/phase_label_comment.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/prefer_library_fake.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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/redundant_docstring.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
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/typed_error_reasons.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.8.3 → sarj_python_lint-1.8.4}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|