sarj-python-lint 1.50.0__tar.gz → 1.52.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.50.0 → sarj_python_lint-1.52.0}/PKG-INFO +1 -1
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/pyproject.toml +1 -1
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_annotation_semantics.py +2 -1
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_resource_provenance.py +2 -1
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_discarded_validation_result.py +2 -2
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_mutating_container_transform.py +290 -33
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/.gitignore +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/LICENSE +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/README.md +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/interpreter_argv.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_closed_helpers.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_data_union_discriminator.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_exports.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_inert_model_annotation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_inherited_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_namespace_access.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_native_namespace_capability.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_pydantic_field_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_resolved_operation_signature.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_runtime_bindings.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_static_json_alias.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_store_writes.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_type_guard_symbols.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/duplicate_adjacent_exception_handler.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/duplicate_closed_pure_helper.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_async_function_cache.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_conflicting_pydantic_alias_validation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_contradictory_type_guard.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_fixed_sleep_in_loop.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_impossible_pydantic_field_bounds.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_invalid_pydantic_bypass.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_invalid_pydantic_validator_return.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_loguru_exc_info.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_loguru_printf_format.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_partial_type_is.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_shallow_record_type_guard.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unknown_pydantic_validator_field.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_bulk_upsert.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_declarative_batch_validation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_discriminated_model_union.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_psycopg_executemany.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/redundant_test_constructor_forwarder.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/repeated_unimplemented_test_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_injectable_retry_sleep.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/store_field_update_reuses_upsert.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/test_without_explicit_oracle.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/unused_test_factory_option.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.52.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/
|
|
@@ -47,7 +47,8 @@ class AnnotationSemantics:
|
|
|
47
47
|
aliases = {name: value for name, value in candidates.items() if name not in ambiguous and name not in rebound}
|
|
48
48
|
return cls(imports, MappingProxyType(aliases))
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
@staticmethod
|
|
51
|
+
def parse(annotation: ast.expr | None) -> ast.expr | None:
|
|
51
52
|
if not isinstance(annotation, ast.Constant) or not isinstance(annotation.value, str):
|
|
52
53
|
return annotation
|
|
53
54
|
try:
|
|
@@ -34,8 +34,9 @@ _RESOURCE_OWNERS = MappingProxyType(
|
|
|
34
34
|
"contextlib": frozenset({"ExitStack", "AsyncExitStack", "_BaseExitStack"}),
|
|
35
35
|
"subprocess": frozenset({"Popen", "TimeoutExpired"}),
|
|
36
36
|
"re": frozenset({"compile", "Match", "Pattern"}),
|
|
37
|
+
"operator": frozenset({"setitem", "delitem", "iadd", "iconcat", "imul", "iand", "isub", "ixor", "ior"}),
|
|
37
38
|
"unittest": frozenset({"mock.Mock", "mock.MagicMock", "mock.AsyncMock"}),
|
|
38
|
-
"typing": frozenset({"final", "Literal", "Annotated"}),
|
|
39
|
+
"typing": frozenset({"final", "Literal", "Annotated", "cast", "assert_type"}),
|
|
39
40
|
"typing_extensions": frozenset({"final", "Literal", "Annotated"}),
|
|
40
41
|
"pydantic": frozenset(
|
|
41
42
|
{
|
|
@@ -140,7 +140,7 @@ class NoDiscardedValidationResult(Rule):
|
|
|
140
140
|
id = "no-discarded-validation-result"
|
|
141
141
|
code = "SARJ519"
|
|
142
142
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
143
|
-
default_level=Severity.
|
|
143
|
+
default_level=Severity.ERROR,
|
|
144
144
|
summary="Consume a value-producing validator's result instead of relying on its exceptions.",
|
|
145
145
|
rationale="Discarding a conversion's returned value makes validation an implicit exception side effect. The caller can continue with unvalidated or unnormalized inputs even after constructing a checked replacement.",
|
|
146
146
|
remediation="Use the checked result in the following operation, or expose a separate validation command returning None. Preserve exception behavior; do not delete the call or add a dummy assignment.",
|
|
@@ -212,7 +212,7 @@ class NoDiscardedValidationResult(Rule):
|
|
|
212
212
|
col=statement.col_offset + 1,
|
|
213
213
|
code=self.code,
|
|
214
214
|
message="This value-producing validator's result is discarded; consume the checked value or use a validation command returning None.",
|
|
215
|
-
severity=Severity.
|
|
215
|
+
severity=Severity.ERROR,
|
|
216
216
|
)
|
|
217
217
|
for statement, call in calls
|
|
218
218
|
if isinstance(call, ast.Call)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from functools import cached_property
|
|
5
6
|
from pathlib import PurePosixPath
|
|
6
7
|
from typing import TYPE_CHECKING, ClassVar, Final, Literal, final, override
|
|
7
8
|
|
|
@@ -17,20 +18,42 @@ from sarj_python_lint.rule_base import (
|
|
|
17
18
|
Severity,
|
|
18
19
|
is_suppressed,
|
|
19
20
|
)
|
|
21
|
+
from sarj_python_lint.rules._annotation_semantics import AnnotationSemantics
|
|
20
22
|
from sarj_python_lint.rules._resource_provenance import ResourceProvenance
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
if TYPE_CHECKING:
|
|
26
|
+
from collections.abc import Mapping
|
|
27
|
+
|
|
24
28
|
from sarj_python_lint._file_context import PythonFileContext
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
type _Kind = Literal["list", "dict", "set"]
|
|
28
32
|
type _Function = ast.FunctionDef | ast.AsyncFunctionDef
|
|
33
|
+
type _Comprehension = ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp
|
|
29
34
|
_MAX_BRANCH_DEPTH = 32
|
|
30
35
|
_PRIMITIVES = frozenset({"str", "int", "float", "bool", "bytes", "complex"})
|
|
31
36
|
_MUTATORS: Final[dict[_Kind, frozenset[str]]] = {
|
|
32
|
-
"list": frozenset(
|
|
33
|
-
|
|
37
|
+
"list": frozenset(
|
|
38
|
+
{
|
|
39
|
+
"append",
|
|
40
|
+
"extend",
|
|
41
|
+
"insert",
|
|
42
|
+
"pop",
|
|
43
|
+
"remove",
|
|
44
|
+
"clear",
|
|
45
|
+
"sort",
|
|
46
|
+
"reverse",
|
|
47
|
+
"__setitem__",
|
|
48
|
+
"__delitem__",
|
|
49
|
+
"__iadd__",
|
|
50
|
+
"__imul__",
|
|
51
|
+
"__init__",
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
"dict": frozenset(
|
|
55
|
+
{"clear", "pop", "popitem", "setdefault", "update", "__setitem__", "__delitem__", "__ior__", "__init__"}
|
|
56
|
+
),
|
|
34
57
|
"set": frozenset(
|
|
35
58
|
{
|
|
36
59
|
"add",
|
|
@@ -42,13 +65,30 @@ _MUTATORS: Final[dict[_Kind, frozenset[str]]] = {
|
|
|
42
65
|
"difference_update",
|
|
43
66
|
"intersection_update",
|
|
44
67
|
"symmetric_difference_update",
|
|
68
|
+
"__iand__",
|
|
69
|
+
"__ior__",
|
|
70
|
+
"__isub__",
|
|
71
|
+
"__ixor__",
|
|
72
|
+
"__init__",
|
|
45
73
|
}
|
|
46
74
|
),
|
|
47
75
|
}
|
|
76
|
+
_OPERATOR_WRITES: Final[dict[str, tuple[frozenset[_Kind], int]]] = {
|
|
77
|
+
"operator.setitem": (frozenset({"list", "dict"}), 3),
|
|
78
|
+
"operator.delitem": (frozenset({"list", "dict"}), 2),
|
|
79
|
+
"operator.iadd": (frozenset({"list"}), 2),
|
|
80
|
+
"operator.iconcat": (frozenset({"list"}), 2),
|
|
81
|
+
"operator.imul": (frozenset({"list"}), 2),
|
|
82
|
+
"operator.iand": (frozenset({"set"}), 2),
|
|
83
|
+
"operator.isub": (frozenset({"set"}), 2),
|
|
84
|
+
"operator.ixor": (frozenset({"set"}), 2),
|
|
85
|
+
"operator.ior": (frozenset({"dict", "set"}), 2),
|
|
86
|
+
}
|
|
87
|
+
_IDENTITY_ARGUMENTS: Final[dict[str, int]] = {"typing.cast": 1, "typing.assert_type": 0}
|
|
88
|
+
_IDENTITY_CALL_ARITY = 2
|
|
48
89
|
_LINEAR = (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Delete, ast.Expr, ast.Pass)
|
|
49
90
|
_DEFERRED_OR_REBINDING = (
|
|
50
91
|
ast.Lambda,
|
|
51
|
-
ast.GeneratorExp,
|
|
52
92
|
ast.NamedExpr,
|
|
53
93
|
ast.Yield,
|
|
54
94
|
ast.YieldFrom,
|
|
@@ -78,19 +118,27 @@ class NoMutatingContainerTransform(Rule):
|
|
|
78
118
|
id = "no-mutating-container-transform"
|
|
79
119
|
code = "SARJ520"
|
|
80
120
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
81
|
-
default_level=Severity.
|
|
121
|
+
default_level=Severity.ERROR,
|
|
82
122
|
summary="Return container replacements without mutating borrowed builtin inputs.",
|
|
83
123
|
rationale="A container-valued transformation can change caller aliases before returning its result. Copying after the write leaves the original input changed.",
|
|
84
124
|
remediation="Copy each changed container before writing, return a fresh replacement, or expose a separate command returning None. Keep documented in-place contracts explicit; do not discard the operation or add a dummy assignment.",
|
|
85
125
|
category=RuleCategory.CORRECTNESS,
|
|
86
126
|
autofix=AutofixPolicy.NONE,
|
|
87
127
|
limitations=(
|
|
88
|
-
"Checks undecorated functions and methods with a direct builtin list/dict/set return annotation, typed primitive builtin container inputs, and assignments, returns, raises and structured if branches ending in a value return. Returned element types do not change borrowed input ownership.",
|
|
128
|
+
"Checks undecorated functions and methods with a direct builtin list/dict/set/tuple/frozenset return annotation, typed primitive mutable builtin container inputs, and assignments, returns, raises and structured if branches ending in a value return. Returned container kinds and element types do not change borrowed input ownership.",
|
|
129
|
+
"Direct unshadowed builtin staticmethod/classmethod and canonical typing.final markers preserve the method body in plain class namespaces. Custom, patched, inherited or metaclass-prepared namespaces and multiple binding markers remain excluded.",
|
|
89
130
|
"Tracks direct aliases and conditional-expression aliases with one proven borrowed owner, item writes/deletions, known builtin mutators and supported in-place operators. Rebinding to a fresh local builder ends the borrowed root's provenance.",
|
|
131
|
+
"Canonical source-resolved operator item writes/deletions and supported in-place functions are checked with positional arity and builtin kind contracts. Patched, escaped or ambiguous operator providers and unknown functional helpers are excluded.",
|
|
132
|
+
"Direct unshadowed builtin list/dict/set mutation descriptors and explicit mutating special methods use the same borrowed-target contract. Unknown classes, read methods and fresh local targets remain excluded.",
|
|
133
|
+
"Source-proven typing.cast/assert_type calls with two positional arguments preserve the borrowed argument's identity through aliases. They do not allocate a replacement; copying before the helper still ends borrowed provenance.",
|
|
90
134
|
"Branches retain only origins shared by every continuing path; uncertain joins lose provenance. Proven literal truth selects reachable statement/expression branches and respects boolean short-circuiting; return/raise paths stop flow. Eager comprehensions isolate target bindings; their outermost iterable uses the enclosing scope.",
|
|
135
|
+
"Boolean value selections preserve aliases only when every reachable result has one proven borrowed owner. Unknown conditions that can return unrelated values lose provenance.",
|
|
91
136
|
"Comprehension operands follow generator/filter order. Empty literal iterables and literal-false filters prevent later operands and bodies from being visited; effects in earlier operands remain checked.",
|
|
137
|
+
"Native collection literals supply truth facts for empty literals, explicit members and directly empty expansions. Contents are still evaluated; unknown spreads and application truth methods remain unresolved.",
|
|
138
|
+
"Literal primitive equality/order and singleton identity comparisons supply branch facts. Comparison chains stop before later operands after a proven false link; unknown application comparisons are never evaluated.",
|
|
139
|
+
"Synchronous generator expressions passed as the sole argument of a direct unshadowed native list/tuple/set/frozenset constructor are eagerly consumed and share comprehension flow. Unknown, shadowed, patched and asynchronous consumers are excluded.",
|
|
92
140
|
"Local annotated assignments visit their value and evaluated target operands, not deferred annotation expressions; an annotation without a value does not perform an item write.",
|
|
93
|
-
"Multiple assignment/deletion targets, unpacking assignments, more than 32 nested branches, loops, try/context-manager orchestration, closures, generators, assignment expressions, opaque type aliases, unknown receivers and dynamic namespaces are excluded.",
|
|
141
|
+
"Multiple assignment/deletion targets, unpacking assignments, more than 32 nested branches, loops, try/context-manager orchestration, closures, other generators, assignment expressions, opaque type aliases, unknown receivers and dynamic namespaces are excluded.",
|
|
94
142
|
"Model-valued inputs and framework validators retain their existing rule owners. Mutation of nested descendants, receiver state, I/O and scalar command/result APIs are outside this rule's supported contract.",
|
|
95
143
|
"Generated and vendored sources are excluded; maintained tests are checked. No automatic copying, deletion, or command rewrite is offered.",
|
|
96
144
|
),
|
|
@@ -139,7 +187,7 @@ class NoMutatingContainerTransform(Rule):
|
|
|
139
187
|
col=mutation.node.col_offset + 1,
|
|
140
188
|
code=self.code,
|
|
141
189
|
message=f"This container-valued transformation mutates borrowed {mutation.owner.kind} input `{mutation.owner.parameter}`; copy before writing or use a command returning None.",
|
|
142
|
-
severity=Severity.
|
|
190
|
+
severity=Severity.ERROR,
|
|
143
191
|
)
|
|
144
192
|
for function in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef)
|
|
145
193
|
for mutation in analysis.mutations(function)
|
|
@@ -153,12 +201,51 @@ class _ContainerTransforms:
|
|
|
153
201
|
self.context = context
|
|
154
202
|
self.provenance = ResourceProvenance(context, require_static_namespace=True)
|
|
155
203
|
|
|
204
|
+
@cached_property
|
|
205
|
+
def native_calls(self) -> dict[ast.Call, str]:
|
|
206
|
+
if self.provenance.mock_namespace_patching:
|
|
207
|
+
return {}
|
|
208
|
+
calls: dict[ast.Call, str] = {}
|
|
209
|
+
for call in self.context.nodes(ast.Call):
|
|
210
|
+
if call.keywords or any(isinstance(argument, ast.Starred) for argument in call.args):
|
|
211
|
+
continue
|
|
212
|
+
qualified = self.provenance.runtime_import_name(call.func)
|
|
213
|
+
if qualified is not None:
|
|
214
|
+
calls[call] = qualified
|
|
215
|
+
return calls
|
|
216
|
+
|
|
217
|
+
@cached_property
|
|
218
|
+
def argument_writes(self) -> dict[ast.Call, frozenset[_Kind]]:
|
|
219
|
+
calls: dict[ast.Call, frozenset[_Kind]] = {}
|
|
220
|
+
for call, qualified in self.native_calls.items():
|
|
221
|
+
contract = _OPERATOR_WRITES.get(qualified)
|
|
222
|
+
if contract is None:
|
|
223
|
+
continue
|
|
224
|
+
kinds, arity = contract
|
|
225
|
+
if len(call.args) == arity:
|
|
226
|
+
calls[call] = kinds
|
|
227
|
+
for call in self.context.nodes(ast.Call):
|
|
228
|
+
if not call.args or isinstance(call.args[0], ast.Starred):
|
|
229
|
+
continue
|
|
230
|
+
if not isinstance(call.func, ast.Attribute) or not isinstance(call.func.value, ast.Name):
|
|
231
|
+
continue
|
|
232
|
+
name = call.func.value.id
|
|
233
|
+
for kind, methods in _MUTATORS.items():
|
|
234
|
+
if name == kind and call.func.attr in methods and self.context.imports.builtin_is_unshadowed(name):
|
|
235
|
+
calls[call] = frozenset({kind})
|
|
236
|
+
return calls
|
|
237
|
+
|
|
238
|
+
@cached_property
|
|
239
|
+
def identity_calls(self) -> dict[ast.Call, ast.expr]:
|
|
240
|
+
calls: dict[ast.Call, ast.expr] = {}
|
|
241
|
+
for call, qualified in self.native_calls.items():
|
|
242
|
+
argument = _IDENTITY_ARGUMENTS.get(qualified)
|
|
243
|
+
if argument is not None and len(call.args) == _IDENTITY_CALL_ARITY:
|
|
244
|
+
calls[call] = call.args[argument]
|
|
245
|
+
return calls
|
|
246
|
+
|
|
156
247
|
def kind(self, annotation: ast.expr | None, *, primitive_elements: bool) -> _Kind | None:
|
|
157
|
-
|
|
158
|
-
try:
|
|
159
|
-
annotation = ast.parse(annotation.value, mode="eval").body
|
|
160
|
-
except SyntaxError:
|
|
161
|
-
return None
|
|
248
|
+
annotation = AnnotationSemantics.parse(annotation)
|
|
162
249
|
if not isinstance(annotation, ast.Subscript) or not isinstance(annotation.value, ast.Name):
|
|
163
250
|
return None
|
|
164
251
|
match annotation.value.id:
|
|
@@ -180,10 +267,64 @@ class _ContainerTransforms:
|
|
|
180
267
|
return None
|
|
181
268
|
return name
|
|
182
269
|
|
|
270
|
+
def returns_container(self, annotation: ast.expr | None) -> bool:
|
|
271
|
+
annotation = AnnotationSemantics.parse(annotation)
|
|
272
|
+
if not isinstance(annotation, ast.Subscript) or not isinstance(annotation.value, ast.Name):
|
|
273
|
+
return False
|
|
274
|
+
name = annotation.value.id
|
|
275
|
+
if name not in {"tuple", "frozenset"}:
|
|
276
|
+
return self.kind(annotation, primitive_elements=False) is not None
|
|
277
|
+
if not self.context.imports.builtin_is_unshadowed(name):
|
|
278
|
+
return False
|
|
279
|
+
elements = annotation.slice.elts if isinstance(annotation.slice, ast.Tuple) else [annotation.slice]
|
|
280
|
+
return name == "tuple" or len(elements) == 1
|
|
281
|
+
|
|
282
|
+
def deferred(self, node: ast.AST) -> bool:
|
|
283
|
+
if not isinstance(node, ast.GeneratorExp):
|
|
284
|
+
return isinstance(node, _DEFERRED_OR_REBINDING)
|
|
285
|
+
parent = self.context.parents.get(node)
|
|
286
|
+
return not (
|
|
287
|
+
isinstance(parent, ast.Call)
|
|
288
|
+
and parent.args == [node]
|
|
289
|
+
and not parent.keywords
|
|
290
|
+
and isinstance(parent.func, ast.Name)
|
|
291
|
+
and parent.func.id in {"list", "tuple", "set", "frozenset"}
|
|
292
|
+
and self.context.imports.builtin_is_unshadowed(parent.func.id)
|
|
293
|
+
and not self.provenance.mock_namespace_patching
|
|
294
|
+
and not any(generator.is_async for generator in node.generators)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def decorators_preserve_body(self, function: _Function) -> bool:
|
|
298
|
+
if not function.decorator_list:
|
|
299
|
+
return True
|
|
300
|
+
if not self._plain_class_namespace(function) or self.provenance.mock_namespace_patching:
|
|
301
|
+
return False
|
|
302
|
+
binders = 0
|
|
303
|
+
for decorator in function.decorator_list:
|
|
304
|
+
if self._native_method_binder(decorator):
|
|
305
|
+
binders += 1
|
|
306
|
+
elif self.provenance.runtime_import_name(decorator) != "typing.final":
|
|
307
|
+
return False
|
|
308
|
+
return binders <= 1
|
|
309
|
+
|
|
310
|
+
def _plain_class_namespace(self, function: _Function) -> bool:
|
|
311
|
+
owner = self.provenance.scope(function)
|
|
312
|
+
return isinstance(owner, ast.ClassDef) and not (
|
|
313
|
+
owner.bases or owner.keywords or owner.decorator_list or owner.type_params
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def _native_method_binder(self, decorator: ast.expr) -> bool:
|
|
317
|
+
return (
|
|
318
|
+
isinstance(decorator, ast.Name)
|
|
319
|
+
and decorator.id in {"staticmethod", "classmethod"}
|
|
320
|
+
and self.context.imports.builtin_is_unshadowed(decorator.id)
|
|
321
|
+
and self.provenance.runtime_binding(decorator.id, decorator) is None
|
|
322
|
+
)
|
|
323
|
+
|
|
183
324
|
def mutations(self, function: _Function) -> list[_Mutation]:
|
|
184
325
|
if (
|
|
185
|
-
function
|
|
186
|
-
or self.
|
|
326
|
+
not self.decorators_preserve_body(function)
|
|
327
|
+
or not self.returns_container(function.returns)
|
|
187
328
|
or self.provenance.dynamic_namespace
|
|
188
329
|
or not self.provenance.builtin_namespace_untouched
|
|
189
330
|
):
|
|
@@ -193,7 +334,7 @@ class _ContainerTransforms:
|
|
|
193
334
|
if isinstance(function.body[-1].value, ast.Constant) and function.body[-1].value.value is None:
|
|
194
335
|
return []
|
|
195
336
|
if not _supported_body(function.body) or any(
|
|
196
|
-
|
|
337
|
+
self.deferred(node) for statement in function.body for node in ast.walk(statement)
|
|
197
338
|
):
|
|
198
339
|
return []
|
|
199
340
|
parameters = (*function.args.posonlyargs, *function.args.args, *function.args.kwonlyargs)
|
|
@@ -204,7 +345,7 @@ class _ContainerTransforms:
|
|
|
204
345
|
}
|
|
205
346
|
if not origins:
|
|
206
347
|
return []
|
|
207
|
-
flow = _ContainerFlow(origins)
|
|
348
|
+
flow = _ContainerFlow(origins, self.argument_writes, self.identity_calls)
|
|
208
349
|
for statement in function.body:
|
|
209
350
|
flow.statement(statement)
|
|
210
351
|
return flow.events
|
|
@@ -212,8 +353,15 @@ class _ContainerTransforms:
|
|
|
212
353
|
|
|
213
354
|
@final
|
|
214
355
|
class _ContainerFlow:
|
|
215
|
-
def __init__(
|
|
356
|
+
def __init__(
|
|
357
|
+
self,
|
|
358
|
+
origins: dict[str, _Borrowed],
|
|
359
|
+
operator_calls: Mapping[ast.Call, frozenset[_Kind]],
|
|
360
|
+
identity_calls: Mapping[ast.Call, ast.expr],
|
|
361
|
+
) -> None:
|
|
216
362
|
self.origins = origins
|
|
363
|
+
self.operator_calls = operator_calls
|
|
364
|
+
self.identity_calls = identity_calls
|
|
217
365
|
self.events: list[_Mutation] = []
|
|
218
366
|
self.active = True
|
|
219
367
|
|
|
@@ -232,7 +380,7 @@ class _ContainerFlow:
|
|
|
232
380
|
pending = [_Evaluation(expression, self.origins)]
|
|
233
381
|
while pending:
|
|
234
382
|
current = pending.pop()
|
|
235
|
-
event = _mutation(current.node, current.origins)
|
|
383
|
+
event = _mutation(current.node, current.origins, self.operator_calls)
|
|
236
384
|
if event is not None:
|
|
237
385
|
self.events.append(event)
|
|
238
386
|
pending.extend(_evaluated_children(current))
|
|
@@ -260,7 +408,7 @@ class _ContainerFlow:
|
|
|
260
408
|
}
|
|
261
409
|
|
|
262
410
|
def _fork(self, statements: list[ast.stmt]) -> _ContainerFlow:
|
|
263
|
-
branch = _ContainerFlow(self.origins.copy())
|
|
411
|
+
branch = _ContainerFlow(self.origins.copy(), self.operator_calls, self.identity_calls)
|
|
264
412
|
for statement in statements:
|
|
265
413
|
branch.statement(statement)
|
|
266
414
|
return branch
|
|
@@ -268,11 +416,11 @@ class _ContainerFlow:
|
|
|
268
416
|
def _bindings(self, statement: ast.stmt) -> None:
|
|
269
417
|
match statement:
|
|
270
418
|
case ast.Assign(targets=targets, value=value):
|
|
271
|
-
origin = _borrowed_origin(value, self.origins)
|
|
419
|
+
origin = _borrowed_origin(value, self.origins, self.identity_calls)
|
|
272
420
|
for target in targets:
|
|
273
421
|
self._assign(target, origin)
|
|
274
422
|
case ast.AnnAssign(target=target, value=value) if value is not None:
|
|
275
|
-
self._assign(target, _borrowed_origin(value, self.origins))
|
|
423
|
+
self._assign(target, _borrowed_origin(value, self.origins, self.identity_calls))
|
|
276
424
|
case ast.AugAssign(target=ast.Name(id=name), op=operation):
|
|
277
425
|
owner = self.origins.get(name)
|
|
278
426
|
if owner is not None and _in_place(owner.kind, operation):
|
|
@@ -304,8 +452,14 @@ class _ContainerFlow:
|
|
|
304
452
|
pass
|
|
305
453
|
|
|
306
454
|
|
|
307
|
-
def _mutation(
|
|
455
|
+
def _mutation(
|
|
456
|
+
node: ast.AST, origins: dict[str, _Borrowed], operator_calls: Mapping[ast.Call, frozenset[_Kind]]
|
|
457
|
+
) -> _Mutation | None:
|
|
308
458
|
match node:
|
|
459
|
+
case ast.Call(args=[ast.Name(id=name), *_]) if node in operator_calls:
|
|
460
|
+
owner = origins.get(name)
|
|
461
|
+
if owner is not None and owner.kind in operator_calls[node]:
|
|
462
|
+
return _Mutation(node, owner)
|
|
309
463
|
case ast.Call(func=ast.Attribute(value=ast.Name(id=name), attr=method)):
|
|
310
464
|
owner = origins.get(name)
|
|
311
465
|
if owner is not None and method in _MUTATORS[owner.kind]:
|
|
@@ -328,9 +482,11 @@ def _evaluated_children(current: _Evaluation) -> list[_Evaluation]:
|
|
|
328
482
|
return [_Evaluation(target, origins), _Evaluation(value, origins)]
|
|
329
483
|
case ast.BoolOp():
|
|
330
484
|
return _boolean_operands(node, origins)
|
|
485
|
+
case ast.Compare():
|
|
486
|
+
return _comparison_operands(node, origins)
|
|
331
487
|
case ast.IfExp():
|
|
332
488
|
return _conditional_operands(node, origins)
|
|
333
|
-
case ast.ListComp() | ast.SetComp() | ast.DictComp():
|
|
489
|
+
case ast.ListComp() | ast.SetComp() | ast.DictComp() | ast.GeneratorExp():
|
|
334
490
|
return _comprehension_operands(node, origins)
|
|
335
491
|
case _:
|
|
336
492
|
return [_Evaluation(child, origins) for child in ast.iter_child_nodes(node)]
|
|
@@ -357,9 +513,18 @@ def _conditional_operands(node: ast.IfExp, origins: dict[str, _Borrowed]) -> lis
|
|
|
357
513
|
return [_Evaluation(node.test, origins), *(_Evaluation(child, origins) for child in alternatives)]
|
|
358
514
|
|
|
359
515
|
|
|
360
|
-
def
|
|
361
|
-
|
|
362
|
-
|
|
516
|
+
def _comparison_operands(node: ast.Compare, origins: dict[str, _Borrowed]) -> list[_Evaluation]:
|
|
517
|
+
left = node.left
|
|
518
|
+
operands = [_Evaluation(left, origins)]
|
|
519
|
+
for operation, right in zip(node.ops, node.comparators, strict=True):
|
|
520
|
+
operands.append(_Evaluation(right, origins))
|
|
521
|
+
if _literal_comparison(left, operation, right) is False:
|
|
522
|
+
break
|
|
523
|
+
left = right
|
|
524
|
+
return operands
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _comprehension_operands(node: _Comprehension, origins: dict[str, _Borrowed]) -> list[_Evaluation]:
|
|
363
528
|
scoped = _comprehension_origins(node, origins)
|
|
364
529
|
operands = [_Evaluation(node.generators[0].iter, origins)]
|
|
365
530
|
for index, generator in enumerate(node.generators):
|
|
@@ -379,9 +544,7 @@ def _comprehension_operands(
|
|
|
379
544
|
return operands
|
|
380
545
|
|
|
381
546
|
|
|
382
|
-
def _comprehension_origins(
|
|
383
|
-
node: ast.ListComp | ast.SetComp | ast.DictComp, origins: dict[str, _Borrowed]
|
|
384
|
-
) -> dict[str, _Borrowed]:
|
|
547
|
+
def _comprehension_origins(node: _Comprehension, origins: dict[str, _Borrowed]) -> dict[str, _Borrowed]:
|
|
385
548
|
targets = {
|
|
386
549
|
child.id
|
|
387
550
|
for generator in node.generators
|
|
@@ -405,6 +568,10 @@ def _literal_truth(expression: ast.expr) -> bool | None:
|
|
|
405
568
|
match expression:
|
|
406
569
|
case ast.Constant(value=value):
|
|
407
570
|
return bool(value)
|
|
571
|
+
case ast.List() | ast.Tuple() | ast.Set() | ast.Dict():
|
|
572
|
+
return _literal_collection_truth(expression)
|
|
573
|
+
case ast.Compare():
|
|
574
|
+
return _comparison_truth(expression)
|
|
408
575
|
case ast.UnaryOp(op=ast.Not(), operand=operand):
|
|
409
576
|
truth = _literal_truth(operand)
|
|
410
577
|
return None if truth is None else not truth
|
|
@@ -426,21 +593,111 @@ def _literal_truth(expression: ast.expr) -> bool | None:
|
|
|
426
593
|
return None
|
|
427
594
|
|
|
428
595
|
|
|
429
|
-
def
|
|
596
|
+
def _comparison_truth(expression: ast.Compare) -> bool | None:
|
|
597
|
+
left = expression.left
|
|
598
|
+
for operation, right in zip(expression.ops, expression.comparators, strict=True):
|
|
599
|
+
truth = _literal_comparison(left, operation, right)
|
|
600
|
+
if truth is not True:
|
|
601
|
+
return truth
|
|
602
|
+
left = right
|
|
603
|
+
return True
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _literal_collection_truth(expression: ast.List | ast.Tuple | ast.Set | ast.Dict) -> bool | None:
|
|
607
|
+
if isinstance(expression, ast.Dict):
|
|
608
|
+
explicit_member = any(key is not None for key in expression.keys)
|
|
609
|
+
empty = all(_empty_literal_iterable(value) for value in expression.values)
|
|
610
|
+
else:
|
|
611
|
+
explicit_member = any(not isinstance(element, ast.Starred) for element in expression.elts)
|
|
612
|
+
empty = all(
|
|
613
|
+
isinstance(element, ast.Starred) and _empty_literal_iterable(element.value) for element in expression.elts
|
|
614
|
+
)
|
|
615
|
+
if explicit_member:
|
|
616
|
+
return True
|
|
617
|
+
return False if empty else None
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _literal_comparison(left: ast.expr, operation: ast.cmpop, right: ast.expr) -> bool | None:
|
|
621
|
+
if not isinstance(left, ast.Constant) or not isinstance(right, ast.Constant):
|
|
622
|
+
return None
|
|
623
|
+
first, second = left.value, right.value
|
|
624
|
+
match operation:
|
|
625
|
+
case ast.Eq():
|
|
626
|
+
return first == second
|
|
627
|
+
case ast.NotEq():
|
|
628
|
+
return first != second
|
|
629
|
+
case ast.Is() | ast.IsNot():
|
|
630
|
+
return _singleton_identity(first, second, negated=isinstance(operation, ast.IsNot))
|
|
631
|
+
case _:
|
|
632
|
+
pass
|
|
633
|
+
match first, second:
|
|
634
|
+
case (int() | float() as a, int() | float() as b):
|
|
635
|
+
return _scalar_order(a, operation, b)
|
|
636
|
+
case (str() as a, str() as b):
|
|
637
|
+
return _scalar_order(a, operation, b)
|
|
638
|
+
case (bytes() as a, bytes() as b):
|
|
639
|
+
return _scalar_order(a, operation, b)
|
|
640
|
+
case _:
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def _scalar_order[T: (int, float, str, bytes)](left: T, operation: ast.cmpop, right: T) -> bool | None:
|
|
645
|
+
match operation:
|
|
646
|
+
case ast.Lt():
|
|
647
|
+
return left < right
|
|
648
|
+
case ast.LtE():
|
|
649
|
+
return left <= right
|
|
650
|
+
case ast.Gt():
|
|
651
|
+
return left > right
|
|
652
|
+
case ast.GtE():
|
|
653
|
+
return left >= right
|
|
654
|
+
case _:
|
|
655
|
+
return None
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _singleton_identity(left: object, right: object, *, negated: bool) -> bool | None:
|
|
659
|
+
if not ((left is None or isinstance(left, bool)) and (right is None or isinstance(right, bool))):
|
|
660
|
+
return None
|
|
661
|
+
same = left is right
|
|
662
|
+
return not same if negated else same
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _borrowed_origin(
|
|
666
|
+
value: ast.expr, origins: dict[str, _Borrowed], identity_calls: Mapping[ast.Call, ast.expr]
|
|
667
|
+
) -> _Borrowed | None:
|
|
668
|
+
while isinstance(value, ast.Call) and value in identity_calls:
|
|
669
|
+
value = identity_calls[value]
|
|
430
670
|
match value:
|
|
431
671
|
case ast.Name(id=name):
|
|
432
672
|
return origins.get(name)
|
|
433
673
|
case ast.IfExp(test=test, body=body, orelse=otherwise):
|
|
434
674
|
truth = _literal_truth(test)
|
|
435
675
|
if truth is not None:
|
|
436
|
-
return _borrowed_origin(body if truth else otherwise, origins)
|
|
437
|
-
first = _borrowed_origin(body, origins)
|
|
438
|
-
second = _borrowed_origin(otherwise, origins)
|
|
676
|
+
return _borrowed_origin(body if truth else otherwise, origins, identity_calls)
|
|
677
|
+
first = _borrowed_origin(body, origins, identity_calls)
|
|
678
|
+
second = _borrowed_origin(otherwise, origins, identity_calls)
|
|
439
679
|
return first if first is not None and first == second else None
|
|
680
|
+
case ast.BoolOp():
|
|
681
|
+
return _boolean_origin(value, origins, identity_calls)
|
|
440
682
|
case _:
|
|
441
683
|
return None
|
|
442
684
|
|
|
443
685
|
|
|
686
|
+
def _boolean_origin(
|
|
687
|
+
value: ast.BoolOp, origins: dict[str, _Borrowed], identity_calls: Mapping[ast.Call, ast.expr]
|
|
688
|
+
) -> _Borrowed | None:
|
|
689
|
+
candidates: list[_Borrowed | None] = []
|
|
690
|
+
for index, operand in enumerate(value.values):
|
|
691
|
+
truth = _literal_truth(operand)
|
|
692
|
+
continues = isinstance(value.op, ast.And) if truth else isinstance(value.op, ast.Or)
|
|
693
|
+
if truth is None or not continues or index == len(value.values) - 1:
|
|
694
|
+
candidates.append(_borrowed_origin(operand, origins, identity_calls))
|
|
695
|
+
if truth is not None and not continues:
|
|
696
|
+
break
|
|
697
|
+
first, *rest = candidates
|
|
698
|
+
return first if first is not None and all(candidate == first for candidate in rest) else None
|
|
699
|
+
|
|
700
|
+
|
|
444
701
|
def _in_place(kind: _Kind, operation: ast.operator) -> bool:
|
|
445
702
|
return (
|
|
446
703
|
(kind == "list" and isinstance(operation, ast.Add | ast.Mult))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/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
|
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/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
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_closed_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.50.0 → sarj_python_lint-1.52.0}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|