sarj-python-lint 1.25.7__tar.gz → 1.26.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.25.7 → sarj_python_lint-1.26.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/pyproject.toml +1 -1
- sarj_python_lint-1.26.1/src/sarj_python_lint/interpreter_argv.py +377 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_registry.py +2 -0
- sarj_python_lint-1.26.1/src/sarj_python_lint/rules/no_interpreter_source_arguments.py +191 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/.gitignore +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/LICENSE +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/README.md +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_file_context.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_source.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_contract_annotation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_copy_on_write.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_dict_key_views.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_local_source.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_nominal_project.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_resource_provenance.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_set_fields.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_test_composition.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_test_provenance.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/async_cleanup_registered_synchronously.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_before_validator_input_mutation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_broad_keyword_capture.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_database_programmability.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_injected_module_loader.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_input_model_mutation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_mocked_pydantic_value_object.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_nullable_dependency_fallback.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_shallow_container_type_guard.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_swallowed_asyncio_cancellation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_test_method_grafting.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_unused_copy_result.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_unused_underscored_keyword_parameter.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_autospec_for_callable_mock.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_monotonic_for_elapsed_time.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_native_string_check.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_pytest_fixture_injection.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_regex_fullmatch.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_required_constructor_parameters.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/repeated_test_composition.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_explicit_contract_implementation.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_explicit_psycopg_transaction.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_explicit_service_contract.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_precise_factory_signature.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_public_dependency_contract.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/shared_mutable_pydantic_factory.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/subprocess_kill_without_reap.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/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.26.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/
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
import re
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_SHORT_ATTACHED_LENGTH = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
ProgramKind = Literal["external", "inline", "shell", "stdin", "other", "unknown"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class InterpreterInvocation:
|
|
22
|
+
kind: ProgramKind
|
|
23
|
+
payload: str | None = None
|
|
24
|
+
forwarded: tuple[str, ...] = ()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class ParsedOption:
|
|
29
|
+
invocation: InterpreterInvocation | None = None
|
|
30
|
+
consumed: int = 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_SHELLS = frozenset({"sh", "bash", "dash", "zsh", "ksh"})
|
|
34
|
+
_NODE_VALUE_FLAGS = frozenset(
|
|
35
|
+
{
|
|
36
|
+
"--require",
|
|
37
|
+
"--import",
|
|
38
|
+
"--loader",
|
|
39
|
+
"--experimental-loader",
|
|
40
|
+
"--input-type",
|
|
41
|
+
"--conditions",
|
|
42
|
+
"--inspect-port",
|
|
43
|
+
"--openssl-config",
|
|
44
|
+
"--icu-data-dir",
|
|
45
|
+
"--title",
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
_JQ_VALUE_FLAGS = MappingProxyType(
|
|
49
|
+
{"--arg": 2, "--argjson": 2, "--slurpfile": 2, "--rawfile": 2, "--indent": 1, "-L": 1}
|
|
50
|
+
)
|
|
51
|
+
_RUBY_VALUE_FLAGS = frozenset({"I", "r", "C", "X", "E"})
|
|
52
|
+
_RUBY_LONG_VALUE_FLAGS = frozenset(
|
|
53
|
+
{"--encoding", "--external-encoding", "--internal-encoding", "--backtrace-limit", "--enable", "--disable"}
|
|
54
|
+
)
|
|
55
|
+
_WRAPPER_VALUES = MappingProxyType(
|
|
56
|
+
{
|
|
57
|
+
"env": frozenset({"-u", "--unset", "-C", "--chdir"}),
|
|
58
|
+
"sudo": frozenset(
|
|
59
|
+
{
|
|
60
|
+
"-u",
|
|
61
|
+
"--user",
|
|
62
|
+
"-g",
|
|
63
|
+
"--group",
|
|
64
|
+
"-h",
|
|
65
|
+
"--host",
|
|
66
|
+
"-p",
|
|
67
|
+
"--prompt",
|
|
68
|
+
"-C",
|
|
69
|
+
"--close-from",
|
|
70
|
+
"-T",
|
|
71
|
+
"--command-timeout",
|
|
72
|
+
"-R",
|
|
73
|
+
"--chroot",
|
|
74
|
+
"-D",
|
|
75
|
+
"--chdir",
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
"timeout": frozenset({"-s", "--signal", "-k", "--kill-after"}),
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
_MAX_WRAPPERS = 8
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class UnprovableCommandError(ValueError):
|
|
85
|
+
"""A wrapper executable position cannot be established safely."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def unwrap_command(argv: Sequence[str]) -> tuple[str, ...]:
|
|
89
|
+
current = tuple(argv)
|
|
90
|
+
for _ in range(_MAX_WRAPPERS):
|
|
91
|
+
if not current:
|
|
92
|
+
return ()
|
|
93
|
+
command = PurePosixPath(current[0]).name
|
|
94
|
+
if command in {"exec", "command"}:
|
|
95
|
+
if command == "command" and any(argument in {"-v", "-V"} for argument in current[1:2]):
|
|
96
|
+
return current
|
|
97
|
+
current = current[_command_wrapper_index(current, command) :]
|
|
98
|
+
continue
|
|
99
|
+
if command not in _WRAPPER_VALUES:
|
|
100
|
+
return current
|
|
101
|
+
current = current[_value_wrapper_index(current, command) :]
|
|
102
|
+
msg = "command wrapper nesting exceeds analysis bound"
|
|
103
|
+
raise UnprovableCommandError(msg)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _command_wrapper_index(arguments: Sequence[str], command: str) -> int:
|
|
107
|
+
index = 1
|
|
108
|
+
while index < len(arguments) and arguments[index].startswith("-"):
|
|
109
|
+
argument = arguments[index]
|
|
110
|
+
if argument == "--":
|
|
111
|
+
return index + 1
|
|
112
|
+
if command == "exec" and argument == "-a":
|
|
113
|
+
index += 2
|
|
114
|
+
continue
|
|
115
|
+
if argument not in {"-p", "-c", "-l"}:
|
|
116
|
+
msg = "unsupported command wrapper option"
|
|
117
|
+
raise UnprovableCommandError(msg)
|
|
118
|
+
index += 1
|
|
119
|
+
return index
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _value_wrapper_index(arguments: Sequence[str], command: str) -> int:
|
|
123
|
+
index = 1
|
|
124
|
+
while index < len(arguments):
|
|
125
|
+
argument = arguments[index]
|
|
126
|
+
if argument == "--":
|
|
127
|
+
index += 1
|
|
128
|
+
break
|
|
129
|
+
if command == "env" and re.match(r"^[A-Za-z_][A-Za-z0-9_]*=", argument):
|
|
130
|
+
index += 1
|
|
131
|
+
continue
|
|
132
|
+
if not argument.startswith("-") or argument == "-":
|
|
133
|
+
break
|
|
134
|
+
if command == "env" and argument in {"-S", "--split-string"}:
|
|
135
|
+
msg = "env split-string has a separate unproven quoting grammar"
|
|
136
|
+
raise UnprovableCommandError(msg)
|
|
137
|
+
index += 2 if argument in _WRAPPER_VALUES[command] else 1
|
|
138
|
+
# Timeout's positional duration precedes its executable.
|
|
139
|
+
return index + int(command == "timeout")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def classify_interpreter(argv: Sequence[str]) -> InterpreterInvocation:
|
|
143
|
+
try:
|
|
144
|
+
arguments = unwrap_command(argv)
|
|
145
|
+
except UnprovableCommandError:
|
|
146
|
+
return InterpreterInvocation("unknown")
|
|
147
|
+
if not arguments:
|
|
148
|
+
return InterpreterInvocation("other")
|
|
149
|
+
executable = PurePosixPath(arguments[0]).name
|
|
150
|
+
if re.fullmatch(r"python(?:[23](?:\.\d+)?)?", executable):
|
|
151
|
+
return _python(arguments[1:])
|
|
152
|
+
if executable in _SHELLS:
|
|
153
|
+
return _short_options(arguments[1:], source="c", values=frozenset({"o", "O"}), shell_mode=True)
|
|
154
|
+
if executable in {"node", "nodejs"}:
|
|
155
|
+
return _node(arguments[1:])
|
|
156
|
+
if executable == "jq":
|
|
157
|
+
return _jq(arguments[1:])
|
|
158
|
+
if executable in {"awk", "gawk", "mawk", "nawk"}:
|
|
159
|
+
return _awk(arguments[1:])
|
|
160
|
+
if executable == "ruby":
|
|
161
|
+
return _ruby(arguments[1:])
|
|
162
|
+
if executable in {"perl", "php"}:
|
|
163
|
+
return _short_options(arguments[1:], source={"perl": "eE", "php": "r"}[executable], values=frozenset({"I"}))
|
|
164
|
+
return InterpreterInvocation("other")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _python(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
168
|
+
index = 0
|
|
169
|
+
while index < len(arguments):
|
|
170
|
+
boundary = _external_boundary(arguments, index)
|
|
171
|
+
if boundary is not None:
|
|
172
|
+
return boundary
|
|
173
|
+
argument = arguments[index]
|
|
174
|
+
if argument.startswith("--"):
|
|
175
|
+
return InterpreterInvocation(
|
|
176
|
+
"external"
|
|
177
|
+
if argument in {"--help", "--version", "--help-env", "--help-xoptions", "--help-all"}
|
|
178
|
+
else "unknown"
|
|
179
|
+
)
|
|
180
|
+
parsed = _python_short_option(argument, _following_operand(arguments, index))
|
|
181
|
+
if parsed.invocation is not None:
|
|
182
|
+
return parsed.invocation
|
|
183
|
+
index += parsed.consumed
|
|
184
|
+
return InterpreterInvocation("stdin")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _python_short_option(argument: str, following: str | None) -> ParsedOption:
|
|
188
|
+
for cursor, flag in enumerate(argument[1:], 1):
|
|
189
|
+
if flag in {"h", "V"}:
|
|
190
|
+
return ParsedOption(InterpreterInvocation("external"))
|
|
191
|
+
if flag in {"c", "m", "W", "X"}:
|
|
192
|
+
attached = argument[cursor + 1 :]
|
|
193
|
+
operand = attached or following
|
|
194
|
+
if operand is None:
|
|
195
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
196
|
+
if flag == "c":
|
|
197
|
+
return ParsedOption(InterpreterInvocation("inline", operand))
|
|
198
|
+
if flag == "m":
|
|
199
|
+
return ParsedOption(InterpreterInvocation("external", operand))
|
|
200
|
+
return ParsedOption(consumed=1 + int(not attached))
|
|
201
|
+
if flag not in "bBdEhiIOPqRsSuvVx":
|
|
202
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
203
|
+
return ParsedOption()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _ruby(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
207
|
+
index = 0
|
|
208
|
+
while index < len(arguments):
|
|
209
|
+
boundary = _external_boundary(arguments, index)
|
|
210
|
+
if boundary is not None:
|
|
211
|
+
return boundary
|
|
212
|
+
parsed = _ruby_option(arguments, index)
|
|
213
|
+
if parsed.invocation is not None:
|
|
214
|
+
return parsed.invocation
|
|
215
|
+
index += parsed.consumed
|
|
216
|
+
return InterpreterInvocation("stdin")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _ruby_option(arguments: Sequence[str], index: int) -> ParsedOption:
|
|
220
|
+
argument = arguments[index]
|
|
221
|
+
if argument.startswith("--"):
|
|
222
|
+
return _ruby_long_option(argument, _following_operand(arguments, index))
|
|
223
|
+
if re.fullmatch(r"-(?:0[0-7]{0,3}|W[0-2]?)", argument):
|
|
224
|
+
return ParsedOption()
|
|
225
|
+
for cursor, flag in enumerate(argument[1:], 1):
|
|
226
|
+
if flag == "h":
|
|
227
|
+
return ParsedOption(InterpreterInvocation("external"))
|
|
228
|
+
if flag in "Fix":
|
|
229
|
+
# These operands are attached only and consume the rest of this word.
|
|
230
|
+
return ParsedOption()
|
|
231
|
+
if flag == "e" or flag in _RUBY_VALUE_FLAGS:
|
|
232
|
+
if cursor + 1 == len(argument) and _following_operand(arguments, index) is None:
|
|
233
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
234
|
+
return _short_option(arguments, index, source="e", values=_RUBY_VALUE_FLAGS, shell_mode=False)
|
|
235
|
+
if flag not in "acdlnpsSvwU":
|
|
236
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
237
|
+
return ParsedOption()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _ruby_long_option(argument: str, following: str | None) -> ParsedOption:
|
|
241
|
+
if argument in {"--help", "--version"}:
|
|
242
|
+
return ParsedOption(InterpreterInvocation("external"))
|
|
243
|
+
if argument in {"--debug", "--verbose", "--copyright"}:
|
|
244
|
+
return ParsedOption()
|
|
245
|
+
name, separator, _operand = argument.partition("=")
|
|
246
|
+
if name in _RUBY_LONG_VALUE_FLAGS and (separator or following is not None):
|
|
247
|
+
return ParsedOption(consumed=1 + int(not separator))
|
|
248
|
+
return ParsedOption(InterpreterInvocation("unknown"))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _short_options(
|
|
252
|
+
arguments: Sequence[str], *, source: str, values: frozenset[str], shell_mode: bool = False
|
|
253
|
+
) -> InterpreterInvocation:
|
|
254
|
+
index = 0
|
|
255
|
+
while index < len(arguments):
|
|
256
|
+
boundary = _external_boundary(arguments, index, plus_options=True)
|
|
257
|
+
if boundary is not None:
|
|
258
|
+
return boundary
|
|
259
|
+
argument = arguments[index]
|
|
260
|
+
if argument.startswith("--"):
|
|
261
|
+
index += 2 if shell_mode and argument in {"--rcfile", "--init-file"} else 1
|
|
262
|
+
continue
|
|
263
|
+
parsed = _short_option(arguments, index, source=source, values=values, shell_mode=shell_mode)
|
|
264
|
+
if parsed.invocation is not None:
|
|
265
|
+
return parsed.invocation
|
|
266
|
+
index += parsed.consumed
|
|
267
|
+
return InterpreterInvocation("stdin")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _short_option(
|
|
271
|
+
arguments: Sequence[str], index: int, *, source: str, values: frozenset[str], shell_mode: bool
|
|
272
|
+
) -> ParsedOption:
|
|
273
|
+
argument = arguments[index]
|
|
274
|
+
for cursor, flag in enumerate(argument[1:], 1):
|
|
275
|
+
if flag in source:
|
|
276
|
+
# Shell -c always consumes the next word, including inside -ceu.
|
|
277
|
+
attached = "" if shell_mode else argument[cursor + 1 :]
|
|
278
|
+
operand = attached or _following_operand(arguments, index)
|
|
279
|
+
forwarded = tuple(arguments[index + 3 :]) if shell_mode else ()
|
|
280
|
+
return ParsedOption(InterpreterInvocation("shell" if shell_mode else "inline", operand, forwarded))
|
|
281
|
+
if flag in values:
|
|
282
|
+
return ParsedOption(consumed=1 + int(cursor + 1 == len(argument)))
|
|
283
|
+
return ParsedOption()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _node(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
287
|
+
index = 0
|
|
288
|
+
while index < len(arguments):
|
|
289
|
+
boundary = _external_boundary(arguments, index)
|
|
290
|
+
if boundary is not None:
|
|
291
|
+
return boundary
|
|
292
|
+
argument = arguments[index]
|
|
293
|
+
inline = _node_source_option(argument, _following_operand(arguments, index))
|
|
294
|
+
if inline is not None:
|
|
295
|
+
return inline
|
|
296
|
+
index += 2 if argument in _NODE_VALUE_FLAGS or argument in {"-r", "-C"} else 1
|
|
297
|
+
return InterpreterInvocation("stdin")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _node_source_option(argument: str, following: str | None) -> InterpreterInvocation | None:
|
|
301
|
+
if argument.split("=", 1)[0] in {"--eval", "--print"}:
|
|
302
|
+
operand = argument.split("=", 1)[1] if "=" in argument else following
|
|
303
|
+
return InterpreterInvocation("inline", operand)
|
|
304
|
+
if argument.startswith("--"):
|
|
305
|
+
return None
|
|
306
|
+
for flag in argument[1:]:
|
|
307
|
+
if flag in "ep":
|
|
308
|
+
return InterpreterInvocation("inline")
|
|
309
|
+
if flag in "rC":
|
|
310
|
+
break
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _external_boundary(
|
|
315
|
+
arguments: Sequence[str], index: int, *, plus_options: bool = False
|
|
316
|
+
) -> InterpreterInvocation | None:
|
|
317
|
+
argument = arguments[index]
|
|
318
|
+
if argument == "--":
|
|
319
|
+
return InterpreterInvocation("external" if index + 1 < len(arguments) else "stdin")
|
|
320
|
+
if argument == "-":
|
|
321
|
+
return InterpreterInvocation("stdin")
|
|
322
|
+
prefixes = ("-", "+") if plus_options else ("-",)
|
|
323
|
+
return None if argument.startswith(prefixes) else InterpreterInvocation("external")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _following_operand(arguments: Sequence[str], index: int) -> str | None:
|
|
327
|
+
return arguments[index + 1] if index + 1 < len(arguments) else None
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _jq(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
331
|
+
index = 0
|
|
332
|
+
while index < len(arguments):
|
|
333
|
+
argument = arguments[index]
|
|
334
|
+
if (
|
|
335
|
+
argument in {"-f", "--from-file"}
|
|
336
|
+
or argument.startswith("--from-file=")
|
|
337
|
+
or (argument.startswith("-f") and len(argument) > _SHORT_ATTACHED_LENGTH)
|
|
338
|
+
):
|
|
339
|
+
return InterpreterInvocation("external")
|
|
340
|
+
if argument.startswith("-") and not argument.startswith("--") and "f" in argument[1:]:
|
|
341
|
+
return InterpreterInvocation("external")
|
|
342
|
+
if argument == "--":
|
|
343
|
+
return InterpreterInvocation("inline" if index + 1 < len(arguments) else "other")
|
|
344
|
+
if not argument.startswith("-"):
|
|
345
|
+
return InterpreterInvocation("inline", argument)
|
|
346
|
+
index += 1 + _JQ_VALUE_FLAGS.get(argument, 0)
|
|
347
|
+
return InterpreterInvocation("other")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _awk(arguments: Sequence[str]) -> InterpreterInvocation:
|
|
351
|
+
index = 0
|
|
352
|
+
external = False
|
|
353
|
+
while index < len(arguments):
|
|
354
|
+
argument = arguments[index]
|
|
355
|
+
if argument in {"-f", "--file", "-E", "--exec"}:
|
|
356
|
+
external = True
|
|
357
|
+
index += 2
|
|
358
|
+
continue
|
|
359
|
+
if argument.startswith(("--file=", "--exec=")) or (
|
|
360
|
+
argument.startswith(("-f", "-E")) and len(argument) > _SHORT_ATTACHED_LENGTH
|
|
361
|
+
):
|
|
362
|
+
external = True
|
|
363
|
+
elif (
|
|
364
|
+
argument in {"-e", "--source"}
|
|
365
|
+
or argument.startswith("--source=")
|
|
366
|
+
or (argument.startswith("-e") and len(argument) > _SHORT_ATTACHED_LENGTH)
|
|
367
|
+
):
|
|
368
|
+
return InterpreterInvocation("inline")
|
|
369
|
+
elif argument in {"-F", "-v", "--field-separator", "--assign"}:
|
|
370
|
+
index += 2
|
|
371
|
+
continue
|
|
372
|
+
elif argument == "--":
|
|
373
|
+
return InterpreterInvocation("external" if external else "inline")
|
|
374
|
+
elif not argument.startswith("-"):
|
|
375
|
+
return InterpreterInvocation("external" if external else "inline", argument)
|
|
376
|
+
index += 1
|
|
377
|
+
return InterpreterInvocation("external" if external else "other")
|
|
@@ -62,6 +62,7 @@ from sarj_python_lint.rules.no_hidden_constructor_fallback import (
|
|
|
62
62
|
)
|
|
63
63
|
from sarj_python_lint.rules.no_injected_module_loader import NoInjectedModuleLoader
|
|
64
64
|
from sarj_python_lint.rules.no_input_model_mutation import NoInputModelMutation
|
|
65
|
+
from sarj_python_lint.rules.no_interpreter_source_arguments import NoInterpreterSourceArguments
|
|
65
66
|
from sarj_python_lint.rules.no_invalid_argument_name_suppression import NoInvalidArgumentNameSuppression
|
|
66
67
|
from sarj_python_lint.rules.no_mocked_pydantic_value_object import NoMockedPydanticValueObject
|
|
67
68
|
from sarj_python_lint.rules.no_nested_pydantic_field_validator import NoNestedPydanticFieldValidator
|
|
@@ -207,6 +208,7 @@ if TYPE_CHECKING:
|
|
|
207
208
|
|
|
208
209
|
REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
209
210
|
{
|
|
211
|
+
NoInterpreterSourceArguments.id: NoInterpreterSourceArguments,
|
|
210
212
|
NoUnusedCopyResult.id: NoUnusedCopyResult,
|
|
211
213
|
NoBeforeValidatorInputMutation.id: NoBeforeValidatorInputMutation,
|
|
212
214
|
NoInputModelMutation.id: NoInputModelMutation,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import PurePosixPath
|
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, final, override
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.interpreter_argv import classify_interpreter
|
|
9
|
+
from sarj_python_lint.rule_base import (
|
|
10
|
+
AutofixPolicy,
|
|
11
|
+
Diagnostic,
|
|
12
|
+
ExampleFile,
|
|
13
|
+
ExampleOutcome,
|
|
14
|
+
ProjectRule,
|
|
15
|
+
RuleCategory,
|
|
16
|
+
RuleDocumentation,
|
|
17
|
+
RuleExample,
|
|
18
|
+
Severity,
|
|
19
|
+
is_suppressed,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from sarj_python_lint._file_context import PythonFileContext
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_SUBPROCESS = frozenset(
|
|
28
|
+
{"subprocess.run", "subprocess.call", "subprocess.check_call", "subprocess.check_output", "subprocess.Popen"}
|
|
29
|
+
)
|
|
30
|
+
_EXEC_VECTOR = frozenset({"os.execv", "os.execve", "os.execvp", "os.execvpe", "os.posix_spawn", "os.posix_spawnp"})
|
|
31
|
+
_EXEC_LIST = frozenset({"os.execl", "os.execle", "os.execlp", "os.execlpe"})
|
|
32
|
+
_SPAWN_VECTOR = frozenset({"os.spawnv", "os.spawnve", "os.spawnvp", "os.spawnvpe"})
|
|
33
|
+
_SPAWN_LIST = frozenset({"os.spawnl", "os.spawnle", "os.spawnlp", "os.spawnlpe"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True, slots=True)
|
|
37
|
+
class ProcessArguments:
|
|
38
|
+
argv: ast.expr | None
|
|
39
|
+
executable: ast.expr | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@final
|
|
43
|
+
class NoInterpreterSourceArguments(ProjectRule):
|
|
44
|
+
id = "no-interpreter-source-arguments"
|
|
45
|
+
code = "SARJ484"
|
|
46
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
47
|
+
default_level=Severity.ERROR,
|
|
48
|
+
summary="Keep interpreter programs in linted files or modules, outside process argv.",
|
|
49
|
+
rationale="Moving deployment logic from YAML into Python still bypasses language checks when the Python wrapper passes program source through interpreter flags or jq/awk argv.",
|
|
50
|
+
remediation="Invoke a repository-owned file or module covered by the normal language checks; pass configuration as arguments rather than interpreter source.",
|
|
51
|
+
category=RuleCategory.ARCHITECTURE,
|
|
52
|
+
autofix=AutofixPolicy.NONE,
|
|
53
|
+
limitations=(
|
|
54
|
+
"Only import-proven subprocess process APIs, asyncio.create_subprocess_exec, and os exec/spawn argv APIs with literal string vectors are checked. Import aliases are followed; shadowed or reassigned imports and methods abstain.",
|
|
55
|
+
"Script/module and interpreter option boundaries use the shared dependency-free classifier, including env/sudo/timeout wrappers and jq/awk file flags.",
|
|
56
|
+
"Dynamic argv, shell strings and shell=True, recursive shell payloads, receiver-based remote execution, and unknown interpreter option grammars are outside this narrow rule. A clean result does not prove those programs are linted.",
|
|
57
|
+
"Generated/vendor sources are excluded; maintained tests are checked. Source-checker fixtures may use an exact reasoned suppression.",
|
|
58
|
+
),
|
|
59
|
+
examples=(
|
|
60
|
+
RuleExample(
|
|
61
|
+
example_id="interpreter-source-argument",
|
|
62
|
+
title="Process argv contains unchecked Python source",
|
|
63
|
+
outcome=ExampleOutcome.MATCH,
|
|
64
|
+
files=(
|
|
65
|
+
ExampleFile.python(
|
|
66
|
+
"tools/check.py",
|
|
67
|
+
"import subprocess\nsubprocess.run(['python3', '-c', 'print(1)'], check=True)\n",
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
focus_path=PurePosixPath("tools/check.py"),
|
|
71
|
+
expected_count=1,
|
|
72
|
+
public=True,
|
|
73
|
+
),
|
|
74
|
+
RuleExample(
|
|
75
|
+
example_id="external-interpreter-module",
|
|
76
|
+
title="Process argv invokes a checked module",
|
|
77
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
78
|
+
files=(
|
|
79
|
+
ExampleFile.python(
|
|
80
|
+
"tools/check.py",
|
|
81
|
+
"import subprocess\nsubprocess.run(['python3', '-m', 'tools.verify', '-c', 'strict'], check=True)\n",
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
focus_path=PurePosixPath("tools/check.py"),
|
|
85
|
+
expected_count=0,
|
|
86
|
+
public=True,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
description = documentation.summary
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
94
|
+
if context.generated or context.tree is None:
|
|
95
|
+
return []
|
|
96
|
+
imports = context.imports
|
|
97
|
+
replaced = {
|
|
98
|
+
name
|
|
99
|
+
for node in context.nodes(ast.Attribute)
|
|
100
|
+
if isinstance(node.ctx, (ast.Store, ast.Del))
|
|
101
|
+
and (name := imports.resolved_qualified_name(node)) is not None
|
|
102
|
+
}
|
|
103
|
+
findings: list[Diagnostic] = []
|
|
104
|
+
for call in context.nodes(ast.Call):
|
|
105
|
+
sink = imports.resolved_qualified_name(call.func)
|
|
106
|
+
if sink is None or sink in replaced or is_suppressed(context.source_lines, call.lineno, self.code):
|
|
107
|
+
continue
|
|
108
|
+
argv = _literal_argv(call, sink)
|
|
109
|
+
if argv is None or classify_interpreter(argv).kind != "inline":
|
|
110
|
+
continue
|
|
111
|
+
findings.append(
|
|
112
|
+
Diagnostic(
|
|
113
|
+
path=context.path,
|
|
114
|
+
line=call.lineno,
|
|
115
|
+
col=call.col_offset + 1,
|
|
116
|
+
code=self.code,
|
|
117
|
+
message="Interpreter source is embedded in process argv; invoke a linted repository-owned file or module instead.",
|
|
118
|
+
severity=Severity.ERROR,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
return findings
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _literal_argv(call: ast.Call, sink: str) -> tuple[str, ...] | None:
|
|
125
|
+
if any(keyword.arg is None for keyword in call.keywords):
|
|
126
|
+
return None # Expanded options can replace the executable or enable shell=True.
|
|
127
|
+
projected = _process_arguments(call, sink)
|
|
128
|
+
if projected is None:
|
|
129
|
+
return None
|
|
130
|
+
argv = _vector(projected.argv)
|
|
131
|
+
if not argv:
|
|
132
|
+
return None
|
|
133
|
+
return _override_executable(argv, projected.executable)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _override_executable(argv: tuple[str, ...], executable: ast.expr | None) -> tuple[str, ...] | None:
|
|
137
|
+
if executable is None:
|
|
138
|
+
return argv
|
|
139
|
+
resolved = _string(executable)
|
|
140
|
+
return None if resolved is None else (resolved, *argv[1:])
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _process_arguments(call: ast.Call, sink: str) -> ProcessArguments | None:
|
|
144
|
+
if sink in _SUBPROCESS:
|
|
145
|
+
return _subprocess_arguments(call)
|
|
146
|
+
if sink == "asyncio.create_subprocess_exec":
|
|
147
|
+
return _asyncio_arguments(call)
|
|
148
|
+
if sink in _EXEC_VECTOR | _SPAWN_VECTOR:
|
|
149
|
+
offset = 1 if sink in _SPAWN_VECTOR else 0
|
|
150
|
+
return ProcessArguments(
|
|
151
|
+
_argument(call, "argv" if "posix_spawn" in sink else "args", offset + 1), _argument(call, "path", offset)
|
|
152
|
+
)
|
|
153
|
+
if sink in _EXEC_LIST | _SPAWN_LIST:
|
|
154
|
+
offset = 1 if sink in _SPAWN_LIST else 0
|
|
155
|
+
arguments = call.args[offset + 1 : -1] if sink.endswith("e") else call.args[offset + 1 :]
|
|
156
|
+
return ProcessArguments(ast.Tuple(elts=arguments, ctx=ast.Load()), _argument(call, "path", offset))
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _subprocess_arguments(call: ast.Call) -> ProcessArguments | None:
|
|
161
|
+
shell = _argument(call, "shell", len(call.args))
|
|
162
|
+
if shell is not None and not (isinstance(shell, ast.Constant) and shell.value is False):
|
|
163
|
+
return None
|
|
164
|
+
return ProcessArguments(_argument(call, "args", 0), _argument(call, "executable", len(call.args)))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _asyncio_arguments(call: ast.Call) -> ProcessArguments:
|
|
168
|
+
arguments = call.args
|
|
169
|
+
if not arguments:
|
|
170
|
+
program = _argument(call, "program", 0)
|
|
171
|
+
arguments = [program] if program is not None else []
|
|
172
|
+
return ProcessArguments(ast.Tuple(elts=arguments, ctx=ast.Load()), _argument(call, "executable", len(call.args)))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _argument(call: ast.Call, name: str, index: int) -> ast.expr | None:
|
|
176
|
+
return (
|
|
177
|
+
call.args[index]
|
|
178
|
+
if index < len(call.args)
|
|
179
|
+
else next((keyword.value for keyword in call.keywords if keyword.arg == name), None)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _vector(node: ast.expr | None) -> tuple[str, ...] | None:
|
|
184
|
+
if not isinstance(node, (ast.List, ast.Tuple)):
|
|
185
|
+
return None
|
|
186
|
+
words = tuple(_string(item) for item in node.elts)
|
|
187
|
+
return tuple(word for word in words if word is not None) if all(word is not None for word in words) else None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _string(node: ast.expr | None) -> str | None:
|
|
191
|
+
return node.value if isinstance(node, ast.Constant) and isinstance(node.value, str) else None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.7 → sarj_python_lint-1.26.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
|
|
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.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_ast_position.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_copy_on_write.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_dict_key_views.py
RENAMED
|
File without changes
|
{sarj_python_lint-1.25.7 → sarj_python_lint-1.26.1}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|