sarj-python-lint 1.79.0__tar.gz → 1.82.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.79.0 → sarj_python_lint-1.82.0}/PKG-INFO +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/pyproject.toml +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__main__.py +1 -6
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix.py +7 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_comments.py +15 -56
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_docs.py +77 -14
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_inert.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/interpreter_argv.py +3 -5
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/json_boundary.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rule_base.py +1 -5
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_async_lifecycle.py +124 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_comment_narration.py +156 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_comments.py +37 -1
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +1 -1
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fastapi.py +2 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +6 -1
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_native_object_contracts.py +734 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_nominal_project.py +6 -1
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_numeric_proof.py +171 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/_opaque_contracts.py +1264 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_project_index.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_prose_budget.py +11 -5
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_registry.py +10 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_executor_shutdown.py +135 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/async_subprocess_cancellation_cleanup.py +221 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/cancel_owned_task_without_join.py +161 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +1 -3
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +1 -2
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/excessive_commentary.py +149 -0
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/failed_probe_zero_threshold.py +289 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +19 -10
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/mock_without_spec.py +3 -7
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +2 -4
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/nan_hidden_by_builtin_min.py +370 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_async_function_cache.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_comment_cruft.py +112 -41
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +1 -3
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +25 -6
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +3 -3
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +3 -3
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_partial_type_is.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_local_alias.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +12 -16
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_restated_comment.py +5 -20
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_tautological_expect.py +15 -6
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +101 -60
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/over_mocked_test.py +2 -5
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/phase_label_comment.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +4 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_library_fake.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +123 -8
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +76 -12
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +8 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +2 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +3 -6
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_str_enum.py +2 -6
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +1 -2
- sarj_python_lint-1.82.0/src/sarj_python_lint/rules/redundant_module_docstring.py +104 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +18 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +2 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_port_for_service.py +2 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +12 -8
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +5 -4
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/stepdown.py +5 -10
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/trailing_value_narration.py +1 -2
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -2
- sarj_python_lint-1.79.0/src/sarj_python_lint/rules/excessive_commentary.py +0 -242
- sarj_python_lint-1.79.0/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -232
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/.gitignore +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/LICENSE +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/README.md +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_aliases.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_annotations.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_contracts.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_declared_tests.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_enums.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_exhaustiveness.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_parameters.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_repeated.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_security.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_sql_projection.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_autofix_tests.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_fixes.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_parallel.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/application_logging.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/autofix.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_alias_fix.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_service_http_callbacks.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_aggregate_identity.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_explain.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_sql_policy_source.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_transactional_sql.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_grouped_settings.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.79.0 → sarj_python_lint-1.82.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.82.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.82.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"
|
|
@@ -341,14 +341,9 @@ def _suppressed_diagnostics(
|
|
|
341
341
|
|
|
342
342
|
def _suppressed_codes(codes: dict[str, set[Severity]]) -> set[tuple[str, Severity]]:
|
|
343
343
|
suppressed = _precedence_suppressions(codes)
|
|
344
|
-
# SARJ094
|
|
345
|
-
# FastAPI contract policy. Its route-aware return finding still owns the
|
|
346
|
-
# overlapping annotation failure: emitting the blocking SARJ447 twin would
|
|
347
|
-
# 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.
|
|
348
345
|
if "SARJ094" in codes:
|
|
349
346
|
suppressed.update(_route_contract_suppressions(codes))
|
|
350
|
-
# The promoted signature-restatement rules now block. Keep their errors and
|
|
351
|
-
# drop only the lower-severity typed-docstring twin at the same owner.
|
|
352
347
|
if _typed_docstring_warning_is_superseded(codes):
|
|
353
348
|
suppressed.add(("SARJ092", Severity.WARNING))
|
|
354
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.79.0 → sarj_python_lint-1.82.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.79.0 → sarj_python_lint-1.82.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,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, final
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rules._resource_provenance import import_name_for_binding
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_INTROSPECTION = frozenset({"__dict__", "__setattr__", "__delattr__", "__getattribute__"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class _LocalAssignment:
|
|
19
|
+
binding: ast.Name
|
|
20
|
+
value: ast.expr
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
class AsyncApiProof:
|
|
25
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
26
|
+
self.context = context
|
|
27
|
+
self.provenance = context.static_resource_provenance
|
|
28
|
+
self._namespace_safety: dict[str, bool] = {}
|
|
29
|
+
self._origins: dict[str, set[str]] = {}
|
|
30
|
+
for node in context.nodes(ast.Import, ast.ImportFrom):
|
|
31
|
+
if isinstance(node, ast.Import):
|
|
32
|
+
for alias in node.names:
|
|
33
|
+
name = alias.asname or alias.name.partition(".")[0]
|
|
34
|
+
target = alias.name if alias.asname else alias.name.partition(".")[0]
|
|
35
|
+
self._origins.setdefault(name, set()).add(target)
|
|
36
|
+
elif node.level == 0 and node.module is not None:
|
|
37
|
+
for alias in node.names:
|
|
38
|
+
self._origins.setdefault(alias.asname or alias.name, set()).add(f"{node.module}.{alias.name}")
|
|
39
|
+
|
|
40
|
+
def imported(self, expression: ast.expr, qualified: str) -> bool:
|
|
41
|
+
root = expression
|
|
42
|
+
while isinstance(root, ast.Attribute):
|
|
43
|
+
root = root.value
|
|
44
|
+
if not isinstance(root, ast.Name):
|
|
45
|
+
return False
|
|
46
|
+
if (
|
|
47
|
+
self.context.dynamic_namespace
|
|
48
|
+
or self.provenance.wildcard
|
|
49
|
+
or root.id in self.provenance.mutated
|
|
50
|
+
or root.id in self.provenance.external_bindings
|
|
51
|
+
):
|
|
52
|
+
return False
|
|
53
|
+
binding = self.provenance.binding(root.id, expression)
|
|
54
|
+
if (
|
|
55
|
+
not isinstance(binding, ast.alias)
|
|
56
|
+
or not isinstance(self.provenance.scope(binding), ast.Module)
|
|
57
|
+
or import_name_for_binding(expression, binding, self.context.parents.get(binding)) != qualified
|
|
58
|
+
):
|
|
59
|
+
return False
|
|
60
|
+
declaration = self.context.parents.get(binding)
|
|
61
|
+
if not isinstance(declaration, ast.Import | ast.ImportFrom) or not isinstance(
|
|
62
|
+
self.context.parents.get(declaration), ast.Module
|
|
63
|
+
):
|
|
64
|
+
return False
|
|
65
|
+
if qualified not in self._namespace_safety:
|
|
66
|
+
self._namespace_safety[qualified] = self._namespace_untouched(qualified)
|
|
67
|
+
return self._namespace_safety[qualified]
|
|
68
|
+
|
|
69
|
+
def _namespace_untouched(self, qualified: str) -> bool:
|
|
70
|
+
for node in self.context.nodes(ast.Name, ast.Attribute):
|
|
71
|
+
targets = self._targets(node)
|
|
72
|
+
if not any(
|
|
73
|
+
target == qualified or target.startswith(qualified + ".") or qualified.startswith(target + ".")
|
|
74
|
+
for target in targets
|
|
75
|
+
):
|
|
76
|
+
continue
|
|
77
|
+
if not isinstance(node.ctx, ast.Load) or not self._safe_api_read(node, qualified):
|
|
78
|
+
return False
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
def _targets(self, expression: ast.Name | ast.Attribute) -> tuple[str, ...]:
|
|
82
|
+
parts: list[str] = []
|
|
83
|
+
root: ast.expr = expression
|
|
84
|
+
while isinstance(root, ast.Attribute):
|
|
85
|
+
parts.append(root.attr)
|
|
86
|
+
root = root.value
|
|
87
|
+
if not isinstance(root, ast.Name):
|
|
88
|
+
return ()
|
|
89
|
+
suffix = ".".join(reversed(parts))
|
|
90
|
+
return tuple(f"{origin}.{suffix}" if suffix else origin for origin in self._origins.get(root.id, ()))
|
|
91
|
+
|
|
92
|
+
def _safe_api_read(self, node: ast.Name | ast.Attribute, qualified: str) -> bool:
|
|
93
|
+
parent = self.context.parents.get(node)
|
|
94
|
+
if isinstance(parent, ast.Attribute) and parent.value is node:
|
|
95
|
+
return parent.attr not in _INTROSPECTION
|
|
96
|
+
if isinstance(parent, ast.Call) and parent.func is node:
|
|
97
|
+
return True
|
|
98
|
+
if isinstance(parent, ast.Subscript) and parent.value is node:
|
|
99
|
+
return True
|
|
100
|
+
if isinstance(parent, ast.arg | ast.AnnAssign) and parent.annotation is node:
|
|
101
|
+
return True
|
|
102
|
+
if isinstance(parent, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
103
|
+
return parent.returns is node or (
|
|
104
|
+
qualified == "contextlib.asynccontextmanager" and node in parent.decorator_list
|
|
105
|
+
)
|
|
106
|
+
return isinstance(parent, ast.ExceptHandler) and parent.type is node
|
|
107
|
+
|
|
108
|
+
def assignment(self, name: ast.Name, at: ast.AST) -> _LocalAssignment | None:
|
|
109
|
+
binding = self.provenance.binding(name.id, at)
|
|
110
|
+
if (
|
|
111
|
+
not isinstance(binding, ast.Name)
|
|
112
|
+
or name.id in self.provenance.mutated
|
|
113
|
+
or name.id in self.provenance.external_bindings
|
|
114
|
+
):
|
|
115
|
+
return None
|
|
116
|
+
value = self.provenance.assignment_value(binding, at)
|
|
117
|
+
return _LocalAssignment(binding=binding, value=value) if value is not None else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def method_receiver(call: ast.Call, methods: frozenset[str]) -> ast.Name | None:
|
|
121
|
+
callee = call.func
|
|
122
|
+
if isinstance(callee, ast.Attribute) and callee.attr in methods and isinstance(callee.value, ast.Name):
|
|
123
|
+
return callee.value
|
|
124
|
+
return None
|