sarj-python-lint 1.1.0__tar.gz → 1.1.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.1.0 → sarj_python_lint-1.1.1}/PKG-INFO +1 -1
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/pyproject.toml +1 -1
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/__main__.py +14 -11
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_file_context.py +5 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_ast_index.py +15 -2
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +15 -15
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/fastapi_class_router_contract.py +6 -9
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +6 -10
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_select_star.py +5 -3
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_typed_doc_sections.py +9 -11
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +6 -6
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +3 -5
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_unused_value_marker.py +6 -4
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +8 -10
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +6 -6
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +8 -6
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +9 -8
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +5 -4
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +5 -4
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +7 -5
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/.gitignore +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/LICENSE +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/README.md +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_python_target.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/benchmark.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/json_boundary.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_annotation_semantics.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_ast_position.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/async_mock_call_without_await_assertion.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/named_record_at_boundaries.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_any_mapping_types.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_delete_statement.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_fixed_sleep_before_assert.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_invalid_argument_name_suppression.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_injected_dependency_over_monkeypatch.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_match_exception_dispatch.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_monkeypatch_for_process_state_in_test.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_pydantic_json_value.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_pydantic_for_structured_payload.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-1.1.0 → sarj_python_lint-1.1.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.1.
|
|
3
|
+
Version: 1.1.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/
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import ast
|
|
4
4
|
from collections import Counter
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from fnmatch import fnmatch
|
|
6
7
|
import json
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
import sys
|
|
@@ -66,19 +67,21 @@ def expand_paths(paths: list[Path]) -> list[Path]:
|
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def _python_files(p: Path) -> list[Path]:
|
|
70
|
+
if not SKIP_DIR_NAMES.isdisjoint(p.parts):
|
|
71
|
+
return []
|
|
69
72
|
out: list[Path] = []
|
|
70
|
-
for
|
|
71
|
-
if not
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
continue
|
|
75
|
-
try:
|
|
76
|
-
if child.stat().st_size > _MAX_FILE_BYTES:
|
|
73
|
+
for directory, directories, filenames in p.walk():
|
|
74
|
+
directories[:] = [name for name in directories if name not in SKIP_DIR_NAMES]
|
|
75
|
+
for name in filenames:
|
|
76
|
+
if not fnmatch(name, "*.py"):
|
|
77
77
|
continue
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
child = directory / name
|
|
79
|
+
try:
|
|
80
|
+
if child.is_file() and child.stat().st_size <= _MAX_FILE_BYTES:
|
|
81
|
+
out.append(child)
|
|
82
|
+
except OSError:
|
|
83
|
+
continue
|
|
84
|
+
return sorted(out)
|
|
82
85
|
|
|
83
86
|
|
|
84
87
|
def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
|
|
@@ -12,6 +12,7 @@ from sarj_python_lint.rules._paths import is_generated
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Mapping
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
|
|
@@ -48,6 +49,10 @@ class PythonFileContext:
|
|
|
48
49
|
def nodes[NodeT: ast.AST](self, *types: type[NodeT]) -> list[NodeT]:
|
|
49
50
|
return [node for node in self.node_index.query(types) if isinstance(node, types)]
|
|
50
51
|
|
|
52
|
+
@property
|
|
53
|
+
def parents(self) -> Mapping[ast.AST, ast.AST]:
|
|
54
|
+
return self.node_index.parents
|
|
55
|
+
|
|
51
56
|
@cached_property
|
|
52
57
|
def imports(self) -> ImportIndex:
|
|
53
58
|
return ImportIndex.from_tree(self._valid_tree())
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
+
from types import MappingProxyType
|
|
6
7
|
from typing import TYPE_CHECKING, TypeIs, final
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
|
-
from collections.abc import Iterator
|
|
11
|
+
from collections.abc import Iterator, Mapping
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
_AST = ast.AST
|
|
@@ -43,7 +44,7 @@ def walk(node: ast.AST, *, index: NodeIndex | None = None) -> Iterator[ast.AST]:
|
|
|
43
44
|
|
|
44
45
|
@final
|
|
45
46
|
class NodeIndex:
|
|
46
|
-
__slots__ = ("_buckets", "_flat", "_queries", "tree")
|
|
47
|
+
__slots__ = ("_buckets", "_flat", "_parents", "_queries", "tree")
|
|
47
48
|
|
|
48
49
|
def __init__(self, tree: ast.AST) -> None:
|
|
49
50
|
self.tree = tree
|
|
@@ -62,8 +63,15 @@ class NodeIndex:
|
|
|
62
63
|
flat += children(current)
|
|
63
64
|
self._buckets: dict[type[ast.AST], list[ast.AST]] = buckets
|
|
64
65
|
self._flat: list[ast.AST] = flat
|
|
66
|
+
self._parents: Mapping[ast.AST, ast.AST] | None = None
|
|
65
67
|
self._queries: dict[tuple[type[ast.AST], ...], list[ast.AST]] = {}
|
|
66
68
|
|
|
69
|
+
@property
|
|
70
|
+
def parents(self) -> Mapping[ast.AST, ast.AST]:
|
|
71
|
+
if self._parents is None:
|
|
72
|
+
self._parents = MappingProxyType({child: parent for parent in self._flat for child in children(parent)})
|
|
73
|
+
return self._parents
|
|
74
|
+
|
|
67
75
|
def query(self, types: tuple[type[ast.AST], ...]) -> list[ast.AST]:
|
|
68
76
|
hit = self._queries.get(types)
|
|
69
77
|
if hit is not None:
|
|
@@ -87,3 +95,8 @@ class NodeIndex:
|
|
|
87
95
|
def nodes[NodeT: ast.AST](tree: ast.AST, *types: type[NodeT], index: NodeIndex | None = None) -> list[NodeT]:
|
|
88
96
|
selected = index if index is not None and index.tree is tree else NodeIndex(tree)
|
|
89
97
|
return [node for node in selected.query(types) if isinstance(node, types)]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parent_map(tree: ast.AST, *, index: NodeIndex | None = None) -> Mapping[ast.AST, ast.AST]:
|
|
101
|
+
selected = index if index is not None and index.tree is tree else NodeIndex(tree)
|
|
102
|
+
return selected.parents
|
|
@@ -23,7 +23,7 @@ from sarj_python_lint.rules.no_analytical_aggregation_in_postgres_store import a
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
|
-
from collections.abc import Callable
|
|
26
|
+
from collections.abc import Callable, Mapping
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
29
|
from sqlglot import exp
|
|
@@ -83,18 +83,18 @@ def _docstring_node_ids(tree: ast.AST, *, node_index: NodeIndex | None = None) -
|
|
|
83
83
|
return result
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def _is_query_context(node: ast.expr, parents:
|
|
86
|
+
def _is_query_context(node: ast.expr, parents: Mapping[ast.AST, ast.AST], roots: frozenset[int]) -> bool:
|
|
87
87
|
current: ast.AST = node
|
|
88
88
|
while True:
|
|
89
89
|
if id(current) in roots:
|
|
90
90
|
return True
|
|
91
|
-
if (parent := parents.get(
|
|
91
|
+
if (parent := parents.get(current)) is None:
|
|
92
92
|
return False
|
|
93
93
|
current = parent
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def _query_roots(
|
|
97
|
-
tree: ast.Module, parents:
|
|
97
|
+
tree: ast.Module, parents: Mapping[ast.AST, ast.AST], *, node_index: NodeIndex | None = None
|
|
98
98
|
) -> frozenset[int]:
|
|
99
99
|
binding_counts: dict[tuple[int, str], int] = {}
|
|
100
100
|
binding_values: dict[tuple[int, str], ast.expr] = {}
|
|
@@ -133,10 +133,10 @@ def _query_roots(
|
|
|
133
133
|
return frozenset(roots)
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
def _scope(node: ast.AST, parents:
|
|
136
|
+
def _scope(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> ast.AST:
|
|
137
137
|
current = node
|
|
138
138
|
while not isinstance(current, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)):
|
|
139
|
-
parent = parents.get(
|
|
139
|
+
parent = parents.get(current)
|
|
140
140
|
if parent is None:
|
|
141
141
|
return current
|
|
142
142
|
current = parent
|
|
@@ -169,7 +169,7 @@ def _call_name(function: ast.expr) -> str:
|
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
def _postgres_sql_constructors(
|
|
172
|
-
tree: ast.Module, parents:
|
|
172
|
+
tree: ast.Module, parents: Mapping[ast.AST, ast.AST], *, node_index: NodeIndex | None = None
|
|
173
173
|
) -> frozenset[_ConstructorImport]:
|
|
174
174
|
constructors: set[_ConstructorImport] = set()
|
|
175
175
|
for node in nodes(tree, ast.Import, ast.ImportFrom, index=node_index):
|
|
@@ -204,7 +204,7 @@ def _qualified_name(node: ast.expr) -> str:
|
|
|
204
204
|
def _visible_constructors(
|
|
205
205
|
owner: ast.AST,
|
|
206
206
|
imports: frozenset[_ConstructorImport],
|
|
207
|
-
parents:
|
|
207
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
208
208
|
binding_counts: dict[tuple[int, str], int],
|
|
209
209
|
wildcard_scopes: set[int],
|
|
210
210
|
) -> frozenset[str]:
|
|
@@ -212,7 +212,7 @@ def _visible_constructors(
|
|
|
212
212
|
current = owner
|
|
213
213
|
while True:
|
|
214
214
|
scope_chain.append(id(current))
|
|
215
|
-
parent = parents.get(
|
|
215
|
+
parent = parents.get(current)
|
|
216
216
|
if parent is None:
|
|
217
217
|
break
|
|
218
218
|
current = _scope(parent, parents)
|
|
@@ -380,7 +380,7 @@ class ComplexPostgresQueryRequiresArchitectureReview(Rule):
|
|
|
380
380
|
return []
|
|
381
381
|
|
|
382
382
|
docstrings = _docstring_node_ids(tree, node_index=context.node_index)
|
|
383
|
-
parents =
|
|
383
|
+
parents = context.parents
|
|
384
384
|
query_roots = _query_roots(tree, parents, node_index=context.node_index)
|
|
385
385
|
diagnostics: list[Diagnostic] = []
|
|
386
386
|
consumed: set[int] = set()
|
|
@@ -417,7 +417,7 @@ class ComplexPostgresQueryRequiresArchitectureReview(Rule):
|
|
|
417
417
|
|
|
418
418
|
def _count_query_bindings(
|
|
419
419
|
tree: ast.Module,
|
|
420
|
-
parents:
|
|
420
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
421
421
|
count: Callable[[ast.AST, str], None],
|
|
422
422
|
wildcard_scopes: set[int],
|
|
423
423
|
*,
|
|
@@ -430,7 +430,7 @@ def _count_query_bindings(
|
|
|
430
430
|
count(_scope(argument, parents), argument.arg)
|
|
431
431
|
_count_import_bindings(tree, parents, count, wildcard_scopes, node_index=node_index)
|
|
432
432
|
for definition in nodes(tree, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, index=node_index):
|
|
433
|
-
parent = parents.get(
|
|
433
|
+
parent = parents.get(definition)
|
|
434
434
|
if parent is not None:
|
|
435
435
|
count(_scope(parent, parents), definition.name)
|
|
436
436
|
for handler in nodes(tree, ast.ExceptHandler, index=node_index):
|
|
@@ -447,7 +447,7 @@ def _count_query_bindings(
|
|
|
447
447
|
|
|
448
448
|
def _query_binding_values(
|
|
449
449
|
tree: ast.Module,
|
|
450
|
-
parents:
|
|
450
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
451
451
|
binding_values: dict[tuple[int, str], ast.expr],
|
|
452
452
|
*,
|
|
453
453
|
node_index: NodeIndex | None = None,
|
|
@@ -461,7 +461,7 @@ def _query_binding_values(
|
|
|
461
461
|
if not isinstance(target, ast.Name) or assignment.value is None:
|
|
462
462
|
continue
|
|
463
463
|
owner = _scope(assignment, parents)
|
|
464
|
-
if parents.get(
|
|
464
|
+
if parents.get(assignment) is owner:
|
|
465
465
|
binding_values[id(owner), target.id] = assignment.value
|
|
466
466
|
|
|
467
467
|
|
|
@@ -591,7 +591,7 @@ def _maximum_query_joins(statement: exp.Query) -> int:
|
|
|
591
591
|
|
|
592
592
|
def _count_import_bindings(
|
|
593
593
|
tree: ast.Module,
|
|
594
|
-
parents:
|
|
594
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
595
595
|
count: Callable[[ast.AST, str], None],
|
|
596
596
|
wildcard_scopes: set[int],
|
|
597
597
|
*,
|
|
@@ -20,6 +20,7 @@ from sarj_python_lint.rules._paths import is_test_path
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Mapping
|
|
23
24
|
from pathlib import Path
|
|
24
25
|
|
|
25
26
|
from sarj_python_lint._file_context import PythonFileContext
|
|
@@ -92,7 +93,7 @@ class FastapiClassRouterContract(Rule):
|
|
|
92
93
|
if tree is None:
|
|
93
94
|
return []
|
|
94
95
|
index = context.fastapi
|
|
95
|
-
parents =
|
|
96
|
+
parents = context.parents
|
|
96
97
|
findings = _router_construction_findings(path, tree, index, parents, node_index=context.node_index)
|
|
97
98
|
for function in (
|
|
98
99
|
node for node in context.nodes(ast.AST) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
@@ -109,7 +110,7 @@ def _router_construction_findings(
|
|
|
109
110
|
path: Path,
|
|
110
111
|
tree: ast.Module,
|
|
111
112
|
index: FastapiIndex,
|
|
112
|
-
parents:
|
|
113
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
113
114
|
*,
|
|
114
115
|
node_index: NodeIndex | None = None,
|
|
115
116
|
) -> list[Diagnostic]:
|
|
@@ -187,16 +188,12 @@ def _is_collection_or_scalar(annotation: ast.expr) -> bool:
|
|
|
187
188
|
return flat_name(target) in _TOP_LEVEL_COLLECTIONS | _SCALARS
|
|
188
189
|
|
|
189
190
|
|
|
190
|
-
def _parents(tree: ast.Module, *, node_index: NodeIndex | None = None) -> dict[int, ast.AST]:
|
|
191
|
-
return {id(child): parent for parent in walk_ast(tree, index=node_index) for child in ast.iter_child_nodes(parent)}
|
|
192
|
-
|
|
193
|
-
|
|
194
191
|
def _ancestor(
|
|
195
|
-
node: ast.AST, parents:
|
|
192
|
+
node: ast.AST, parents: Mapping[ast.AST, ast.AST], kind: type[ast.AST] | tuple[type[ast.AST], ...]
|
|
196
193
|
) -> ast.AST | None:
|
|
197
|
-
current = parents.get(
|
|
194
|
+
current = parents.get(node)
|
|
198
195
|
while current is not None:
|
|
199
196
|
if isinstance(current, kind):
|
|
200
197
|
return current
|
|
201
|
-
current = parents.get(
|
|
198
|
+
current = parents.get(current)
|
|
202
199
|
return None
|
|
@@ -14,14 +14,14 @@ from sarj_python_lint.rule_base import (
|
|
|
14
14
|
RuleDocumentation,
|
|
15
15
|
RuleExample,
|
|
16
16
|
)
|
|
17
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
18
17
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
19
18
|
from sarj_python_lint.rules._paths import is_test_path
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Mapping
|
|
23
|
+
|
|
23
24
|
from sarj_python_lint._file_context import PythonFileContext
|
|
24
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
_PYDANTIC_BASE_MODEL_SOURCES = frozenset({"pydantic", "pydantic.main", "pydantic.v1", "pydantic.v1.main"})
|
|
@@ -108,7 +108,7 @@ class NoNestedPydanticFieldValidator(Rule):
|
|
|
108
108
|
if tree is None:
|
|
109
109
|
return []
|
|
110
110
|
imports = _module_scope_imports(tree)
|
|
111
|
-
parents =
|
|
111
|
+
parents = context.parents
|
|
112
112
|
diagnostics: list[Diagnostic] = []
|
|
113
113
|
|
|
114
114
|
def collect_nested_validators(outer: ast.ClassDef, nested: ast.ClassDef, outer_fields: frozenset[str]) -> None:
|
|
@@ -148,7 +148,7 @@ class NoNestedPydanticFieldValidator(Rule):
|
|
|
148
148
|
return diagnostics
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
def _is_direct_model(node: ast.ClassDef, imports: ImportIndex, parents:
|
|
151
|
+
def _is_direct_model(node: ast.ClassDef, imports: ImportIndex, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
|
152
152
|
return any(
|
|
153
153
|
(
|
|
154
154
|
imports.resolves(base, sources=_PYDANTIC_BASE_MODEL_SOURCES, symbol="BaseModel")
|
|
@@ -174,7 +174,7 @@ def _direct_fields(node: ast.ClassDef, imports: ImportIndex) -> frozenset[str]:
|
|
|
174
174
|
def _field_validators(
|
|
175
175
|
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
176
176
|
imports: ImportIndex,
|
|
177
|
-
parents:
|
|
177
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
178
178
|
) -> tuple[tuple[ast.Call, frozenset[str]], ...]:
|
|
179
179
|
validators: list[tuple[ast.Call, frozenset[str]]] = []
|
|
180
180
|
for decorator in node.decorator_list:
|
|
@@ -221,14 +221,10 @@ def _module_scope_imports(tree: ast.Module) -> ImportIndex:
|
|
|
221
221
|
return ImportIndex.from_tree(ast.Module(body=body, type_ignores=[]))
|
|
222
222
|
|
|
223
223
|
|
|
224
|
-
def _parent_index(tree: ast.Module, *, node_index: NodeIndex | None = None) -> dict[ast.AST, ast.AST]:
|
|
225
|
-
return {child: owner for owner in walk_ast(tree, index=node_index) for child in ast.iter_child_nodes(owner)}
|
|
226
|
-
|
|
227
|
-
|
|
228
224
|
def _shadowed_in_enclosing_function(
|
|
229
225
|
reference: ast.expr,
|
|
230
226
|
owner: ast.AST,
|
|
231
|
-
parents:
|
|
227
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
232
228
|
) -> bool:
|
|
233
229
|
root = _root_name(reference)
|
|
234
230
|
if root is None:
|
{sarj_python_lint-1.1.0 → sarj_python_lint-1.1.1}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
@@ -21,6 +21,8 @@ from sarj_python_lint.rules._sql import sql_string_value, strip_sql_noise
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
|
|
24
26
|
from sarj_python_lint._file_context import PythonFileContext
|
|
25
27
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
26
28
|
|
|
@@ -176,7 +178,7 @@ class NoSelectStar(Rule):
|
|
|
176
178
|
return []
|
|
177
179
|
|
|
178
180
|
diags: list[Diagnostic] = []
|
|
179
|
-
parents =
|
|
181
|
+
parents = context.parents
|
|
180
182
|
docstrings = _docstring_value_ids(tree, node_index=context.node_index)
|
|
181
183
|
consumed: set[int] = set()
|
|
182
184
|
for node in context.nodes(ast.Constant, ast.BinOp, ast.JoinedStr):
|
|
@@ -221,9 +223,9 @@ def _docstring_value_ids(tree: ast.AST, *, node_index: NodeIndex | None = None)
|
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
|
|
224
|
-
def _is_query_context(node: ast.expr, parents:
|
|
226
|
+
def _is_query_context(node: ast.expr, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
|
225
227
|
current: ast.AST = node
|
|
226
|
-
while (parent := parents.get(
|
|
228
|
+
while (parent := parents.get(current)) is not None:
|
|
227
229
|
match parent:
|
|
228
230
|
case ast.Assign() | ast.AnnAssign() | ast.NamedExpr():
|
|
229
231
|
return any(_is_query_binding(target) for target in _assignment_targets(parent))
|
|
@@ -14,14 +14,12 @@ from sarj_python_lint.rule_base import (
|
|
|
14
14
|
RuleDocumentation,
|
|
15
15
|
RuleExample,
|
|
16
16
|
Severity,
|
|
17
|
-
parse_or_none,
|
|
18
17
|
)
|
|
19
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
20
18
|
from sarj_python_lint.rules._prose_budget import groups
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
if TYPE_CHECKING:
|
|
24
|
-
from
|
|
22
|
+
from collections.abc import Mapping
|
|
25
23
|
|
|
26
24
|
from sarj_python_lint._file_context import PythonFileContext
|
|
27
25
|
|
|
@@ -87,7 +85,7 @@ class NoTypedDocSections(Rule):
|
|
|
87
85
|
def check_context(self, context: PythonFileContext) -> list[Diagnostic]:
|
|
88
86
|
path = context.path
|
|
89
87
|
source = context.source
|
|
90
|
-
excluded_lines = _public_contract_docstring_lines(
|
|
88
|
+
excluded_lines = _public_contract_docstring_lines(context)
|
|
91
89
|
return [
|
|
92
90
|
Diagnostic(
|
|
93
91
|
path,
|
|
@@ -110,14 +108,14 @@ _PUBLIC_CONTRACT_DECORATORS = frozenset({"abstractmethod", "cached_property", "o
|
|
|
110
108
|
_PUBLIC_CONTRACT_BASES = frozenset({"Protocol"})
|
|
111
109
|
|
|
112
110
|
|
|
113
|
-
def _public_contract_docstring_lines(
|
|
114
|
-
tree =
|
|
111
|
+
def _public_contract_docstring_lines(context: PythonFileContext) -> frozenset[int]:
|
|
112
|
+
tree = context.tree
|
|
115
113
|
if tree is None:
|
|
116
114
|
return frozenset()
|
|
117
|
-
parents =
|
|
115
|
+
parents = context.parents
|
|
118
116
|
excluded: set[int] = set()
|
|
119
|
-
for node in
|
|
120
|
-
if not
|
|
117
|
+
for node in context.nodes(ast.FunctionDef, ast.AsyncFunctionDef):
|
|
118
|
+
if not node.body:
|
|
121
119
|
continue
|
|
122
120
|
first = node.body[0]
|
|
123
121
|
if not (isinstance(first, ast.Expr) and isinstance(first.value, ast.Constant)):
|
|
@@ -127,9 +125,9 @@ def _public_contract_docstring_lines(path: Path, source: str) -> frozenset[int]:
|
|
|
127
125
|
return frozenset(excluded)
|
|
128
126
|
|
|
129
127
|
|
|
130
|
-
def _is_public_contract(node: ast.FunctionDef | ast.AsyncFunctionDef, parents:
|
|
128
|
+
def _is_public_contract(node: ast.FunctionDef | ast.AsyncFunctionDef, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
|
131
129
|
decorators = {_terminal_name(item.func if isinstance(item, ast.Call) else item) for item in node.decorator_list}
|
|
132
|
-
owner = parents.get(
|
|
130
|
+
owner = parents.get(node)
|
|
133
131
|
protocol_method = isinstance(owner, ast.ClassDef) and any(
|
|
134
132
|
_terminal_name(base) in _PUBLIC_CONTRACT_BASES for base in owner.bases
|
|
135
133
|
)
|
|
@@ -22,7 +22,7 @@ from sarj_python_lint.rules._paths import is_test_path
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from collections.abc import Iterator
|
|
25
|
+
from collections.abc import Iterator, Mapping
|
|
26
26
|
|
|
27
27
|
from sarj_python_lint._file_context import PythonFileContext
|
|
28
28
|
|
|
@@ -108,7 +108,7 @@ class NoUniqueViolationMessageMatch(Rule):
|
|
|
108
108
|
if tree is None:
|
|
109
109
|
return []
|
|
110
110
|
imports = _module_import_index(tree)
|
|
111
|
-
parents =
|
|
111
|
+
parents = context.parents
|
|
112
112
|
findings: list[tuple[ast.expr, _HandlerContext]] = []
|
|
113
113
|
for handler in (node for node in context.nodes(ast.AST) if isinstance(node, ast.ExceptHandler)):
|
|
114
114
|
driver = _caught_driver(handler.type, imports)
|
|
@@ -194,11 +194,11 @@ def _statement_bound_names(statement: ast.stmt) -> frozenset[str]:
|
|
|
194
194
|
def _locally_shadows_exception(
|
|
195
195
|
caught: ast.expr | None,
|
|
196
196
|
handler: ast.ExceptHandler,
|
|
197
|
-
parents:
|
|
197
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
198
198
|
) -> bool:
|
|
199
199
|
roots = _exception_root_names(caught)
|
|
200
200
|
owner: ast.AST = handler
|
|
201
|
-
while (parent := parents.get(
|
|
201
|
+
while (parent := parents.get(owner)) is not None:
|
|
202
202
|
owner = parent
|
|
203
203
|
if isinstance(owner, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
204
204
|
return any(
|
|
@@ -244,10 +244,10 @@ def _walk_same_scope(node: ast.AST) -> Iterator[ast.AST]:
|
|
|
244
244
|
def _builtins_are_available(
|
|
245
245
|
tree: ast.Module,
|
|
246
246
|
handler: ast.ExceptHandler,
|
|
247
|
-
parents:
|
|
247
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
248
248
|
) -> bool:
|
|
249
249
|
owner: ast.AST = handler
|
|
250
|
-
while (parent := parents.get(
|
|
250
|
+
while (parent := parents.get(owner)) is not None:
|
|
251
251
|
owner = parent
|
|
252
252
|
if isinstance(owner, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
253
253
|
return not any(
|
|
@@ -16,7 +16,7 @@ from sarj_python_lint.rule_base import (
|
|
|
16
16
|
RuleExample,
|
|
17
17
|
Severity,
|
|
18
18
|
)
|
|
19
|
-
from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
19
|
+
from sarj_python_lint.rules._ast_index import parent_map, walk as walk_ast
|
|
20
20
|
from sarj_python_lint.rules._docstrings import docstring_expression
|
|
21
21
|
|
|
22
22
|
|
|
@@ -365,15 +365,13 @@ def _owner_index(owners: frozenset[str]) -> dict[str, tuple[str, ...]]:
|
|
|
365
365
|
|
|
366
366
|
|
|
367
367
|
def _bare_module_docstring_consumed(tree: ast.Module, *, node_index: NodeIndex | None = None) -> bool:
|
|
368
|
-
parents =
|
|
369
|
-
id(child): parent for parent in walk_ast(tree, index=node_index) for child in ast.iter_child_nodes(parent)
|
|
370
|
-
}
|
|
368
|
+
parents = parent_map(tree, index=node_index)
|
|
371
369
|
for node in walk_ast(tree, index=node_index):
|
|
372
370
|
if not (isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load) and node.id == "__doc__"):
|
|
373
371
|
continue
|
|
374
372
|
current: ast.AST = node
|
|
375
373
|
inside_function = False
|
|
376
|
-
while (parent := parents.get(
|
|
374
|
+
while (parent := parents.get(current)) is not None:
|
|
377
375
|
if isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
|
|
378
376
|
inside_function = True
|
|
379
377
|
if _function_binds_docstring_name(parent):
|
|
@@ -20,6 +20,8 @@ from sarj_python_lint.rules._ast_index import walk as walk_ast
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Mapping
|
|
24
|
+
|
|
23
25
|
from sarj_python_lint._file_context import PythonFileContext
|
|
24
26
|
|
|
25
27
|
|
|
@@ -136,8 +138,8 @@ class NoUnusedValueMarker(Rule):
|
|
|
136
138
|
]
|
|
137
139
|
if not markers:
|
|
138
140
|
return []
|
|
139
|
-
parents = (
|
|
140
|
-
|
|
141
|
+
parents: Mapping[ast.AST, ast.AST] = (
|
|
142
|
+
context.parents
|
|
141
143
|
if any(isinstance(node, ast.Assign) and isinstance(node.value, (ast.Name, ast.Tuple)) for node in markers)
|
|
142
144
|
else {}
|
|
143
145
|
)
|
|
@@ -166,7 +168,7 @@ def _is_unused_value_marker(node: ast.Assign | ast.AnnAssign) -> bool:
|
|
|
166
168
|
|
|
167
169
|
def _is_pure_parameter_marker(
|
|
168
170
|
node: ast.Assign | ast.AnnAssign,
|
|
169
|
-
parents:
|
|
171
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
170
172
|
) -> bool:
|
|
171
173
|
if not isinstance(node, ast.Assign) or len(node.targets) != 1:
|
|
172
174
|
return False
|
|
@@ -193,7 +195,7 @@ def _marker_reference_names(value: ast.expr) -> set[str] | None:
|
|
|
193
195
|
|
|
194
196
|
|
|
195
197
|
def _enclosing_function(
|
|
196
|
-
node: ast.AST, parents:
|
|
198
|
+
node: ast.AST, parents: Mapping[ast.AST, ast.AST]
|
|
197
199
|
) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
|
|
198
200
|
current = node
|
|
199
201
|
while current in parents:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
from collections import Counter
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from pathlib import PurePosixPath
|
|
7
8
|
from typing import TYPE_CHECKING, final, override
|
|
@@ -26,7 +27,6 @@ if TYPE_CHECKING:
|
|
|
26
27
|
from pathlib import Path
|
|
27
28
|
|
|
28
29
|
from sarj_python_lint._file_context import PythonFileContext
|
|
29
|
-
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
_PAYLOAD_TERMINALS = frozenset({"body", "bodies", "content", "data", "json", "payload", "payloads", "text"})
|
|
@@ -60,7 +60,7 @@ class _SuiteFacts:
|
|
|
60
60
|
class _AnalysisFacts:
|
|
61
61
|
suites: dict[int, _SuiteFacts]
|
|
62
62
|
statement_suites: dict[int, int]
|
|
63
|
-
parents:
|
|
63
|
+
parents: Mapping[ast.AST, ast.AST]
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
def _reference_tokens(value: ast.expr) -> tuple[str, ...]:
|
|
@@ -171,7 +171,7 @@ class NoWholeRequestResponsePayloadInLog(Rule):
|
|
|
171
171
|
tree = context.tree
|
|
172
172
|
if tree is None:
|
|
173
173
|
return []
|
|
174
|
-
facts = _analysis_facts(
|
|
174
|
+
facts = _analysis_facts(context)
|
|
175
175
|
diagnostics: list[Diagnostic] = []
|
|
176
176
|
for node in context.nodes(ast.Call):
|
|
177
177
|
if not _is_logging_call(node):
|
|
@@ -295,13 +295,11 @@ def _is_payload_reference(value: ast.expr, *, label: str | None = None) -> bool:
|
|
|
295
295
|
return bool(_REQUEST_RESPONSE.intersection(tokens) and _PAYLOAD_TERMINALS.intersection(tokens))
|
|
296
296
|
|
|
297
297
|
|
|
298
|
-
def _analysis_facts(
|
|
298
|
+
def _analysis_facts(context: PythonFileContext) -> _AnalysisFacts:
|
|
299
299
|
suites: dict[int, _SuiteFacts] = {}
|
|
300
300
|
statement_suites: dict[int, int] = {}
|
|
301
|
-
parents =
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
for owner in walk_ast(tree, index=node_index):
|
|
301
|
+
parents = context.parents
|
|
302
|
+
for owner in context.nodes(ast.AST):
|
|
305
303
|
for field_name in owner._fields:
|
|
306
304
|
statements = _statement_list(getattr(owner, field_name, None))
|
|
307
305
|
if statements is None:
|
|
@@ -387,10 +385,10 @@ def _root_name(node: ast.expr) -> str | None:
|
|
|
387
385
|
return node.id if isinstance(node, ast.Name) else None
|
|
388
386
|
|
|
389
387
|
|
|
390
|
-
def _containing_statement(node: ast.AST, parents:
|
|
388
|
+
def _containing_statement(node: ast.AST, parents: Mapping[ast.AST, ast.AST]) -> ast.stmt | None:
|
|
391
389
|
current: ast.AST | None = node
|
|
392
390
|
while current is not None and not isinstance(current, ast.stmt):
|
|
393
|
-
current = parents.get(
|
|
391
|
+
current = parents.get(current)
|
|
394
392
|
return current
|
|
395
393
|
|
|
396
394
|
|
|
@@ -22,7 +22,7 @@ from sarj_python_lint.rules._paths import is_test_path, is_test_support_path
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from collections.abc import Iterable, Iterator
|
|
25
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
26
26
|
|
|
27
27
|
from sarj_python_lint._file_context import PythonFileContext
|
|
28
28
|
from sarj_python_lint.rules._ast_index import NodeIndex
|
|
@@ -156,7 +156,7 @@ class PreferConstantTimeSecretCompare(Rule):
|
|
|
156
156
|
tree = context.tree
|
|
157
157
|
if tree is None:
|
|
158
158
|
return []
|
|
159
|
-
parents =
|
|
159
|
+
parents = context.parents
|
|
160
160
|
role_cache: dict[int, _Roles] = {}
|
|
161
161
|
environment_names = _environment_names(tree)
|
|
162
162
|
dunder_compares = _equality_dunder_compares(tree, source, node_index=context.node_index)
|
|
@@ -427,13 +427,13 @@ def _bound_names(scope: ast.FunctionDef | ast.AsyncFunctionDef) -> frozenset[str
|
|
|
427
427
|
|
|
428
428
|
def _dominating_walrus_roles(
|
|
429
429
|
comparison: ast.Compare,
|
|
430
|
-
parents:
|
|
430
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
431
431
|
roles: _Roles,
|
|
432
432
|
position: tuple[int, int],
|
|
433
433
|
) -> dict[str, _Role]:
|
|
434
434
|
result: dict[str, _Role] = {}
|
|
435
435
|
current: ast.AST = comparison
|
|
436
|
-
while (parent := parents.get(
|
|
436
|
+
while (parent := parents.get(current)) is not None:
|
|
437
437
|
if isinstance(parent, ast.If) and current in parent.body:
|
|
438
438
|
for named in nodes(parent.test, ast.NamedExpr):
|
|
439
439
|
result[named.target.id] = _operand_role(named.value, roles, position, result)
|
|
@@ -459,11 +459,11 @@ def _terminal_name(node: ast.expr) -> str | None:
|
|
|
459
459
|
|
|
460
460
|
def _enclosing_scope(
|
|
461
461
|
node: ast.AST,
|
|
462
|
-
parents:
|
|
462
|
+
parents: Mapping[ast.AST, ast.AST],
|
|
463
463
|
module: ast.Module,
|
|
464
464
|
) -> ast.Module | ast.FunctionDef | ast.AsyncFunctionDef:
|
|
465
465
|
current: ast.AST = node
|
|
466
|
-
while (parent := parents.get(
|
|
466
|
+
while (parent := parents.get(current)) is not None:
|
|
467
467
|
if isinstance(parent, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
468
468
|
return parent
|
|
469
469
|
current = parent
|