sarj-python-lint 1.28.2__tar.gz → 1.28.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.28.2 → sarj_python_lint-1.28.4}/PKG-INFO +2 -2
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/pyproject.toml +2 -2
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_file_context.py +10 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_docstrings.py +90 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +4 -3
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +1 -8
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +1 -10
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -9
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +1 -9
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/redundant_class_docstring.py +1 -89
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/redundant_docstring.py +3 -90
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +4 -1
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +2 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/.gitignore +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/LICENSE +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/README.md +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.28.2 → sarj_python_lint-1.28.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.28.
|
|
3
|
+
Version: 1.28.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/
|
|
@@ -19,7 +19,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
20
|
Requires-Dist: packaging==26.3
|
|
21
21
|
Requires-Dist: ruff>=0.16.10
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.2.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.2.3
|
|
23
23
|
Requires-Dist: sqlglot==30.19.0
|
|
24
24
|
Requires-Dist: typer==0.27.2
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.28.
|
|
3
|
+
version = "1.28.4"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["sarj-rule-contracts==0.2.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.2.3", "packaging==26.3", "ruff>=0.16.10", "sqlglot==30.19.0", "typer==0.27.2"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -57,6 +57,16 @@ class PythonFileContext:
|
|
|
57
57
|
def nodes[NodeT: ast.AST](self, *types: type[NodeT]) -> list[NodeT]:
|
|
58
58
|
return [node for node in self.node_index.query(types) if isinstance(node, types)]
|
|
59
59
|
|
|
60
|
+
@cached_property
|
|
61
|
+
def has_wildcard_import(self) -> bool:
|
|
62
|
+
return any(alias.name == "*" for node in self.nodes(ast.ImportFrom) for alias in node.names)
|
|
63
|
+
|
|
64
|
+
@cached_property
|
|
65
|
+
def consumed_docstring_names(self) -> frozenset[str]:
|
|
66
|
+
from sarj_python_lint.rules._docstrings import consumed_docstring_names # ruff: ignore[import-outside-top-level] -- share runtime docstring analysis only when requested.
|
|
67
|
+
|
|
68
|
+
return consumed_docstring_names(self._valid_tree(), node_index=self.node_index)
|
|
69
|
+
|
|
60
70
|
@property
|
|
61
71
|
def parents(self) -> Mapping[ast.AST, ast.AST]:
|
|
62
72
|
return self.node_index.parents
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
@@ -5,12 +5,17 @@ from itertools import starmap
|
|
|
5
5
|
import re
|
|
6
6
|
from typing import TYPE_CHECKING, NamedTuple
|
|
7
7
|
|
|
8
|
+
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
8
9
|
from sarj_python_lint.rules._comments import split_identifier, stem
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from collections.abc import Iterable
|
|
13
14
|
|
|
15
|
+
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_GETATTR_MIN_ARGS = 2
|
|
14
19
|
|
|
15
20
|
# Docstring filler that says nothing about *which* thing is being described.
|
|
16
21
|
_BASE_STOPWORDS = frozenset(
|
|
@@ -325,3 +330,88 @@ def restates(text: str, known: Iterable[str]) -> bool:
|
|
|
325
330
|
if not content:
|
|
326
331
|
return False
|
|
327
332
|
return all(stem(word) in known_stems for word in content)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def consumed_docstring_names(tree: ast.Module, *, node_index: NodeIndex | None = None) -> frozenset[str]:
|
|
336
|
+
consumed: set[str] = set()
|
|
337
|
+
aliases: dict[str, set[str]] = {}
|
|
338
|
+
for node in walk_ast(tree, index=node_index):
|
|
339
|
+
_record_docstring_use(node, consumed, aliases)
|
|
340
|
+
changed = True
|
|
341
|
+
while changed:
|
|
342
|
+
changed = False
|
|
343
|
+
for alias, originals in aliases.items():
|
|
344
|
+
if alias not in consumed:
|
|
345
|
+
continue
|
|
346
|
+
before = len(consumed)
|
|
347
|
+
consumed.update(originals)
|
|
348
|
+
changed |= len(consumed) != before
|
|
349
|
+
return frozenset(consumed)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _terminal_name(node: ast.expr) -> str | None:
|
|
353
|
+
match node:
|
|
354
|
+
case ast.Name():
|
|
355
|
+
return node.id
|
|
356
|
+
case ast.Attribute():
|
|
357
|
+
return node.attr
|
|
358
|
+
case _:
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _record_docstring_use(node: ast.AST, consumed: set[str], aliases: dict[str, set[str]]) -> None:
|
|
363
|
+
if isinstance(node, ast.Attribute) and node.attr == "__doc__":
|
|
364
|
+
if name := _terminal_name(node.value):
|
|
365
|
+
consumed.add(name)
|
|
366
|
+
elif isinstance(node, ast.Call) and _is_docstring_reader(node):
|
|
367
|
+
if name := _terminal_name(node.args[0]):
|
|
368
|
+
consumed.add(name)
|
|
369
|
+
elif isinstance(node, ast.Subscript):
|
|
370
|
+
if name := _subscripted_docstring_owner(node):
|
|
371
|
+
consumed.add(name)
|
|
372
|
+
else:
|
|
373
|
+
_record_docstring_alias(node, aliases)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _subscripted_docstring_owner(node: ast.Subscript) -> str | None:
|
|
377
|
+
if not isinstance(node.slice, ast.Constant) or node.slice.value != "__doc__":
|
|
378
|
+
return None
|
|
379
|
+
value = node.value
|
|
380
|
+
if isinstance(value, ast.Attribute) and value.attr == "__dict__":
|
|
381
|
+
return _terminal_name(value.value)
|
|
382
|
+
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "vars" and value.args:
|
|
383
|
+
return _terminal_name(value.args[0])
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _is_docstring_reader(node: ast.Call) -> bool:
|
|
388
|
+
if not node.args:
|
|
389
|
+
return False
|
|
390
|
+
function = node.func
|
|
391
|
+
if isinstance(function, ast.Name) and function.id == "help":
|
|
392
|
+
return True
|
|
393
|
+
if isinstance(function, ast.Attribute) and function.attr in {"getdoc", "render_doc"}:
|
|
394
|
+
return True
|
|
395
|
+
return (
|
|
396
|
+
isinstance(function, ast.Name)
|
|
397
|
+
and function.id == "getattr"
|
|
398
|
+
and len(node.args) >= _GETATTR_MIN_ARGS
|
|
399
|
+
and isinstance(node.args[1], ast.Constant)
|
|
400
|
+
and node.args[1].value == "__doc__"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _record_docstring_alias(node: ast.AST, aliases: dict[str, set[str]]) -> None:
|
|
405
|
+
if isinstance(node, ast.Assign) and isinstance(node.value, (ast.Name, ast.Attribute)):
|
|
406
|
+
original = _terminal_name(node.value)
|
|
407
|
+
for target in node.targets:
|
|
408
|
+
if isinstance(target, ast.Name) and original is not None:
|
|
409
|
+
aliases.setdefault(target.id, set()).add(original)
|
|
410
|
+
elif (
|
|
411
|
+
isinstance(node, ast.AnnAssign)
|
|
412
|
+
and isinstance(node.target, ast.Name)
|
|
413
|
+
and isinstance(node.value, (ast.Name, ast.Attribute))
|
|
414
|
+
):
|
|
415
|
+
original = _terminal_name(node.value)
|
|
416
|
+
if original is not None:
|
|
417
|
+
aliases.setdefault(node.target.id, set()).add(original)
|
|
@@ -131,11 +131,12 @@ def _pytest_plugin_test_targets(tree: ast.Module) -> list[_PluginTarget]:
|
|
|
131
131
|
for statement in tree.body:
|
|
132
132
|
value: ast.expr | None = None
|
|
133
133
|
match statement:
|
|
134
|
-
case (
|
|
135
|
-
|
|
136
|
-
| ast.AnnAssign(target=ast.Name(id="pytest_plugins"))
|
|
134
|
+
case ast.Assign(targets=targets) if any(
|
|
135
|
+
isinstance(target, ast.Name) and target.id == "pytest_plugins" for target in targets
|
|
137
136
|
):
|
|
138
137
|
value = statement.value
|
|
138
|
+
case ast.AnnAssign(target=ast.Name(id="pytest_plugins")):
|
|
139
|
+
value = statement.value
|
|
139
140
|
case _:
|
|
140
141
|
continue
|
|
141
142
|
findings.extend(
|
|
@@ -177,7 +177,7 @@ class PreferMatchExceptionDispatch(Rule):
|
|
|
177
177
|
imports = context.imports
|
|
178
178
|
if (
|
|
179
179
|
not imports.builtin_is_unshadowed("isinstance")
|
|
180
|
-
or
|
|
180
|
+
or context.has_wildcard_import
|
|
181
181
|
or _has_local_import(tree, node_index=context.node_index)
|
|
182
182
|
):
|
|
183
183
|
return []
|
|
@@ -412,13 +412,6 @@ def _body_terminates(body: list[ast.stmt]) -> bool:
|
|
|
412
412
|
return False
|
|
413
413
|
|
|
414
414
|
|
|
415
|
-
def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
416
|
-
return any(
|
|
417
|
-
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
|
|
418
|
-
for node in walk_ast(tree, index=node_index)
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
|
|
422
415
|
def _has_local_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
423
416
|
module_imports = {id(statement) for statement in tree.body if isinstance(statement, (ast.Import, ast.ImportFrom))}
|
|
424
417
|
return any(
|
|
@@ -16,12 +16,10 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
Severity,
|
|
17
17
|
parse_or_none,
|
|
18
18
|
)
|
|
19
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
if TYPE_CHECKING:
|
|
23
22
|
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
25
23
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
26
24
|
|
|
27
25
|
|
|
@@ -101,7 +99,7 @@ class PreferNamedtupleOverTupleReturn(Rule):
|
|
|
101
99
|
if context.generated or _is_documentation_path(path):
|
|
102
100
|
return []
|
|
103
101
|
tree = context.tree
|
|
104
|
-
if tree is None or
|
|
102
|
+
if tree is None or context.has_wildcard_import:
|
|
105
103
|
return []
|
|
106
104
|
imports = context.imports
|
|
107
105
|
diagnostics: list[Diagnostic] = []
|
|
@@ -127,13 +125,6 @@ def _is_documentation_path(path: Path) -> bool:
|
|
|
127
125
|
return any(part.lower() in _DOCUMENTATION_DIR_NAMES for part in path.parts)
|
|
128
126
|
|
|
129
127
|
|
|
130
|
-
def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
131
|
-
return any(
|
|
132
|
-
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
|
|
133
|
-
for node in walk_ast(tree, index=node_index)
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
128
|
def _is_public_record_tuple(annotation: ast.expr, imports: ImportIndex) -> bool:
|
|
138
129
|
annotation = _unwrap_return_annotation(annotation, imports)
|
|
139
130
|
if not isinstance(annotation, ast.Subscript) or not isinstance(annotation.slice, ast.Tuple):
|
|
@@ -27,7 +27,6 @@ if TYPE_CHECKING:
|
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
29
|
from sarj_python_lint._file_context import PythonFileContext
|
|
30
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
31
30
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
32
31
|
|
|
33
32
|
|
|
@@ -221,7 +220,7 @@ class PreferNominalIdTypes(Rule):
|
|
|
221
220
|
if _is_excluded_path(path) or context.generated:
|
|
222
221
|
return []
|
|
223
222
|
tree = context.tree
|
|
224
|
-
if tree is None or
|
|
223
|
+
if tree is None or context.has_wildcard_import:
|
|
225
224
|
return []
|
|
226
225
|
|
|
227
226
|
imports = context.imports
|
|
@@ -311,13 +310,6 @@ def _is_excluded_path(path: Path) -> bool:
|
|
|
311
310
|
)
|
|
312
311
|
|
|
313
312
|
|
|
314
|
-
def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
315
|
-
return any(
|
|
316
|
-
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
|
|
317
|
-
for node in walk_ast(tree, index=node_index)
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
|
|
321
313
|
def _boundary_nodes(
|
|
322
314
|
tree: ast.Module,
|
|
323
315
|
imports: ImportIndex,
|
|
@@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
|
|
23
23
|
from collections.abc import Iterator
|
|
24
24
|
|
|
25
25
|
from sarj_python_lint._file_context import PythonFileContext
|
|
26
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
27
26
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
28
27
|
|
|
29
28
|
|
|
@@ -99,7 +98,7 @@ class PreferNonNullableCollection(Rule):
|
|
|
99
98
|
if is_test_path(path) or is_test_support_path(path) or context.generated:
|
|
100
99
|
return []
|
|
101
100
|
tree = context.tree
|
|
102
|
-
if tree is None or
|
|
101
|
+
if tree is None or context.has_wildcard_import:
|
|
103
102
|
return []
|
|
104
103
|
|
|
105
104
|
imports = context.imports
|
|
@@ -125,13 +124,6 @@ class PreferNonNullableCollection(Rule):
|
|
|
125
124
|
return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
|
|
126
125
|
|
|
127
126
|
|
|
128
|
-
def _has_wildcard_import(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
129
|
-
return any(
|
|
130
|
-
isinstance(node, ast.ImportFrom) and any(alias.name == "*" for alias in node.names)
|
|
131
|
-
for node in walk_ast(tree, index=node_index)
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
127
|
def _eligible_functions(tree: ast.Module, imports: ImportIndex) -> list[ast.FunctionDef | ast.AsyncFunctionDef]:
|
|
136
128
|
functions: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
|
|
137
129
|
for statement in tree.body:
|
|
@@ -17,7 +17,6 @@ from sarj_python_lint.rule_base import (
|
|
|
17
17
|
Severity,
|
|
18
18
|
is_suppressed,
|
|
19
19
|
)
|
|
20
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
21
20
|
from sarj_python_lint.rules._comments import is_protected, stem
|
|
22
21
|
from sarj_python_lint.rules._docstrings import (
|
|
23
22
|
VALUE_MARKER_RE,
|
|
@@ -27,11 +26,9 @@ from sarj_python_lint.rules._docstrings import (
|
|
|
27
26
|
|
|
28
27
|
if TYPE_CHECKING:
|
|
29
28
|
from sarj_python_lint._file_context import PythonFileContext
|
|
30
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
_GRAMMATICAL_FILLER = frozenset({"a", "an", "class", "the"})
|
|
34
|
-
_GETATTR_MIN_ARGS = 2
|
|
35
32
|
_DOCSTRING_TOKEN_RE = re.compile(r"[^\W\d_]+(?:'[^\W\d_]+)?|\d+")
|
|
36
33
|
|
|
37
34
|
|
|
@@ -93,7 +90,7 @@ class RedundantClassDocstring(Rule):
|
|
|
93
90
|
if tree is None:
|
|
94
91
|
return []
|
|
95
92
|
source_lines = context.source_lines
|
|
96
|
-
consumed_docstrings =
|
|
93
|
+
consumed_docstrings = context.consumed_docstring_names
|
|
97
94
|
diags: list[Diagnostic] = []
|
|
98
95
|
for node in context.nodes(ast.ClassDef):
|
|
99
96
|
if node.name not in consumed_docstrings and self._is_ceremony(node):
|
|
@@ -128,88 +125,3 @@ class RedundantClassDocstring(Rule):
|
|
|
128
125
|
if (word := match.group(0).lower()) not in _GRAMMATICAL_FILLER
|
|
129
126
|
]
|
|
130
127
|
return bool(content) and all(stem(word) in known for word in content)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _consumed_docstring_names(tree: ast.Module, *, node_index: NodeIndex | None = None) -> set[str]:
|
|
134
|
-
consumed: set[str] = set()
|
|
135
|
-
aliases: dict[str, set[str]] = {}
|
|
136
|
-
for node in walk_ast(tree, index=node_index):
|
|
137
|
-
_record_docstring_use(node, consumed, aliases)
|
|
138
|
-
changed = True
|
|
139
|
-
while changed:
|
|
140
|
-
changed = False
|
|
141
|
-
for alias, originals in aliases.items():
|
|
142
|
-
if alias not in consumed:
|
|
143
|
-
continue
|
|
144
|
-
before = len(consumed)
|
|
145
|
-
consumed.update(originals)
|
|
146
|
-
changed |= len(consumed) != before
|
|
147
|
-
return consumed
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _terminal_name(node: ast.expr) -> str | None:
|
|
151
|
-
match node:
|
|
152
|
-
case ast.Name():
|
|
153
|
-
return node.id
|
|
154
|
-
case ast.Attribute():
|
|
155
|
-
return node.attr
|
|
156
|
-
case _:
|
|
157
|
-
return None
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _record_docstring_use(node: ast.AST, consumed: set[str], aliases: dict[str, set[str]]) -> None:
|
|
161
|
-
if isinstance(node, ast.Attribute) and node.attr == "__doc__":
|
|
162
|
-
if name := _terminal_name(node.value):
|
|
163
|
-
consumed.add(name)
|
|
164
|
-
elif isinstance(node, ast.Call) and _is_docstring_reader(node):
|
|
165
|
-
if name := _terminal_name(node.args[0]):
|
|
166
|
-
consumed.add(name)
|
|
167
|
-
elif isinstance(node, ast.Subscript):
|
|
168
|
-
if name := _subscripted_docstring_owner(node):
|
|
169
|
-
consumed.add(name)
|
|
170
|
-
else:
|
|
171
|
-
_record_docstring_alias(node, aliases)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def _subscripted_docstring_owner(node: ast.Subscript) -> str | None:
|
|
175
|
-
if not isinstance(node.slice, ast.Constant) or node.slice.value != "__doc__":
|
|
176
|
-
return None
|
|
177
|
-
value = node.value
|
|
178
|
-
if isinstance(value, ast.Attribute) and value.attr == "__dict__":
|
|
179
|
-
return _terminal_name(value.value)
|
|
180
|
-
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "vars" and value.args:
|
|
181
|
-
return _terminal_name(value.args[0])
|
|
182
|
-
return None
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def _is_docstring_reader(node: ast.Call) -> bool:
|
|
186
|
-
if not node.args:
|
|
187
|
-
return False
|
|
188
|
-
function = node.func
|
|
189
|
-
if isinstance(function, ast.Name) and function.id == "help":
|
|
190
|
-
return True
|
|
191
|
-
if isinstance(function, ast.Attribute) and function.attr in {"getdoc", "render_doc"}:
|
|
192
|
-
return True
|
|
193
|
-
return (
|
|
194
|
-
isinstance(function, ast.Name)
|
|
195
|
-
and function.id == "getattr"
|
|
196
|
-
and len(node.args) >= _GETATTR_MIN_ARGS
|
|
197
|
-
and isinstance(node.args[1], ast.Constant)
|
|
198
|
-
and node.args[1].value == "__doc__"
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def _record_docstring_alias(node: ast.AST, aliases: dict[str, set[str]]) -> None:
|
|
203
|
-
if isinstance(node, ast.Assign) and isinstance(node.value, (ast.Name, ast.Attribute)):
|
|
204
|
-
original = _terminal_name(node.value)
|
|
205
|
-
for target in node.targets:
|
|
206
|
-
if isinstance(target, ast.Name) and original is not None:
|
|
207
|
-
aliases.setdefault(target.id, set()).add(original)
|
|
208
|
-
elif (
|
|
209
|
-
isinstance(node, ast.AnnAssign)
|
|
210
|
-
and isinstance(node.target, ast.Name)
|
|
211
|
-
and isinstance(node.value, (ast.Name, ast.Attribute))
|
|
212
|
-
):
|
|
213
|
-
original = _terminal_name(node.value)
|
|
214
|
-
if original is not None:
|
|
215
|
-
aliases.setdefault(node.target.id, set()).add(original)
|
|
@@ -18,7 +18,7 @@ from sarj_python_lint.rule_base import (
|
|
|
18
18
|
Severity,
|
|
19
19
|
is_suppressed,
|
|
20
20
|
)
|
|
21
|
-
from sarj_python_lint.rules._ast_index import children
|
|
21
|
+
from sarj_python_lint.rules._ast_index import children
|
|
22
22
|
from sarj_python_lint.rules._comments import is_protected, split_identifier, stem
|
|
23
23
|
from sarj_python_lint.rules._docstrings import (
|
|
24
24
|
VALUE_MARKER_RE,
|
|
@@ -28,12 +28,10 @@ from sarj_python_lint.rules._docstrings import (
|
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
30
|
from sarj_python_lint._file_context import PythonFileContext
|
|
31
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
_DOCSTRING_TOKEN_RE = re.compile(r"[^\W\d_]+(?:'[^\W\d_]+)?|\d+")
|
|
35
34
|
_GRAMMATICAL_FILLER = frozenset({"a", "an", "function", "method", "the"})
|
|
36
|
-
_GETATTR_MIN_ARGS = 2
|
|
37
35
|
_MULTI_PARAMETER_MIN = 2
|
|
38
36
|
|
|
39
37
|
|
|
@@ -41,7 +39,7 @@ _MULTI_PARAMETER_MIN = 2
|
|
|
41
39
|
class _ScanContext:
|
|
42
40
|
path: Path
|
|
43
41
|
source_lines: list[str]
|
|
44
|
-
consumed_docstrings:
|
|
42
|
+
consumed_docstrings: frozenset[str]
|
|
45
43
|
diagnostics: list[Diagnostic]
|
|
46
44
|
|
|
47
45
|
|
|
@@ -105,7 +103,7 @@ class RedundantDocstring(Rule):
|
|
|
105
103
|
if tree is None:
|
|
106
104
|
return []
|
|
107
105
|
source_lines = context.source_lines
|
|
108
|
-
consumed_docstrings =
|
|
106
|
+
consumed_docstrings = context.consumed_docstring_names
|
|
109
107
|
diags: list[Diagnostic] = []
|
|
110
108
|
analysis_context = _ScanContext(path, source_lines, consumed_docstrings, diags)
|
|
111
109
|
self._walk(tree, analysis_context)
|
|
@@ -190,88 +188,3 @@ def _parameters_are_distinguished(groups: list[set[str]], content: set[str]) ->
|
|
|
190
188
|
if not distinguishing or distinguishing.isdisjoint(content):
|
|
191
189
|
return False
|
|
192
190
|
return True
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _consumed_docstring_names(tree: ast.Module, *, node_index: NodeIndex | None = None) -> set[str]:
|
|
196
|
-
consumed: set[str] = set()
|
|
197
|
-
aliases: dict[str, set[str]] = {}
|
|
198
|
-
for node in walk_ast(tree, index=node_index):
|
|
199
|
-
_record_docstring_use(node, consumed, aliases)
|
|
200
|
-
changed = True
|
|
201
|
-
while changed:
|
|
202
|
-
changed = False
|
|
203
|
-
for alias, originals in aliases.items():
|
|
204
|
-
if alias not in consumed:
|
|
205
|
-
continue
|
|
206
|
-
before = len(consumed)
|
|
207
|
-
consumed.update(originals)
|
|
208
|
-
changed |= len(consumed) != before
|
|
209
|
-
return consumed
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def _terminal_name(node: ast.expr) -> str | None:
|
|
213
|
-
match node:
|
|
214
|
-
case ast.Name():
|
|
215
|
-
return node.id
|
|
216
|
-
case ast.Attribute():
|
|
217
|
-
return node.attr
|
|
218
|
-
case _:
|
|
219
|
-
return None
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def _record_docstring_use(node: ast.AST, consumed: set[str], aliases: dict[str, set[str]]) -> None:
|
|
223
|
-
if isinstance(node, ast.Attribute) and node.attr == "__doc__":
|
|
224
|
-
if name := _terminal_name(node.value):
|
|
225
|
-
consumed.add(name)
|
|
226
|
-
elif isinstance(node, ast.Call) and _is_docstring_reader(node):
|
|
227
|
-
if name := _terminal_name(node.args[0]):
|
|
228
|
-
consumed.add(name)
|
|
229
|
-
elif isinstance(node, ast.Subscript):
|
|
230
|
-
if name := _subscripted_docstring_owner(node):
|
|
231
|
-
consumed.add(name)
|
|
232
|
-
else:
|
|
233
|
-
_record_docstring_alias(node, aliases)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def _subscripted_docstring_owner(node: ast.Subscript) -> str | None:
|
|
237
|
-
if not isinstance(node.slice, ast.Constant) or node.slice.value != "__doc__":
|
|
238
|
-
return None
|
|
239
|
-
value = node.value
|
|
240
|
-
if isinstance(value, ast.Attribute) and value.attr == "__dict__":
|
|
241
|
-
return _terminal_name(value.value)
|
|
242
|
-
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "vars" and value.args:
|
|
243
|
-
return _terminal_name(value.args[0])
|
|
244
|
-
return None
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _is_docstring_reader(node: ast.Call) -> bool:
|
|
248
|
-
if not node.args:
|
|
249
|
-
return False
|
|
250
|
-
function = node.func
|
|
251
|
-
if isinstance(function, ast.Name) and function.id == "help":
|
|
252
|
-
return True
|
|
253
|
-
if isinstance(function, ast.Attribute) and function.attr in {"getdoc", "render_doc"}:
|
|
254
|
-
return True
|
|
255
|
-
return (
|
|
256
|
-
isinstance(function, ast.Name)
|
|
257
|
-
and function.id == "getattr"
|
|
258
|
-
and len(node.args) >= _GETATTR_MIN_ARGS
|
|
259
|
-
and isinstance(node.args[1], ast.Constant)
|
|
260
|
-
and node.args[1].value == "__doc__"
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def _record_docstring_alias(node: ast.AST, aliases: dict[str, set[str]]) -> None:
|
|
265
|
-
if isinstance(node, ast.Assign) and isinstance(node.value, (ast.Name, ast.Attribute)):
|
|
266
|
-
original = _terminal_name(node.value)
|
|
267
|
-
for target in node.targets:
|
|
268
|
-
if isinstance(target, ast.Name) and original is not None:
|
|
269
|
-
aliases.setdefault(target.id, set()).add(original)
|
|
270
|
-
elif (
|
|
271
|
-
isinstance(node, ast.AnnAssign)
|
|
272
|
-
and isinstance(node.target, ast.Name)
|
|
273
|
-
and isinstance(node.value, (ast.Name, ast.Attribute))
|
|
274
|
-
):
|
|
275
|
-
original = _terminal_name(node.value)
|
|
276
|
-
if original is not None:
|
|
277
|
-
aliases.setdefault(node.target.id, set()).add(original)
|
|
@@ -61,7 +61,10 @@ class RedundantTestConstructorForwarder(ProjectRule):
|
|
|
61
61
|
example_id=name,
|
|
62
62
|
title=title,
|
|
63
63
|
outcome=outcome,
|
|
64
|
-
files=(
|
|
64
|
+
files=(
|
|
65
|
+
ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
|
|
66
|
+
ExampleFile.python("tests/test_record.py", source),
|
|
67
|
+
),
|
|
65
68
|
focus_path=PurePosixPath("tests/test_record.py"),
|
|
66
69
|
expected_count=count,
|
|
67
70
|
public=True,
|
|
@@ -60,6 +60,7 @@ class RepeatedUnimplementedTestContract(ProjectRule):
|
|
|
60
60
|
title="Two test fakes repeat one unconfigured operation",
|
|
61
61
|
outcome=ExampleOutcome.MATCH,
|
|
62
62
|
files=(
|
|
63
|
+
ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
|
|
63
64
|
ExampleFile.python(
|
|
64
65
|
"tests/test_store.py",
|
|
65
66
|
"from abc import ABC, abstractmethod\nclass Store(ABC):\n @abstractmethod\n def read(self, key: str) -> str:\n pass\nclass First(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\nclass Second(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\n",
|
|
@@ -74,6 +75,7 @@ class RepeatedUnimplementedTestContract(ProjectRule):
|
|
|
74
75
|
title="Configured fake behavior belongs to the leaf",
|
|
75
76
|
outcome=ExampleOutcome.NO_MATCH,
|
|
76
77
|
files=(
|
|
78
|
+
ExampleFile.python("tests/__init__.py", "# Example test package.\n"),
|
|
77
79
|
ExampleFile.python(
|
|
78
80
|
"tests/test_store.py",
|
|
79
81
|
"from abc import ABC, abstractmethod\nclass Store(ABC):\n @abstractmethod\n def read(self, key: str) -> str:\n pass\nclass First(Store):\n def read(self, key: str) -> str:\n raise NotImplementedError\nclass Second(Store):\n def read(self, key: str) -> str:\n return key\n",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/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
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/interpreter_argv.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.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.28.2 → sarj_python_lint-1.28.4}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|