sarj-python-lint 1.70.2__tar.gz → 1.72.1__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.70.2 → sarj_python_lint-1.72.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/pyproject.toml +1 -1
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/__init__.py +2 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_file_context.py +2 -2
- sarj_python_lint-1.72.1/src/sarj_python_lint/_fixes.py +67 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_parallel.py +2 -1
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_source.py +6 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rule_base.py +23 -0
- sarj_python_lint-1.72.1/src/sarj_python_lint/rules/_local_alias_fix.py +161 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_redundant_local_alias.py +49 -7
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/.gitignore +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/LICENSE +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/README.md +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/cast_access.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_annotation_origins.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_names.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_declared_contracts.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_mapping_shape_callbacks.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_route_shapes.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/delete_route_returns_no_content.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/duplicate_pytest_fixture_params.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_blanket_suppression.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_create_verb_in_post_path.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_defaulted_environment_setting.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_discarded_validation_result.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_dual_shape_model_field_reader.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_duplicate_field_validator_body.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_exception_text_in_500_detail.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_folded_bad_request_not_found.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_hasattr.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_mutating_container_transform.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_slash_collection_root_under_prefix.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/no_work_verb_get_route.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_direct_import_module.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_precise_object_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_pydantic_model_assertion.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_pytest_dependency_fixtures.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_typed_settings_field.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/pytest_fixture_returns_closed_resource.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_exp_on_jwt_decode.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_pydantic_for_mapping_shape.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
- {sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/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.72.1
|
|
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/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from sarj_python_lint._analysis_session import AnalysisSession as AnalysisSession
|
|
2
|
+
from sarj_python_lint._fixes import apply_fixes as apply_fixes, python_source_hash as python_source_hash
|
|
1
3
|
from sarj_python_lint._ratchet_cli import run_ratchet as run_ratchet
|
|
2
4
|
from sarj_python_lint._source import python_source_encoding as python_source_encoding
|
|
3
5
|
from sarj_python_lint._version import __version__ as __version__
|
|
@@ -5,7 +5,7 @@ from functools import cached_property
|
|
|
5
5
|
from typing import TYPE_CHECKING, final
|
|
6
6
|
|
|
7
7
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
8
|
-
from sarj_python_lint._source import symbol_prefilter_source
|
|
8
|
+
from sarj_python_lint._source import python_source_lines, symbol_prefilter_source
|
|
9
9
|
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
|
|
10
10
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
11
11
|
from sarj_python_lint.rules._fastapi import FastapiIndex
|
|
@@ -47,7 +47,7 @@ class PythonFileContext:
|
|
|
47
47
|
|
|
48
48
|
@cached_property
|
|
49
49
|
def source_lines(self) -> list[str]:
|
|
50
|
-
return self.source
|
|
50
|
+
return python_source_lines(self.source)
|
|
51
51
|
|
|
52
52
|
@cached_property
|
|
53
53
|
def generated(self) -> bool:
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import AutofixPolicy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
from sarj_python_lint.rule_base import Fix
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def python_source_hash(source: str) -> str:
|
|
17
|
+
canonical = source.removeprefix("\ufeff").replace("\r\n", "\n").replace("\r", "\n")
|
|
18
|
+
return sha256(canonical.encode("utf-8")).hexdigest()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply_fixes(source: str, fixes: Sequence[Fix]) -> str:
|
|
22
|
+
if not fixes:
|
|
23
|
+
return source
|
|
24
|
+
digest = python_source_hash(source)
|
|
25
|
+
prefix = "\ufeff" if source.startswith("\ufeff") else ""
|
|
26
|
+
raw = source[len(prefix) :].encode("utf-8")
|
|
27
|
+
lines = raw.splitlines(keepends=True)
|
|
28
|
+
offsets = [0]
|
|
29
|
+
for line in lines:
|
|
30
|
+
offsets.append(offsets[-1] + len(line))
|
|
31
|
+
if not lines or lines[-1].endswith((b"\n", b"\r")):
|
|
32
|
+
lines.append(b"")
|
|
33
|
+
|
|
34
|
+
def offset(line: int, col: int) -> int:
|
|
35
|
+
if line < 1 or line > len(lines) or col < 1 or col > len(lines[line - 1].rstrip(b"\r\n")) + 1:
|
|
36
|
+
msg = "fix position is outside the source"
|
|
37
|
+
raise ValueError(msg)
|
|
38
|
+
prefix = lines[line - 1][: col - 1]
|
|
39
|
+
prefix.decode("utf-8")
|
|
40
|
+
return offsets[line - 1] + len(prefix)
|
|
41
|
+
|
|
42
|
+
edits: list[tuple[int, int, bytes]] = []
|
|
43
|
+
for fix in fixes:
|
|
44
|
+
if fix.safety is not AutofixPolicy.SAFE or fix.source_hash != digest or not fix.edits:
|
|
45
|
+
msg = "fix must be safe, nonempty and match the complete source revision"
|
|
46
|
+
raise ValueError(msg)
|
|
47
|
+
for edit in fix.edits:
|
|
48
|
+
start = offset(edit.start_line, edit.start_col)
|
|
49
|
+
end = offset(edit.end_line, edit.end_col)
|
|
50
|
+
if end < start:
|
|
51
|
+
msg = "fix range runs backwards"
|
|
52
|
+
raise ValueError(msg)
|
|
53
|
+
edits.append((start, end, edit.replacement.encode("utf-8")))
|
|
54
|
+
edits.sort()
|
|
55
|
+
result: list[bytes] = []
|
|
56
|
+
previous = 0
|
|
57
|
+
for start, end, replacement in edits:
|
|
58
|
+
if start < previous:
|
|
59
|
+
msg = "fix groups overlap"
|
|
60
|
+
raise ValueError(msg)
|
|
61
|
+
result.extend((raw[previous:start], replacement))
|
|
62
|
+
previous = end
|
|
63
|
+
result.append(raw[previous:])
|
|
64
|
+
fixed = b"".join(result).decode("utf-8")
|
|
65
|
+
tree = ast.parse(fixed)
|
|
66
|
+
compile(tree, "<native-fix>", "exec")
|
|
67
|
+
return prefix + fixed
|
|
@@ -87,7 +87,8 @@ def check_parallel(
|
|
|
87
87
|
resolutions: set[_ResolutionKey] = set()
|
|
88
88
|
modules: set[Path] = set()
|
|
89
89
|
try:
|
|
90
|
-
|
|
90
|
+
# Keep workers busy while an earlier file delays ordered results.
|
|
91
|
+
for result in pool.map(_check_file, loaded.items(), chunksize=1, buffersize=workers * 4):
|
|
91
92
|
resolutions.update(result.resolutions)
|
|
92
93
|
modules.update(result.modules)
|
|
93
94
|
# A worker cannot hit either order-dependent limit when the whole
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from io import BytesIO
|
|
4
|
+
import re
|
|
4
5
|
import tokenize
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
7
|
import unicodedata
|
|
@@ -19,6 +20,11 @@ def read_python_source(path: Path) -> str:
|
|
|
19
20
|
return stream.read()
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def python_source_lines(source: str) -> list[str]:
|
|
24
|
+
lines = re.split(r"\r\n?|\n", source)
|
|
25
|
+
return lines[:-1] if not lines[-1] else lines
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def symbol_prefilter_source(source: str) -> str:
|
|
23
29
|
# Only necessary symbol gates may use this view; parse and locate original source.
|
|
24
30
|
return source if source.isascii() else unicodedata.normalize("NFKC", source)
|
|
@@ -75,6 +75,28 @@ class ColumnEncoding(StrEnum):
|
|
|
75
75
|
CODEPOINTS = "codepoints"
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class TextEdit:
|
|
80
|
+
"""An exact, half-open range of one-based lines and UTF-8 byte columns.
|
|
81
|
+
|
|
82
|
+
Columns refer to decoded source without its original leading BOM.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
start_line: int
|
|
86
|
+
start_col: int
|
|
87
|
+
end_line: int
|
|
88
|
+
end_col: int
|
|
89
|
+
replacement: str
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True, slots=True)
|
|
93
|
+
class Fix:
|
|
94
|
+
title: str
|
|
95
|
+
safety: AutofixPolicy
|
|
96
|
+
source_hash: str
|
|
97
|
+
edits: tuple[TextEdit, ...]
|
|
98
|
+
|
|
99
|
+
|
|
78
100
|
@dataclass(frozen=True, slots=True)
|
|
79
101
|
class Diagnostic:
|
|
80
102
|
path: Path
|
|
@@ -84,6 +106,7 @@ class Diagnostic:
|
|
|
84
106
|
message: str
|
|
85
107
|
severity: Severity = Severity.ERROR
|
|
86
108
|
column_encoding: ColumnEncoding = ColumnEncoding.UTF8_BYTES
|
|
109
|
+
fixes: tuple[Fix, ...] = ()
|
|
87
110
|
|
|
88
111
|
def format(self) -> str:
|
|
89
112
|
label = "warning: " if self.severity is Severity.WARNING else ""
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint._fixes import python_source_hash
|
|
8
|
+
from sarj_python_lint._source import python_source_encoding
|
|
9
|
+
from sarj_python_lint.rule_base import AutofixPolicy, Fix, TextEdit
|
|
10
|
+
from sarj_python_lint.rules._suppression_comments import comment_lines
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
|
|
16
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_NAMESPACE_MUTATORS = frozenset(
|
|
20
|
+
{
|
|
21
|
+
"setattr",
|
|
22
|
+
"delattr",
|
|
23
|
+
"setitem",
|
|
24
|
+
"delitem",
|
|
25
|
+
"__setattr__",
|
|
26
|
+
"__delattr__",
|
|
27
|
+
"__setitem__",
|
|
28
|
+
"__delitem__",
|
|
29
|
+
"__init__",
|
|
30
|
+
"__ior__",
|
|
31
|
+
"ior",
|
|
32
|
+
"methodcaller",
|
|
33
|
+
"patch",
|
|
34
|
+
"clear",
|
|
35
|
+
"pop",
|
|
36
|
+
"popitem",
|
|
37
|
+
"setdefault",
|
|
38
|
+
"update",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LocalAliasFixes:
|
|
44
|
+
def __init__(self, context: PythonFileContext) -> None:
|
|
45
|
+
self.context = context
|
|
46
|
+
|
|
47
|
+
@cached_property
|
|
48
|
+
def _lines(self) -> tuple[bytes, ...]:
|
|
49
|
+
return tuple(line.encode("utf-8") for line in self.context.source_lines)
|
|
50
|
+
|
|
51
|
+
def spelling(self, node: ast.Name) -> str:
|
|
52
|
+
return self._lines[node.lineno - 1][node.col_offset : node.end_col_offset].decode("utf-8")
|
|
53
|
+
|
|
54
|
+
@cached_property
|
|
55
|
+
def _available(self) -> bool:
|
|
56
|
+
context = self.context
|
|
57
|
+
return (
|
|
58
|
+
python_source_encoding(context.source.encode("utf-8")) in {"utf-8", "utf-8-sig"}
|
|
59
|
+
and context.imports.builtin_is_unshadowed("dict")
|
|
60
|
+
and not any("dict" in bindings for bindings in context.resource_provenance.bindings.values())
|
|
61
|
+
and context.resource_provenance.builtin_namespace_untouched
|
|
62
|
+
and not any(node.attr == "__code__" for node in context.nodes(ast.Attribute))
|
|
63
|
+
and not _visible_namespace_mutation(context)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@cached_property
|
|
67
|
+
def _comments(self) -> Mapping[int, str]:
|
|
68
|
+
return comment_lines(tuple(self.context.source_lines))
|
|
69
|
+
|
|
70
|
+
@cached_property
|
|
71
|
+
def _digest(self) -> str:
|
|
72
|
+
return python_source_hash(self.context.source)
|
|
73
|
+
|
|
74
|
+
def for_alias(self, assignment: ast.Assign, use: ast.Name) -> tuple[Fix, ...]:
|
|
75
|
+
context = self.context
|
|
76
|
+
owner = context.parents.get(assignment)
|
|
77
|
+
if not isinstance(owner, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
78
|
+
return ()
|
|
79
|
+
if not isinstance(context.parents.get(owner), ast.Module) or owner.decorator_list or owner.type_params:
|
|
80
|
+
return ()
|
|
81
|
+
args = owner.args
|
|
82
|
+
parameters = [*args.posonlyargs, *args.args, *args.kwonlyargs]
|
|
83
|
+
if len(parameters) != 1 or args.vararg or args.kwarg or args.defaults or any(args.kw_defaults):
|
|
84
|
+
return ()
|
|
85
|
+
source = assignment.value
|
|
86
|
+
if not isinstance(source, ast.Name) or parameters[0].arg != source.id:
|
|
87
|
+
return ()
|
|
88
|
+
body = owner.body[1:] if ast.get_docstring(owner) is not None else owner.body
|
|
89
|
+
if body[:1] != [assignment] or len(body[1:]) != 1 or not isinstance(body[-1], ast.Return):
|
|
90
|
+
return ()
|
|
91
|
+
call = body[-1].value
|
|
92
|
+
if not isinstance(call, ast.Call) or not isinstance(call.func, ast.Name) or call.func.id != "dict":
|
|
93
|
+
return ()
|
|
94
|
+
if call.args or len(call.keywords) != 1 or call.keywords[0].value is not use or not self._available:
|
|
95
|
+
return ()
|
|
96
|
+
edits = self._edits(assignment, use, source=source, owner=owner)
|
|
97
|
+
if not edits:
|
|
98
|
+
return ()
|
|
99
|
+
return (Fix("Pass the original local directly", AutofixPolicy.SAFE, self._digest, edits),)
|
|
100
|
+
|
|
101
|
+
def _edits(
|
|
102
|
+
self,
|
|
103
|
+
assignment: ast.Assign,
|
|
104
|
+
use: ast.Name,
|
|
105
|
+
*,
|
|
106
|
+
source: ast.Name,
|
|
107
|
+
owner: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
108
|
+
) -> tuple[TextEdit, ...]:
|
|
109
|
+
if any(line in self._comments for line in range(owner.lineno, (owner.end_lineno or owner.lineno) + 1)):
|
|
110
|
+
return ()
|
|
111
|
+
if assignment.end_lineno != assignment.lineno:
|
|
112
|
+
return ()
|
|
113
|
+
line = self.context.source_lines[assignment.lineno - 1]
|
|
114
|
+
raw = self._lines[assignment.lineno - 1]
|
|
115
|
+
if (
|
|
116
|
+
raw[: assignment.col_offset].strip()
|
|
117
|
+
or raw[assignment.end_col_offset :].strip()
|
|
118
|
+
or ";" in line
|
|
119
|
+
or "\\" in line
|
|
120
|
+
):
|
|
121
|
+
return ()
|
|
122
|
+
return (
|
|
123
|
+
TextEdit(assignment.lineno, 1, assignment.lineno + 1, 1, ""),
|
|
124
|
+
TextEdit(
|
|
125
|
+
use.lineno,
|
|
126
|
+
use.col_offset + 1,
|
|
127
|
+
use.end_lineno or use.lineno,
|
|
128
|
+
(use.end_col_offset or 0) + 1,
|
|
129
|
+
self.spelling(source),
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _visible_namespace_mutation(context: PythonFileContext) -> bool:
|
|
135
|
+
if _imports_unittest(context):
|
|
136
|
+
return True
|
|
137
|
+
for node in context.nodes(ast.Name, ast.alias, ast.Attribute, ast.Subscript, ast.AugAssign):
|
|
138
|
+
match node:
|
|
139
|
+
case ast.Name(id=name) | ast.alias(name=name):
|
|
140
|
+
if name in _NAMESPACE_MUTATORS:
|
|
141
|
+
return True
|
|
142
|
+
case ast.Attribute(attr=name, ctx=ctx):
|
|
143
|
+
if name in _NAMESPACE_MUTATORS or not isinstance(ctx, ast.Load):
|
|
144
|
+
return True
|
|
145
|
+
case ast.Subscript(ctx=ctx):
|
|
146
|
+
if not isinstance(ctx, ast.Load):
|
|
147
|
+
return True
|
|
148
|
+
case ast.AugAssign(op=op):
|
|
149
|
+
if isinstance(op, ast.BitOr):
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _imports_unittest(context: PythonFileContext) -> bool:
|
|
155
|
+
for node in context.nodes(ast.Import):
|
|
156
|
+
if any(alias.name.partition(".")[0] == "unittest" for alias in node.names):
|
|
157
|
+
return True
|
|
158
|
+
return any(
|
|
159
|
+
node.module is not None and node.module.partition(".")[0] == "unittest"
|
|
160
|
+
for node in context.nodes(ast.ImportFrom)
|
|
161
|
+
)
|
|
@@ -19,6 +19,7 @@ from sarj_python_lint.rule_base import (
|
|
|
19
19
|
Severity,
|
|
20
20
|
)
|
|
21
21
|
from sarj_python_lint.rules._ast_index import children
|
|
22
|
+
from sarj_python_lint.rules._local_alias_fix import LocalAliasFixes
|
|
22
23
|
from sarj_python_lint.rules._suppression_comments import comment_lines
|
|
23
24
|
|
|
24
25
|
|
|
@@ -82,9 +83,9 @@ class NoRedundantLocalAlias(Rule):
|
|
|
82
83
|
default_level=Severity.WARNING,
|
|
83
84
|
summary="Remove a local alias used only for its matching keyword argument.",
|
|
84
85
|
rationale="An extra local binding makes readers trace two names for one value when the keyword already communicates its role.",
|
|
85
|
-
remediation="
|
|
86
|
+
remediation="Apply the safe fix where offered; otherwise pass the original local to the matching keyword and remove the unannotated alias after review.",
|
|
86
87
|
category=RuleCategory.MAINTAINABILITY,
|
|
87
|
-
autofix=AutofixPolicy.
|
|
88
|
+
autofix=AutofixPolicy.SAFE,
|
|
88
89
|
limitations=(
|
|
89
90
|
"Only unannotated, single-target `alias = original` assignments with one binding and one read at `alias=alias` are checked.",
|
|
90
91
|
"The original must be a parameter or an earlier initialized local with one binding; reassignment, deletion, imports, alias chains and uncertain initialization are excluded.",
|
|
@@ -93,10 +94,35 @@ class NoRedundantLocalAlias(Rule):
|
|
|
93
94
|
"Typed aliases, files with type comments, same-scope quoted references and conditional reads of either local are excluded. Captured bindings and module/class aliases are preserved.",
|
|
94
95
|
"Files with wildcard imports, Pydantic imports or references to known namespace readers, dynamic attribute getters or reference-count observers are excluded. Wildcard exports cannot establish builtin identity; computed type names can consume caller-local bindings without a Name read in the AST.",
|
|
95
96
|
"Dynamic class construction and class-valued references to type are excluded. Direct, unshadowed unary type(value) queries and standard-library dataclasses remain supported.",
|
|
96
|
-
"Generated source, stubs and malformed input are excluded. Arbitrary external frame introspection is not modeled.",
|
|
97
|
-
"
|
|
97
|
+
"Generated source, stubs and malformed input are excluded; files must compile with normal module execution flags. Arbitrary external frame introspection is not modeled.",
|
|
98
|
+
"Safe fixes are limited to undecorated module functions with one required parameter, an optional existing docstring, a clean single-line alias and a tail return of unshadowed builtin dict with exactly the matching keyword. Recorded dict bindings, visible attribute/subscript writes, setter, mapping-mutator, ior, methodcaller or patch references, unittest imports, `|=` assignments, comments, non-UTF8 encodings, type parameters and visible code-object inspection prevent fixes. Mutation guards also exclude unrelated ordinary mappings, nonmutating methodcaller uses, harmless unittest imports and integer `|=` assignments. Other calls remain diagnostic-only; arbitrary external tracing, reflection and monkeypatching are not modeled.",
|
|
99
|
+
"Readability judgments, multiple reads, positional forwarding and more complex control flow remain outside the contract.",
|
|
98
100
|
),
|
|
99
101
|
examples=(
|
|
102
|
+
RuleExample(
|
|
103
|
+
example_id="safe-builtin-tail",
|
|
104
|
+
scenario="safe-fix",
|
|
105
|
+
title="A safe builtin mapping return",
|
|
106
|
+
outcome=ExampleOutcome.MATCH,
|
|
107
|
+
files=(
|
|
108
|
+
ExampleFile.python(
|
|
109
|
+
"case.py", "def render(item):\n payload = item\n return dict(payload=payload)\n"
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
focus_path=PurePosixPath("case.py"),
|
|
113
|
+
expected_count=1,
|
|
114
|
+
public=True,
|
|
115
|
+
),
|
|
116
|
+
RuleExample(
|
|
117
|
+
example_id="direct-builtin-tail",
|
|
118
|
+
scenario="safe-fix",
|
|
119
|
+
title="Return the original local in the mapping",
|
|
120
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
121
|
+
files=(ExampleFile.python("case.py", "def render(item):\n return dict(payload=item)\n"),),
|
|
122
|
+
focus_path=PurePosixPath("case.py"),
|
|
123
|
+
expected_count=0,
|
|
124
|
+
public=True,
|
|
125
|
+
),
|
|
100
126
|
RuleExample(
|
|
101
127
|
example_id="keyword-supplies-role",
|
|
102
128
|
title="The keyword supplies the role",
|
|
@@ -138,6 +164,7 @@ class NoRedundantLocalAlias(Rule):
|
|
|
138
164
|
quoted = _quoted_references(context, provenance, names)
|
|
139
165
|
positions = {statement: i for owner in context.nodes(*_SUITES) for i, statement in enumerate(owner.body)}
|
|
140
166
|
findings: list[Diagnostic] = []
|
|
167
|
+
fixes = LocalAliasFixes(context)
|
|
141
168
|
for assignment in candidates:
|
|
142
169
|
target = assignment.targets[0]
|
|
143
170
|
source = assignment.value
|
|
@@ -152,14 +179,17 @@ class NoRedundantLocalAlias(Rule):
|
|
|
152
179
|
continue
|
|
153
180
|
if not _stable_source(source, assignment, provenance, positions, conditional_sources):
|
|
154
181
|
continue
|
|
182
|
+
target_spelling = fixes.spelling(target)
|
|
183
|
+
source_spelling = fixes.spelling(source)
|
|
155
184
|
findings.append(
|
|
156
185
|
Diagnostic(
|
|
157
186
|
path=context.path,
|
|
158
187
|
line=target.lineno,
|
|
159
188
|
col=target.col_offset + 1,
|
|
160
189
|
code=self.code,
|
|
161
|
-
message=f"Local `{
|
|
190
|
+
message=f"Local `{target_spelling}` only forwards `{source_spelling}` to `{target_spelling}=`; pass `{source_spelling}` directly and remove the alias.",
|
|
162
191
|
severity=Severity.WARNING,
|
|
192
|
+
fixes=fixes.for_alias(assignment, use),
|
|
163
193
|
)
|
|
164
194
|
)
|
|
165
195
|
return findings
|
|
@@ -204,7 +234,7 @@ def _local_assignments(context: PythonFileContext, names: set[str]) -> list[ast.
|
|
|
204
234
|
and isinstance(node.value, ast.Name)
|
|
205
235
|
and node.targets[0].id != node.value.id
|
|
206
236
|
]
|
|
207
|
-
if not candidates or context.has_wildcard_import
|
|
237
|
+
if not candidates or context.has_wildcard_import:
|
|
208
238
|
return []
|
|
209
239
|
provenance = context.resource_provenance
|
|
210
240
|
blocked = {provenance.scope(node) for node in context.nodes(*_UNCERTAIN)}
|
|
@@ -214,11 +244,23 @@ def _local_assignments(context: PythonFileContext, names: set[str]) -> list[ast.
|
|
|
214
244
|
eligible = [
|
|
215
245
|
node for node in candidates if isinstance(scope := provenance.scope(node), _FUNCTIONS) and scope not in blocked
|
|
216
246
|
]
|
|
217
|
-
if not eligible or context.
|
|
247
|
+
if not eligible or context.dynamic_namespace or _observes_references(context):
|
|
248
|
+
return []
|
|
249
|
+
if context.generated or _has_type_comments(context) or not _compiles(context):
|
|
218
250
|
return []
|
|
219
251
|
return eligible
|
|
220
252
|
|
|
221
253
|
|
|
254
|
+
def _compiles(context: PythonFileContext) -> bool:
|
|
255
|
+
if context.tree is None:
|
|
256
|
+
return False
|
|
257
|
+
try:
|
|
258
|
+
compile(context.tree, str(context.path), "exec")
|
|
259
|
+
except SyntaxError:
|
|
260
|
+
return False
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
|
|
222
264
|
def _has_type_comments(context: PythonFileContext) -> bool:
|
|
223
265
|
return any(_TYPE_COMMENT.search(comment) for comment in comment_lines(tuple(context.source_lines)).values())
|
|
224
266
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/interpreter_argv.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_names.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_local_source.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_namespace_access.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_nominal_project.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_route_shapes.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_runtime_bindings.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_set_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_static_json_alias.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_store_writes.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_composition.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.70.2 → sarj_python_lint-1.72.1}/src/sarj_python_lint/rules/_test_provenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|