sarj-python-lint 1.77.3__tar.gz → 1.81.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/PKG-INFO +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/pyproject.toml +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/__main__.py +2 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix.py +7 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_comments.py +15 -56
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_docs.py +77 -14
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_inert.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/interpreter_argv.py +3 -5
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/json_boundary.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rule_base.py +1 -5
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_comment_narration.py +156 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_comments.py +37 -1
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +1 -1
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_fastapi.py +2 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +6 -1
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_native_object_contracts.py +734 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_nominal_project.py +37 -16
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_opaque_contracts.py +1264 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_project_index.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_prose_budget.py +11 -5
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_service_http_callbacks.py +403 -0
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_sql_aggregate_identity.py +356 -0
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_sql_explain.py +140 -0
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_sql_policy_source.py +32 -0
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/_transactional_sql.py +389 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +1 -3
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +1 -2
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/excessive_commentary.py +149 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +19 -10
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/mock_without_spec.py +3 -7
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +2 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +32 -67
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_async_function_cache.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_comment_cruft.py +112 -41
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +1 -3
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +25 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +3 -3
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +3 -3
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_partial_type_is.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +12 -16
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_restated_comment.py +5 -20
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_tautological_expect.py +15 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +101 -60
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/over_mocked_test.py +2 -5
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/phase_label_comment.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +4 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_library_fake.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +123 -8
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +76 -12
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +8 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +2 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +3 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_str_enum.py +2 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +1 -2
- sarj_python_lint-1.81.0/src/sarj_python_lint/rules/redundant_module_docstring.py +104 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +18 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +2 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +11 -6
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_port_for_service.py +2 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +12 -8
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +5 -4
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/stepdown.py +5 -10
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/trailing_value_narration.py +1 -2
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -2
- sarj_python_lint-1.77.3/src/sarj_python_lint/rules/excessive_commentary.py +0 -242
- sarj_python_lint-1.77.3/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -232
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/.gitignore +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/LICENSE +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/README.md +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/suppression_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.81.0
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
|
|
6
6
|
Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
|
|
@@ -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.17.2
|
|
22
|
-
Requires-Dist: sarj-rule-contracts==0.4.
|
|
22
|
+
Requires-Dist: sarj-rule-contracts==0.4.2
|
|
23
23
|
Requires-Dist: sqlglot==30.24.0
|
|
24
24
|
Requires-Dist: typer==0.27.3
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.81.0"
|
|
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.4.
|
|
18
|
+
dependencies = ["sarj-rule-contracts==0.4.2", "packaging==26.3", "ruff>=0.17.2", "sqlglot==30.24.0", "typer==0.27.3"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -166,6 +166,7 @@ def analyze(
|
|
|
166
166
|
|
|
167
167
|
_DIAGNOSTIC_PRECEDENCE = MappingProxyType(
|
|
168
168
|
{
|
|
169
|
+
"SARJ020": frozenset({"SARJ437"}),
|
|
169
170
|
"SARJ054": frozenset({"SARJ419"}),
|
|
170
171
|
"SARJ448": frozenset({"SARJ419"}),
|
|
171
172
|
"SARJ523": frozenset({"SARJ419"}),
|
|
@@ -340,14 +341,9 @@ def _suppressed_diagnostics(
|
|
|
340
341
|
|
|
341
342
|
def _suppressed_codes(codes: dict[str, set[Severity]]) -> set[tuple[str, Severity]]:
|
|
342
343
|
suppressed = _precedence_suppressions(codes)
|
|
343
|
-
# SARJ094
|
|
344
|
-
# FastAPI contract policy. Its route-aware return finding still owns the
|
|
345
|
-
# overlapping annotation failure: emitting the blocking SARJ447 twin would
|
|
346
|
-
# defeat warning staging and give two remediations for one return contract.
|
|
344
|
+
# SARJ094 warning staging owns the overlapping return failure; its blocking twin would defeat that policy.
|
|
347
345
|
if "SARJ094" in codes:
|
|
348
346
|
suppressed.update(_route_contract_suppressions(codes))
|
|
349
|
-
# The promoted signature-restatement rules now block. Keep their errors and
|
|
350
|
-
# drop only the lower-severity typed-docstring twin at the same owner.
|
|
351
347
|
if _typed_docstring_warning_is_superseded(codes):
|
|
352
348
|
suppressed.add(("SARJ092", Severity.WARNING))
|
|
353
349
|
return suppressed
|
|
@@ -33,6 +33,7 @@ from sarj_python_lint.rule_base import Severity
|
|
|
33
33
|
from sarj_python_lint.rules import (
|
|
34
34
|
REGISTRY,
|
|
35
35
|
duplicate_adjacent_exception_handler as exception_rule,
|
|
36
|
+
no_comment_cruft as comment_cruft_rule,
|
|
36
37
|
prefer_collection_comprehension as collection_rule,
|
|
37
38
|
prefer_or_pattern as pattern_rule,
|
|
38
39
|
)
|
|
@@ -201,7 +202,11 @@ def _comment_fix(source: _Source, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
|
201
202
|
if token is None:
|
|
202
203
|
return []
|
|
203
204
|
body = token.string.removeprefix("#").strip()
|
|
204
|
-
if
|
|
205
|
+
if (
|
|
206
|
+
diagnostic.code == "SARJ016"
|
|
207
|
+
and _SEPARATOR.fullmatch(body) is None
|
|
208
|
+
and not comment_cruft_rule.is_section_label(body)
|
|
209
|
+
):
|
|
205
210
|
return []
|
|
206
211
|
start = source.starts[token.start[0] - 1]
|
|
207
212
|
if source.text[start : start + token.start[1]].strip():
|
|
@@ -1312,7 +1317,7 @@ _BUILDERS: Final[dict[str, Callable[[_Source, Diagnostic], list[TextEdit]]]] = {
|
|
|
1312
1317
|
"SARJ011": _security_fix,
|
|
1313
1318
|
**dict.fromkeys(_COMMENT_CODES, _comment_fix),
|
|
1314
1319
|
**dict.fromkeys(_WALRUS_CODES, _walrus_fix),
|
|
1315
|
-
**dict.fromkeys({"SARJ050", "SARJ086", "SARJ087", "SARJ092", "SARJ420"}, _docstring_fix),
|
|
1320
|
+
**dict.fromkeys({"SARJ050", "SARJ086", "SARJ087", "SARJ092", "SARJ099", "SARJ420"}, _docstring_fix),
|
|
1316
1321
|
"SARJ023": _stepdown_fix,
|
|
1317
1322
|
"SARJ024": _repeated_fix,
|
|
1318
1323
|
"SARJ032": _exhaustiveness_fix,
|
{sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/_autofix_comments.py
RENAMED
|
@@ -9,7 +9,8 @@ from typing import TYPE_CHECKING, final
|
|
|
9
9
|
from sarj_rule_contracts import TextEdit
|
|
10
10
|
|
|
11
11
|
from sarj_python_lint.rule_base import Severity
|
|
12
|
-
from sarj_python_lint.rules.
|
|
12
|
+
from sarj_python_lint.rules._comment_narration import CommentNarration, same_words
|
|
13
|
+
from sarj_python_lint.rules._prose_budget import groups
|
|
13
14
|
from sarj_python_lint.rules.trailing_value_narration import UNIT_WORDS, number_key
|
|
14
15
|
|
|
15
16
|
|
|
@@ -18,16 +19,12 @@ if TYPE_CHECKING:
|
|
|
18
19
|
from sarj_python_lint.rule_base import Diagnostic
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
_IDENTIFIER = re.compile(r"[a-z]+(?:_[a-z]+)+\Z")
|
|
22
|
-
_WORDS = re.compile(r"[A-Za-z]+(?:\s+[A-Za-z]+)*\.?\Z")
|
|
23
22
|
_NUMBER_AND_UNIT = re.compile(r"([0-9][0-9_]*(?:\.[0-9_]+)?)\s*([A-Za-z]+)\Z")
|
|
24
23
|
_UNUSED_API_DIRECTIVE = re.compile(
|
|
25
24
|
r"#\s*ruff:\s*(?:noqa\s*:\s*TID251|file-ignore\s*\[\s*(?:TID251|banned-api)\s*\])\s*\Z",
|
|
26
25
|
re.IGNORECASE,
|
|
27
26
|
)
|
|
28
27
|
_MIN_PARAGRAPH_LINES = 4
|
|
29
|
-
_INITIALIZER_AND_RETURN_COUNT = 2
|
|
30
|
-
_ACCUMULATOR_METHODS = frozenset({"add", "append", "extend", "update"})
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
@final
|
|
@@ -46,6 +43,8 @@ class CommentFixes:
|
|
|
46
43
|
self.statements: dict[int, list[ast.stmt]] = {}
|
|
47
44
|
for statement in context.nodes(ast.stmt):
|
|
48
45
|
self.statements.setdefault(statement.lineno, []).append(statement)
|
|
46
|
+
self.prose_groups = {(group.line, group.col): group for group in groups(context.path, context.source)}
|
|
47
|
+
self.narration: CommentNarration | None = None
|
|
49
48
|
|
|
50
49
|
def fixes(self, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
51
50
|
token = self.comments.get(diagnostic.line)
|
|
@@ -82,7 +81,7 @@ class CommentFixes:
|
|
|
82
81
|
return (
|
|
83
82
|
statement.col_offset == comment.start[1]
|
|
84
83
|
and statement.lineno == statement.end_lineno
|
|
85
|
-
and
|
|
84
|
+
and same_words(comment.string.removeprefix("#").strip(), name)
|
|
86
85
|
)
|
|
87
86
|
case _:
|
|
88
87
|
return False
|
|
@@ -116,35 +115,19 @@ class CommentFixes:
|
|
|
116
115
|
return False
|
|
117
116
|
|
|
118
117
|
def _restates_accumulation(self, first: tokenize.TokenInfo) -> list[tokenize.TokenInfo]:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
while (comment := self.comments.get(line)) is not None:
|
|
122
|
-
if comment.start[1] != first.start[1] or self.lines[line - 1][: comment.start[1]].strip():
|
|
123
|
-
return []
|
|
124
|
-
paragraph.append(comment)
|
|
125
|
-
line += 1
|
|
126
|
-
if len(paragraph) < _MIN_PARAGRAPH_LINES:
|
|
118
|
+
group = self.prose_groups.get((first.start[0], first.start[1] + 1))
|
|
119
|
+
if group is None:
|
|
127
120
|
return []
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
owner = self.context.parents.get(initialized)
|
|
132
|
-
if not isinstance(owner, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
133
|
-
return []
|
|
134
|
-
if len(owner.body) != len(paragraph) + _INITIALIZER_AND_RETURN_COUNT or owner.body[0] is not initialized:
|
|
135
|
-
return []
|
|
136
|
-
returned = owner.body[-1]
|
|
121
|
+
if self.narration is None:
|
|
122
|
+
self.narration = CommentNarration(self.context)
|
|
123
|
+
matched = self.narration.match(group)
|
|
137
124
|
if (
|
|
138
|
-
not
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
matched is not None
|
|
126
|
+
and matched.kind == "accumulation"
|
|
127
|
+
and len(matched.comment_lines) >= _MIN_PARAGRAPH_LINES
|
|
141
128
|
):
|
|
142
|
-
return []
|
|
143
|
-
|
|
144
|
-
called = _accumulated_call(statement, name)
|
|
145
|
-
if called is None or not _same_words(comment, called):
|
|
146
|
-
return []
|
|
147
|
-
return paragraph
|
|
129
|
+
return [self.comments[line] for line in matched.comment_lines]
|
|
130
|
+
return []
|
|
148
131
|
|
|
149
132
|
def _remove(self, comment: tokenize.TokenInfo) -> TextEdit:
|
|
150
133
|
line = comment.start[0]
|
|
@@ -157,16 +140,6 @@ class CommentFixes:
|
|
|
157
140
|
)
|
|
158
141
|
|
|
159
142
|
|
|
160
|
-
def _same_words(comment: tokenize.TokenInfo, identifier: str) -> bool:
|
|
161
|
-
identifier = identifier.strip("_")
|
|
162
|
-
body = comment.string.removeprefix("#").strip()
|
|
163
|
-
return (
|
|
164
|
-
_IDENTIFIER.fullmatch(identifier) is not None
|
|
165
|
-
and _WORDS.fullmatch(body) is not None
|
|
166
|
-
and body.removesuffix(".").casefold().split() == identifier.split("_")
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
143
|
def _literal_name_assignment(statement: ast.stmt) -> bool:
|
|
171
144
|
return (
|
|
172
145
|
isinstance(statement, ast.Assign)
|
|
@@ -175,17 +148,3 @@ def _literal_name_assignment(statement: ast.stmt) -> bool:
|
|
|
175
148
|
and isinstance(statement.value, ast.Constant)
|
|
176
149
|
and isinstance(statement.value.value, (str, bytes, int, float, complex, bool, type(None)))
|
|
177
150
|
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def _accumulated_call(statement: ast.stmt, collection: str) -> str | None:
|
|
181
|
-
match statement:
|
|
182
|
-
case ast.Expr(
|
|
183
|
-
value=ast.Call(
|
|
184
|
-
func=ast.Attribute(value=ast.Name(id=name), attr=method),
|
|
185
|
-
args=[ast.Call(func=ast.Name(id=called), args=[], keywords=[])],
|
|
186
|
-
keywords=[],
|
|
187
|
-
)
|
|
188
|
-
):
|
|
189
|
-
return called if name == collection and method in _ACCUMULATOR_METHODS else None
|
|
190
|
-
case _:
|
|
191
|
-
return None
|
|
@@ -2,8 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from functools import cached_property
|
|
5
|
-
from io import StringIO
|
|
5
|
+
from io import BytesIO, StringIO
|
|
6
6
|
import re
|
|
7
|
+
import tokenize
|
|
7
8
|
from typing import TYPE_CHECKING, final
|
|
8
9
|
|
|
9
10
|
from sarj_rule_contracts import TextEdit
|
|
@@ -12,8 +13,10 @@ from sarj_python_lint._autofix_inert import inert_private_scope
|
|
|
12
13
|
from sarj_python_lint.rule_base import Severity
|
|
13
14
|
from sarj_python_lint.rules import REGISTRY
|
|
14
15
|
from sarj_python_lint.rules._closed_helpers import dynamic_namespace
|
|
15
|
-
from sarj_python_lint.rules.
|
|
16
|
+
from sarj_python_lint.rules._comments import has_legal_notice
|
|
17
|
+
from sarj_python_lint.rules._docstrings import ARG_SECTIONS, docstring_expression, section_ranges
|
|
16
18
|
from sarj_python_lint.rules._prose_budget import groups
|
|
19
|
+
from sarj_python_lint.rules.no_unnecessary_docstring import module_docstring_may_escape
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
if TYPE_CHECKING:
|
|
@@ -178,6 +181,13 @@ class DocstringFixes:
|
|
|
178
181
|
)
|
|
179
182
|
|
|
180
183
|
def fixes(self, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
184
|
+
if diagnostic.code in {"SARJ099", "SARJ420"}:
|
|
185
|
+
module_edits = self._module(diagnostic)
|
|
186
|
+
if module_edits:
|
|
187
|
+
return module_edits
|
|
188
|
+
return self._function(diagnostic)
|
|
189
|
+
|
|
190
|
+
def _function(self, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
181
191
|
for owner in self.context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
|
|
182
192
|
if not owner.body or not private_unescaped_function(owner, self.context):
|
|
183
193
|
continue
|
|
@@ -196,33 +206,87 @@ class DocstringFixes:
|
|
|
196
206
|
return self._labels(expression, diagnostic)
|
|
197
207
|
return []
|
|
198
208
|
|
|
209
|
+
def _module(self, diagnostic: Diagnostic) -> list[TextEdit]:
|
|
210
|
+
tree = self.context.tree
|
|
211
|
+
if tree is None or not tree.body:
|
|
212
|
+
return []
|
|
213
|
+
expression = docstring_expression(tree)
|
|
214
|
+
if expression is None:
|
|
215
|
+
return []
|
|
216
|
+
if (
|
|
217
|
+
(expression.lineno, expression.col_offset + 1) not in self.unconsumed
|
|
218
|
+
or diagnostic.line != expression.lineno
|
|
219
|
+
or self._standalone(expression) is None
|
|
220
|
+
):
|
|
221
|
+
return []
|
|
222
|
+
if (
|
|
223
|
+
dynamic_namespace(self.context)
|
|
224
|
+
or frame_metadata_observed(self.context)
|
|
225
|
+
or any(node.attr == "modules" for node in self.context.nodes(ast.Attribute))
|
|
226
|
+
or any(node.attr == "import_module" for node in self.context.nodes(ast.Attribute))
|
|
227
|
+
or any(node.name == "import_module" for node in self.context.nodes(ast.alias))
|
|
228
|
+
):
|
|
229
|
+
return []
|
|
230
|
+
if module_docstring_may_escape(self.context):
|
|
231
|
+
return []
|
|
232
|
+
if has_legal_notice(ast.get_docstring(tree, clean=False) or ""):
|
|
233
|
+
return []
|
|
234
|
+
if not self._module_header_preserved(expression):
|
|
235
|
+
return []
|
|
236
|
+
return self._remove(tree, expression)
|
|
237
|
+
|
|
238
|
+
def _module_header_preserved(self, expression: ast.Expr) -> bool:
|
|
239
|
+
end_line = expression.end_lineno or expression.lineno
|
|
240
|
+
if any(
|
|
241
|
+
token.type == tokenize.COMMENT and expression.lineno <= token.start[0] <= end_line
|
|
242
|
+
for token in tokenize.generate_tokens(StringIO(self.context.source, newline="").readline)
|
|
243
|
+
):
|
|
244
|
+
return False
|
|
245
|
+
original = self.context.source
|
|
246
|
+
candidate = original[: self.starts[expression.lineno - 1]] + original[self.starts[end_line] :]
|
|
247
|
+
if candidate.startswith("#!") != original.startswith("#!"):
|
|
248
|
+
return False
|
|
249
|
+
try:
|
|
250
|
+
original_encoding = tokenize.detect_encoding(BytesIO(original.encode("utf-8")).readline)[0]
|
|
251
|
+
candidate_encoding = tokenize.detect_encoding(BytesIO(candidate.encode("utf-8")).readline)[0]
|
|
252
|
+
except SyntaxError, LookupError:
|
|
253
|
+
return False
|
|
254
|
+
return original_encoding == candidate_encoding
|
|
255
|
+
|
|
199
256
|
def _literal(self, expression: ast.Expr) -> tuple[int, int, str] | None:
|
|
257
|
+
bounds = self._standalone(expression)
|
|
258
|
+
if bounds is None:
|
|
259
|
+
return None
|
|
260
|
+
start, end = bounds
|
|
261
|
+
literal = self.context.source[start:end]
|
|
262
|
+
if literal[:3] not in {'"""', "'''"} or literal[-3:] != literal[:3] or "\\" in literal:
|
|
263
|
+
return None
|
|
264
|
+
return start, end, literal
|
|
265
|
+
|
|
266
|
+
def _standalone(self, expression: ast.Expr) -> tuple[int, int] | None:
|
|
200
267
|
end_line = expression.end_lineno
|
|
201
268
|
end_column = expression.end_col_offset
|
|
202
269
|
if end_line is None or end_column is None:
|
|
203
270
|
return None
|
|
204
271
|
start = self._offset(expression.lineno, expression.col_offset)
|
|
205
272
|
end = self._offset(end_line, end_column)
|
|
206
|
-
literal = self.context.source[start:end]
|
|
207
|
-
if literal[:3] not in {'"""', "'''"} or literal[-3:] != literal[:3] or "\\" in literal:
|
|
208
|
-
return None
|
|
209
273
|
if self.context.source[self.starts[expression.lineno - 1] : start].strip():
|
|
210
274
|
return None
|
|
211
275
|
if self.context.source[end : self.starts[end_line]].strip():
|
|
212
276
|
return None
|
|
213
|
-
return start, end
|
|
277
|
+
return start, end
|
|
214
278
|
|
|
215
279
|
def _offset(self, line: int, byte_column: int) -> int:
|
|
216
280
|
return self.starts[line - 1] + len(self.lines[line - 1].encode("utf-8")[:byte_column].decode("utf-8"))
|
|
217
281
|
|
|
218
|
-
def _remove(self, owner: _Function, expression: ast.Expr) -> list[TextEdit]:
|
|
219
|
-
if not owner.body[1:]:
|
|
282
|
+
def _remove(self, owner: ast.Module | _Function, expression: ast.Expr) -> list[TextEdit]:
|
|
283
|
+
if not owner.body[1:] and not isinstance(owner, ast.Module):
|
|
220
284
|
return []
|
|
221
|
-
next_statement = owner.body[1]
|
|
222
285
|
if (
|
|
223
|
-
|
|
224
|
-
and isinstance(
|
|
225
|
-
and isinstance(
|
|
286
|
+
len(owner.body) > 1
|
|
287
|
+
and isinstance(owner.body[1], ast.Expr)
|
|
288
|
+
and isinstance(owner.body[1].value, ast.Constant)
|
|
289
|
+
and isinstance(owner.body[1].value.value, str)
|
|
226
290
|
):
|
|
227
291
|
return []
|
|
228
292
|
end_line = expression.end_lineno or expression.lineno
|
|
@@ -239,8 +303,7 @@ class DocstringFixes:
|
|
|
239
303
|
else frozenset({"Returns", "Return", "Yields", "Yield"})
|
|
240
304
|
)
|
|
241
305
|
ranges = [(start, end) for name, start, end in section_ranges(value.value) if name in wanted]
|
|
242
|
-
#
|
|
243
|
-
# Other heading variants can carry independent, meaningful contracts.
|
|
306
|
+
# Other section headings may carry independent contracts; only the detector-owned heading can be removed.
|
|
244
307
|
if len(ranges) != 1:
|
|
245
308
|
return []
|
|
246
309
|
document = value.value
|
|
@@ -74,7 +74,7 @@ _PRIMITIVE_CLASSES: Final[dict[str, type[object]]] = {
|
|
|
74
74
|
@dataclass(slots=True)
|
|
75
75
|
class _Value:
|
|
76
76
|
kind: _Kind
|
|
77
|
-
literal:
|
|
77
|
+
literal: bool | int | float | complex | str | bytes | None = None
|
|
78
78
|
known: bool = False
|
|
79
79
|
items: list[_Value] = field(default_factory=list)
|
|
80
80
|
function: _Function | None = None
|
|
@@ -108,7 +108,7 @@ def _constant(value: object) -> _Value:
|
|
|
108
108
|
bytes: _Kind.BYTES,
|
|
109
109
|
}
|
|
110
110
|
kind = kinds.get(type(value))
|
|
111
|
-
if kind is None:
|
|
111
|
+
if kind is None or not isinstance(value, bool | int | float | complex | str | bytes | None):
|
|
112
112
|
raise _UnsafeError
|
|
113
113
|
return _Value(kind, value, known=True)
|
|
114
114
|
|
{sarj_python_lint-1.77.3 → sarj_python_lint-1.81.0}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
@@ -122,7 +122,7 @@ _MAX_WRAPPERS = 8
|
|
|
122
122
|
|
|
123
123
|
|
|
124
124
|
class UnprovableCommandError(ValueError):
|
|
125
|
-
|
|
125
|
+
pass
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
def unwrap_command(
|
|
@@ -210,8 +210,7 @@ def _env_wrapper_index(arguments: Sequence[str]) -> int:
|
|
|
210
210
|
index += 1
|
|
211
211
|
break
|
|
212
212
|
index += _env_option_size(arguments, index)
|
|
213
|
-
#
|
|
214
|
-
# are executable operands, not additional env options.
|
|
213
|
+
# After env assignments begin, option-looking words are executable operands, including after --.
|
|
215
214
|
while index < len(arguments) and "=" in arguments[index]:
|
|
216
215
|
if arguments[index].startswith("="):
|
|
217
216
|
msg = "env assignment requires a nonempty variable name"
|
|
@@ -484,8 +483,7 @@ def _shell_short_option(arguments: Sequence[str], index: int, values: frozenset[
|
|
|
484
483
|
for flag in flags:
|
|
485
484
|
if flag in values:
|
|
486
485
|
operand = _following_operand(arguments, index + consumed - 1)
|
|
487
|
-
# Bash shopt (-O)
|
|
488
|
-
# it unproven until a native option contract is available.
|
|
486
|
+
# Bash shopt (-O) uses a separate versioned namespace; no native contract is proven here.
|
|
489
487
|
if flag == "O" or operand not in _SHELL_NAMED_OPTIONS:
|
|
490
488
|
return ParsedOption(InterpreterInvocation("unknown"))
|
|
491
489
|
consumed += 1
|
|
@@ -10,8 +10,8 @@ def parse_json(text: str) -> object:
|
|
|
10
10
|
raise ValueError(message) from error
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def _unique_pairs(pairs: list[tuple[str,
|
|
14
|
-
result: dict[str,
|
|
13
|
+
def _unique_pairs[T](pairs: list[tuple[str, T]]) -> dict[str, T]:
|
|
14
|
+
result: dict[str, T] = {}
|
|
15
15
|
for key, value in pairs:
|
|
16
16
|
if key in result:
|
|
17
17
|
message = f"duplicate JSON key: {key}"
|
|
@@ -77,11 +77,7 @@ class ColumnEncoding(StrEnum):
|
|
|
77
77
|
|
|
78
78
|
@dataclass(frozen=True, slots=True)
|
|
79
79
|
class TextEdit:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Columns refer to decoded source without its original leading BOM.
|
|
83
|
-
"""
|
|
84
|
-
|
|
80
|
+
# Coordinates are one-based UTF-8 bytes after BOM removal; end positions are exclusive.
|
|
85
81
|
start_line: int
|
|
86
82
|
start_col: int
|
|
87
83
|
end_line: int
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from bisect import bisect_left
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING, Literal, NamedTuple, final
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.rules._comments import comment_line_numbers, has_narration_qualifier, is_protected
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
13
|
+
from sarj_python_lint.rules._prose_budget import ProseGroup
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_IDENTIFIER = re.compile(r"[a-z]+(?:_[a-z]+)+\Z")
|
|
17
|
+
_WORDS = re.compile(r"[A-Za-z]+(?:\s+[A-Za-z]+)*\.?\Z")
|
|
18
|
+
_ACCUMULATOR_METHODS = frozenset({"add", "append", "extend", "update"})
|
|
19
|
+
_MIN_LINES = 2
|
|
20
|
+
_MIN_WORDS = 12
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NarrationMatch(NamedTuple):
|
|
24
|
+
kind: Literal["accumulation", "actions"]
|
|
25
|
+
comment_lines: tuple[int, ...]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@final
|
|
29
|
+
class CommentNarration:
|
|
30
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
31
|
+
self.context = context
|
|
32
|
+
self.comment_lines = tuple(sorted(comment_line_numbers(context.source)))
|
|
33
|
+
self.statements: dict[int, list[ast.stmt]] = {}
|
|
34
|
+
for statement in context.nodes(ast.stmt):
|
|
35
|
+
self.statements.setdefault(statement.lineno, []).append(statement)
|
|
36
|
+
|
|
37
|
+
def match(self, group: ProseGroup) -> NarrationMatch | None:
|
|
38
|
+
lines = group.text.splitlines()
|
|
39
|
+
if (
|
|
40
|
+
group.kind != "comment"
|
|
41
|
+
or len(lines) < _MIN_LINES
|
|
42
|
+
or len(group.text.split()) < _MIN_WORDS
|
|
43
|
+
or any(not line.strip() for line in lines)
|
|
44
|
+
or any(has_narration_qualifier(line) or is_protected(line) for line in lines)
|
|
45
|
+
):
|
|
46
|
+
return None
|
|
47
|
+
following = self.statements.get(group.line + len(lines), [])
|
|
48
|
+
if len(following) != 1:
|
|
49
|
+
return None
|
|
50
|
+
first = following[0]
|
|
51
|
+
owner = self.context.parents.get(first)
|
|
52
|
+
if (
|
|
53
|
+
not isinstance(owner, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef))
|
|
54
|
+
or owner.body[0] is not first
|
|
55
|
+
or first.col_offset != group.col - 1
|
|
56
|
+
or self._intervening_comment(first, owner.body[-1])
|
|
57
|
+
):
|
|
58
|
+
return None
|
|
59
|
+
if self._accumulation(owner, lines):
|
|
60
|
+
return NarrationMatch("accumulation", tuple(range(group.line, group.line + len(lines))))
|
|
61
|
+
if (
|
|
62
|
+
len(owner.body) == len(lines)
|
|
63
|
+
and not any(isinstance(node, ast.Return) for node in owner.body[:-1])
|
|
64
|
+
and all(
|
|
65
|
+
(called := _direct_action_call(statement)) is not None and same_words(line, called)
|
|
66
|
+
for line, statement in zip(lines, owner.body, strict=True)
|
|
67
|
+
)
|
|
68
|
+
):
|
|
69
|
+
return NarrationMatch("actions", tuple(range(group.line, group.line + len(lines))))
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def _intervening_comment(self, first: ast.stmt, last: ast.stmt) -> bool:
|
|
73
|
+
index = bisect_left(self.comment_lines, first.lineno)
|
|
74
|
+
return index < len(self.comment_lines) and self.comment_lines[index] <= (last.end_lineno or last.lineno)
|
|
75
|
+
|
|
76
|
+
def _accumulation(self, owner: ast.Module | ast.FunctionDef | ast.AsyncFunctionDef, lines: list[str]) -> bool:
|
|
77
|
+
if not isinstance(owner, (ast.FunctionDef, ast.AsyncFunctionDef)) or len(owner.body) != len(lines) + 2:
|
|
78
|
+
return False
|
|
79
|
+
name = empty_collection_name(owner.body[0], self.context)
|
|
80
|
+
returned = owner.body[-1]
|
|
81
|
+
if (
|
|
82
|
+
name is None
|
|
83
|
+
or not isinstance(returned, ast.Return)
|
|
84
|
+
or not isinstance(returned.value, ast.Name)
|
|
85
|
+
or returned.value.id != name
|
|
86
|
+
):
|
|
87
|
+
return False
|
|
88
|
+
return all(
|
|
89
|
+
(called := _accumulated_call(statement, name)) is not None and same_words(line, called)
|
|
90
|
+
for line, statement in zip(lines, owner.body[1:-1], strict=True)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def same_words(body: str, identifier: str) -> bool:
|
|
95
|
+
identifier = identifier.strip("_")
|
|
96
|
+
return (
|
|
97
|
+
_IDENTIFIER.fullmatch(identifier) is not None
|
|
98
|
+
and _WORDS.fullmatch(body) is not None
|
|
99
|
+
and body.removesuffix(".").casefold().split() == identifier.split("_")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def empty_collection_name(statement: ast.stmt, context: PythonFileContext) -> str | None:
|
|
104
|
+
target: ast.expr | None = None
|
|
105
|
+
value: ast.expr | None = None
|
|
106
|
+
if isinstance(statement, ast.AnnAssign):
|
|
107
|
+
target, value = statement.target, statement.value
|
|
108
|
+
elif isinstance(statement, ast.Assign) and len(statement.targets) == 1:
|
|
109
|
+
target, value = statement.targets[0], statement.value
|
|
110
|
+
if not isinstance(target, ast.Name) or value is None:
|
|
111
|
+
return None
|
|
112
|
+
if isinstance(value, (ast.List, ast.Set)) and not value.elts:
|
|
113
|
+
return target.id
|
|
114
|
+
if isinstance(value, ast.Dict) and not value.keys:
|
|
115
|
+
return target.id
|
|
116
|
+
if (
|
|
117
|
+
isinstance(value, ast.Call)
|
|
118
|
+
and isinstance(value.func, ast.Name)
|
|
119
|
+
and value.func.id in {"dict", "list", "set"}
|
|
120
|
+
and not value.args
|
|
121
|
+
and not value.keywords
|
|
122
|
+
):
|
|
123
|
+
provenance = context.static_resource_provenance
|
|
124
|
+
if provenance.builtin_namespace_untouched and provenance.binding(value.func.id, value.func) is None:
|
|
125
|
+
return target.id
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _direct_action_call(statement: ast.stmt) -> str | None:
|
|
130
|
+
match statement:
|
|
131
|
+
case ast.Expr(value=value) | ast.Assign(targets=[ast.Name()], value=value) | ast.Return(value=value):
|
|
132
|
+
return _zero_argument_call(value)
|
|
133
|
+
case _:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _zero_argument_call(value: ast.AST | None) -> str | None:
|
|
138
|
+
match value:
|
|
139
|
+
case ast.Call(func=ast.Name(id=called), args=[], keywords=[]):
|
|
140
|
+
return called
|
|
141
|
+
case _:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _accumulated_call(statement: ast.stmt, collection: str) -> str | None:
|
|
146
|
+
match statement:
|
|
147
|
+
case ast.Expr(
|
|
148
|
+
value=ast.Call(
|
|
149
|
+
func=ast.Attribute(value=ast.Name(id=name), attr=method),
|
|
150
|
+
args=[value],
|
|
151
|
+
keywords=[],
|
|
152
|
+
)
|
|
153
|
+
):
|
|
154
|
+
return _zero_argument_call(value) if name == collection and method in _ACCUMULATOR_METHODS else None
|
|
155
|
+
case _:
|
|
156
|
+
return None
|
|
@@ -16,6 +16,16 @@ if TYPE_CHECKING:
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
_LICENSE_RE = re.compile(
|
|
20
|
+
r"copyright|license|licen[cs]ed|spdx|permission is hereby granted|all rights reserved",
|
|
21
|
+
re.IGNORECASE,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def has_legal_notice(body: str) -> bool:
|
|
26
|
+
return bool(_LICENSE_RE.search(body))
|
|
27
|
+
|
|
28
|
+
|
|
19
29
|
# S1 — external references: URLs, tickets, RFCs, PEPs, CVEs, issues, and handles.
|
|
20
30
|
_REF_RE = re.compile(
|
|
21
31
|
r"https?://|\bRFC[- ]?\d+|\bPEP[- ]?\d+|\bCVE-\d{4}|"
|
|
@@ -57,7 +67,7 @@ _NEGATION_RE = re.compile(
|
|
|
57
67
|
# S6 — upstream/vendor quirk, workaround provenance, or an external contract.
|
|
58
68
|
_UPSTREAM_RE = re.compile(
|
|
59
69
|
r"\b(?:upstream|workaround|quirk|backport|vendored|regression|fixed upstream|"
|
|
60
|
-
r"
|
|
70
|
+
r"require[sd]?\b|convention\b|rate.?limit|deprecat|opts? in(?:to)?\b|"
|
|
61
71
|
r"raises?\b.{0,60}\b(?:when|if|unless)\b)",
|
|
62
72
|
re.IGNORECASE,
|
|
63
73
|
)
|
|
@@ -113,6 +123,32 @@ def has_external_reference(body: str) -> bool:
|
|
|
113
123
|
return bool(_REF_RE.search(body))
|
|
114
124
|
|
|
115
125
|
|
|
126
|
+
_NARRATION_MODALITY_RE = re.compile(r"\b(?:can|could|should|shall|may|might|must|will|would|cannot)\b", re.IGNORECASE)
|
|
127
|
+
_NARRATION_LEAD_IN_RE = re.compile(r":$")
|
|
128
|
+
_NARRATION_EMPHASIS_RE = re.compile(r"\*\w[^*]*\*|`[^`]+`")
|
|
129
|
+
_NARRATION_NEGATION_RE = re.compile(r"\b(?:no|not|never|neither|nor|without|none|non)\b", re.IGNORECASE)
|
|
130
|
+
_NARRATION_QUALIFIER_RE = re.compile(
|
|
131
|
+
r"\b(?:all|any|both|each|every|some|only|need|needs|needed|require|requires|required)\b|"
|
|
132
|
+
r"^\s*(?:and|after|before|finally|given|next|then|when)\b",
|
|
133
|
+
re.IGNORECASE,
|
|
134
|
+
)
|
|
135
|
+
_NARRATION_NAVIGATION_RE = re.compile(r"^\s*(?:sheet|slide|step)\s+\d+\s*(?:[:.\-]|$)", re.IGNORECASE)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def has_narration_qualifier(body: str) -> bool:
|
|
139
|
+
return any(
|
|
140
|
+
pattern.search(body)
|
|
141
|
+
for pattern in (
|
|
142
|
+
_NARRATION_MODALITY_RE,
|
|
143
|
+
_NARRATION_LEAD_IN_RE,
|
|
144
|
+
_NARRATION_EMPHASIS_RE,
|
|
145
|
+
_NARRATION_NEGATION_RE,
|
|
146
|
+
_NARRATION_QUALIFIER_RE,
|
|
147
|
+
_NARRATION_NAVIGATION_RE,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
116
152
|
# Below this length an inflection strip would eat the word itself.
|
|
117
153
|
_MIN_STEM_LENGTH = 3
|
|
118
154
|
|
|
@@ -124,7 +124,7 @@ class _DataUnions:
|
|
|
124
124
|
message=f"Data union `{name}` has no shared disjoint Literal discriminator. "
|
|
125
125
|
"Add a common field such as `kind` or `scope` with non-overlapping Literal values "
|
|
126
126
|
"on every variant; preserve constructor and serialized contracts when choosing defaults.",
|
|
127
|
-
severity=Severity.
|
|
127
|
+
severity=Severity.ERROR,
|
|
128
128
|
)
|
|
129
129
|
)
|
|
130
130
|
return findings
|