sarj-python-lint 0.82.0__tar.gz → 0.82.2__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-0.82.0 → sarj_python_lint-0.82.2}/PKG-INFO +1 -1
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/pyproject.toml +1 -1
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/__main__.py +53 -33
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_python_target.py +3 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_ratchet_cli.py +4 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/ratchet.py +33 -23
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rule_base.py +3 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_comments.py +37 -26
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_fastapi.py +230 -170
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_first_party.py +11 -5
- sarj_python_lint-0.82.2/src/sarj_python_lint/rules/_fixed_record.py +170 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_imports.py +19 -15
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_paths.py +11 -6
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_project_index.py +172 -141
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_prose_budget.py +145 -133
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_registry.py +2 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_sql.py +38 -58
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +231 -177
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +12 -6
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +216 -140
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +394 -349
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/mock_without_spec.py +48 -51
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +17 -8
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_comment_cruft.py +66 -54
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_deleted_only_override_parameter.py +10 -6
- sarj_python_lint-0.82.2/src/sarj_python_lint/rules/no_excessive_cognitive_complexity.py +262 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_first_party_private_import.py +11 -5
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +20 -14
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_generic_single_export_module.py +9 -5
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +144 -100
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +25 -23
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +235 -171
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +118 -70
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +11 -6
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +9 -5
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +54 -40
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_redundant_literal_description.py +38 -30
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_redundant_module_alias_exports.py +98 -15
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_repeated_string_literal.py +43 -24
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_repeated_test_body.py +65 -44
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_restated_comment.py +46 -32
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_secret_in_log.py +23 -17
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_service_behavior_in_settings.py +18 -12
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +37 -21
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_typed_doc_sections.py +10 -6
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +25 -15
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +167 -144
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +9 -5
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +25 -17
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/over_mocked_test.py +25 -15
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_class_row.py +26 -17
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +18 -13
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +119 -79
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +38 -28
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_library_fake.py +42 -30
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_match_assert_never.py +33 -28
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +36 -32
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_match_value_dispatch.py +46 -31
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_module_level_constant.py +14 -10
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +101 -71
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +49 -45
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +33 -17
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +8 -4
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_str_enum.py +434 -313
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +47 -26
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +5 -1
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +4 -1
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +11 -7
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +7 -5
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/production_derived_test_cases.py +40 -26
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +24 -16
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +54 -34
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/redundant_class_docstring.py +40 -31
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/redundant_docstring.py +40 -31
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/repeated_static_call_cases.py +27 -17
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +54 -41
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +175 -163
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/require_port_for_service.py +190 -131
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +204 -150
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +272 -206
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/restated_test_docstring.py +70 -48
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/stepdown.py +145 -116
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +24 -14
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +72 -50
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +63 -54
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/typed_error_reasons.py +8 -8
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +121 -90
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/unused_mock_setup.py +121 -101
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/unused_test_factory_option.py +95 -69
- sarj_python_lint-0.82.0/src/sarj_python_lint/rules/_fixed_record.py +0 -146
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/.gitignore +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/LICENSE +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/README.md +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_dunder_all.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-0.82.0 → sarj_python_lint-0.82.2}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.82.
|
|
3
|
+
Version: 0.82.2
|
|
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/
|
|
@@ -14,7 +14,7 @@ import typer
|
|
|
14
14
|
from sarj_python_lint import __version__
|
|
15
15
|
from sarj_python_lint._analysis_session import AnalysisSession
|
|
16
16
|
from sarj_python_lint._filesystem import atomic_write_text
|
|
17
|
-
from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Severity, is_suppressed
|
|
17
|
+
from sarj_python_lint.rule_base import Diagnostic, ProjectRule, Rule, Severity, is_suppressed
|
|
18
18
|
from sarj_python_lint.rules import REGISTRY
|
|
19
19
|
from sarj_python_lint.rules._paths import clear_path_caches
|
|
20
20
|
from sarj_python_lint.rules._project_index import ProjectIndexSet
|
|
@@ -59,17 +59,23 @@ def _expand_paths(paths: list[Path]) -> list[Path]:
|
|
|
59
59
|
except OSError:
|
|
60
60
|
pass
|
|
61
61
|
continue
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
out.extend(_python_files(p))
|
|
63
|
+
return out
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _python_files(p: Path) -> list[Path]:
|
|
67
|
+
out: list[Path] = []
|
|
68
|
+
for child in p.rglob("*.py"):
|
|
69
|
+
if not child.is_file():
|
|
70
|
+
continue
|
|
71
|
+
if any(part in SKIP_DIR_NAMES for part in child.parts):
|
|
72
|
+
continue
|
|
73
|
+
try:
|
|
74
|
+
if child.stat().st_size > _MAX_FILE_BYTES:
|
|
71
75
|
continue
|
|
72
|
-
|
|
76
|
+
except OSError:
|
|
77
|
+
continue
|
|
78
|
+
out.append(child)
|
|
73
79
|
return out
|
|
74
80
|
|
|
75
81
|
|
|
@@ -98,22 +104,23 @@ def _check(rule_ids: list[str], paths: list[Path]) -> list[Diagnostic]:
|
|
|
98
104
|
rule.prepare(indexes)
|
|
99
105
|
diags: list[Diagnostic] = []
|
|
100
106
|
for p, source in loaded.items():
|
|
101
|
-
|
|
102
|
-
raw = [diagnostic for rule in rules for diagnostic in rule.check(p, source)]
|
|
103
|
-
diags.extend(
|
|
104
|
-
diagnostic
|
|
105
|
-
for diagnostic in deduplicate_diagnostics(
|
|
106
|
-
[
|
|
107
|
-
diagnostic
|
|
108
|
-
for diagnostic in raw
|
|
109
|
-
if diagnostic.code == "SARJ419" or not is_suppressed(source_lines, diagnostic.line, diagnostic.code)
|
|
110
|
-
],
|
|
111
|
-
source=source,
|
|
112
|
-
)
|
|
113
|
-
)
|
|
107
|
+
diags.extend(_check_source(rules, p, source))
|
|
114
108
|
return diags
|
|
115
109
|
|
|
116
110
|
|
|
111
|
+
def _check_source(rules: list[Rule], p: Path, source: str) -> list[Diagnostic]:
|
|
112
|
+
source_lines = source.splitlines()
|
|
113
|
+
raw = [diagnostic for rule in rules for diagnostic in rule.check(p, source)]
|
|
114
|
+
return deduplicate_diagnostics(
|
|
115
|
+
[
|
|
116
|
+
diagnostic
|
|
117
|
+
for diagnostic in raw
|
|
118
|
+
if diagnostic.code == "SARJ419" or not is_suppressed(source_lines, diagnostic.line, diagnostic.code)
|
|
119
|
+
],
|
|
120
|
+
source=source,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
117
124
|
def analyze(
|
|
118
125
|
rule_ids: list[str],
|
|
119
126
|
paths: list[Path],
|
|
@@ -172,15 +179,7 @@ def deduplicate_diagnostics(diags: list[Diagnostic], *, source: str | None = Non
|
|
|
172
179
|
location = owner_location(diagnostic)
|
|
173
180
|
by_code = present.setdefault((diagnostic.path, location.line, location.column), {})
|
|
174
181
|
by_code.setdefault(diagnostic.code, set()).add(diagnostic.severity)
|
|
175
|
-
suppressed =
|
|
176
|
-
(location, generic, generic_severity)
|
|
177
|
-
for location, codes in present.items()
|
|
178
|
-
for specific, generics in _DIAGNOSTIC_PRECEDENCE.items()
|
|
179
|
-
if specific in codes
|
|
180
|
-
for generic in generics
|
|
181
|
-
for generic_severity in codes.get(generic, set())
|
|
182
|
-
if generic_severity is Severity.WARNING or Severity.ERROR in codes[specific]
|
|
183
|
-
}
|
|
182
|
+
suppressed = _suppressed_diagnostics(present)
|
|
184
183
|
return [
|
|
185
184
|
diagnostic
|
|
186
185
|
for diagnostic in diags
|
|
@@ -199,6 +198,27 @@ def deduplicate_diagnostics(diags: list[Diagnostic], *, source: str | None = Non
|
|
|
199
198
|
]
|
|
200
199
|
|
|
201
200
|
|
|
201
|
+
def _suppressed_diagnostics(
|
|
202
|
+
present: dict[tuple[Path, int, int], dict[str, set[Severity]]],
|
|
203
|
+
) -> set[tuple[tuple[Path, int, int], str, Severity]]:
|
|
204
|
+
return {
|
|
205
|
+
(location, generic, severity)
|
|
206
|
+
for location, codes in present.items()
|
|
207
|
+
for generic, severity in _suppressed_codes(codes)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _suppressed_codes(codes: dict[str, set[Severity]]) -> set[tuple[str, Severity]]:
|
|
212
|
+
return {
|
|
213
|
+
(generic, generic_severity)
|
|
214
|
+
for specific, generics in _DIAGNOSTIC_PRECEDENCE.items()
|
|
215
|
+
if specific in codes
|
|
216
|
+
for generic in generics
|
|
217
|
+
for generic_severity in codes.get(generic, set())
|
|
218
|
+
if generic_severity is Severity.WARNING or Severity.ERROR in codes[specific]
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
202
222
|
def _function_signature_owner_locations(source: str) -> dict[int, _OwnerLocation]:
|
|
203
223
|
try:
|
|
204
224
|
tree = ast.parse(source)
|
|
@@ -53,6 +53,9 @@ class PythonTargetFacts:
|
|
|
53
53
|
if root not in self._installed:
|
|
54
54
|
self._installed[root] = _installed_declarations(root)
|
|
55
55
|
return self._installed[root].get(resolved)
|
|
56
|
+
return self._project_target(resolved)
|
|
57
|
+
|
|
58
|
+
def _project_target(self, resolved: Path) -> str | None:
|
|
56
59
|
for ancestor in list(resolved.parents)[:_MAX_ANCESTORS]:
|
|
57
60
|
try:
|
|
58
61
|
if any((ancestor / marker).exists() for marker in _PROJECT_BOUNDARIES):
|
|
@@ -111,6 +111,10 @@ def _run(args: _Options) -> int:
|
|
|
111
111
|
allow_increase=args.allow_increase or not baseline_path.exists(),
|
|
112
112
|
)
|
|
113
113
|
|
|
114
|
+
return _report_gate(measurement, baseline, packages)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _report_gate(measurement: Measurement, baseline: Baseline, packages: list[str]) -> int:
|
|
114
118
|
sys.stdout.write(
|
|
115
119
|
f"{measurement.total} suppressions across {len(measurement.codes)} codes "
|
|
116
120
|
f"in {len(packages)} package(s) (baseline total {sum(baseline.codes.values())})\n"
|
|
@@ -323,29 +323,7 @@ def _count_comment(
|
|
|
323
323
|
standalone: bool,
|
|
324
324
|
ruff_aliases: Mapping[str, str] | None,
|
|
325
325
|
) -> None:
|
|
326
|
-
if standalone and (
|
|
327
|
-
counts.update(
|
|
328
|
-
f"file-noqa:{_normalized_ruff_selector(code, ruff_aliases)}" for code in file_ignore.group(1).split(",")
|
|
329
|
-
)
|
|
330
|
-
return
|
|
331
|
-
if standalone and (file_noqa := _FILE_NOQA_RE.match(comment)):
|
|
332
|
-
listed = file_noqa.group(1)
|
|
333
|
-
if listed:
|
|
334
|
-
counts.update(f"file-noqa:{_normalized_ruff_selector(code, ruff_aliases)}" for code in listed.split(","))
|
|
335
|
-
else:
|
|
336
|
-
counts[_BLANKET_KEY] += 1
|
|
337
|
-
return
|
|
338
|
-
if standalone and _FLAKE8_FILE_NOQA_RE.match(comment):
|
|
339
|
-
counts[_FLAKE8_BLANKET_KEY] += 1
|
|
340
|
-
return
|
|
341
|
-
if standalone and (disabled := _RUFF_DISABLE_RE.match(comment)):
|
|
342
|
-
counts.update(
|
|
343
|
-
f"ruff-range:{_normalized_ruff_selector(code, ruff_aliases)}" for code in disabled.group(1).split(",")
|
|
344
|
-
)
|
|
345
|
-
return
|
|
346
|
-
if standalone and _FILE_PYRIGHT_RE.match(comment):
|
|
347
|
-
rules: list[str] = _FILE_PYRIGHT_RULE_RE.findall(comment)
|
|
348
|
-
counts.update(f"file-pyright:{rule}" for rule in rules)
|
|
326
|
+
if standalone and _count_file_comment(comment, counts, ruff_aliases=ruff_aliases):
|
|
349
327
|
return
|
|
350
328
|
for match in _NOQA_RE.finditer(comment):
|
|
351
329
|
listed = match.group(1)
|
|
@@ -358,6 +336,10 @@ def _count_comment(
|
|
|
358
336
|
counts.update(f"{prefix}{_normalized_ruff_selector(code, ruff_aliases)}" for code in match.group(1).split(","))
|
|
359
337
|
for match in _SARJ_NOQA_RE.finditer(comment):
|
|
360
338
|
counts.update(f"sarj-noqa:{code.strip().upper()}" for code in match.group(1).split(","))
|
|
339
|
+
_count_type_checker_comments(comment, counts)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _count_type_checker_comments(comment: str, counts: Counter[str]) -> None:
|
|
361
343
|
for match in _PYRIGHT_IGNORE_RE.finditer(comment):
|
|
362
344
|
counts.update(f"pyright:{code.strip()}" for code in match.group(1).split(","))
|
|
363
345
|
if re.search(r"#\s*pyright:\s*ignore\b(?!\s*\[)", comment):
|
|
@@ -370,6 +352,34 @@ def _count_comment(
|
|
|
370
352
|
counts[_BARE_TYPE_IGNORE_KEY] += 1
|
|
371
353
|
|
|
372
354
|
|
|
355
|
+
def _count_file_comment(comment: str, counts: Counter[str], *, ruff_aliases: Mapping[str, str] | None) -> bool:
|
|
356
|
+
if file_ignore := _RUFF_FILE_IGNORE_RE.match(comment):
|
|
357
|
+
counts.update(
|
|
358
|
+
f"file-noqa:{_normalized_ruff_selector(code, ruff_aliases)}" for code in file_ignore.group(1).split(",")
|
|
359
|
+
)
|
|
360
|
+
return True
|
|
361
|
+
if file_noqa := _FILE_NOQA_RE.match(comment):
|
|
362
|
+
listed = file_noqa.group(1)
|
|
363
|
+
if listed:
|
|
364
|
+
counts.update(f"file-noqa:{_normalized_ruff_selector(code, ruff_aliases)}" for code in listed.split(","))
|
|
365
|
+
else:
|
|
366
|
+
counts[_BLANKET_KEY] += 1
|
|
367
|
+
return True
|
|
368
|
+
if _FLAKE8_FILE_NOQA_RE.match(comment):
|
|
369
|
+
counts[_FLAKE8_BLANKET_KEY] += 1
|
|
370
|
+
return True
|
|
371
|
+
if disabled := _RUFF_DISABLE_RE.match(comment):
|
|
372
|
+
counts.update(
|
|
373
|
+
f"ruff-range:{_normalized_ruff_selector(code, ruff_aliases)}" for code in disabled.group(1).split(",")
|
|
374
|
+
)
|
|
375
|
+
return True
|
|
376
|
+
if _FILE_PYRIGHT_RE.match(comment):
|
|
377
|
+
rules: list[str] = _FILE_PYRIGHT_RULE_RE.findall(comment)
|
|
378
|
+
counts.update(f"file-pyright:{rule}" for rule in rules)
|
|
379
|
+
return True
|
|
380
|
+
return False
|
|
381
|
+
|
|
382
|
+
|
|
373
383
|
def _normalized_ruff_selector(selector: str, aliases: Mapping[str, str] | None) -> str:
|
|
374
384
|
stripped = selector.strip()
|
|
375
385
|
normalized = stripped.upper() if stripped.isupper() else stripped.lower()
|
|
@@ -184,6 +184,9 @@ class RuleDocumentation:
|
|
|
184
184
|
if len(example_ids) != len(set(example_ids)):
|
|
185
185
|
msg = "rule example IDs must be unique"
|
|
186
186
|
raise ValueError(msg)
|
|
187
|
+
self._validate_public_scenarios()
|
|
188
|
+
|
|
189
|
+
def _validate_public_scenarios(self) -> None:
|
|
187
190
|
public_scenarios = {example.scenario for example in self.examples if example.public}
|
|
188
191
|
for scenario in public_scenarios:
|
|
189
192
|
pair = tuple(example for example in self.examples if example.public and example.scenario == scenario)
|
|
@@ -457,32 +457,7 @@ def statement_comment_walls(
|
|
|
457
457
|
statements = [item for item in value if _is_wall_statement(item)]
|
|
458
458
|
if len(statements) < _WALL_MIN_STATEMENTS:
|
|
459
459
|
continue
|
|
460
|
-
|
|
461
|
-
for index, statement in enumerate(statements):
|
|
462
|
-
entry = comments.get(statement.lineno - 1)
|
|
463
|
-
if entry is None or entry[0] != statement.col_offset:
|
|
464
|
-
continue
|
|
465
|
-
line = statement.lineno - 1
|
|
466
|
-
segment = ast.get_source_segment(source, statement) or ""
|
|
467
|
-
attached.append((index, line, _weak_walkthrough_comment(entry[1], segment)))
|
|
468
|
-
clusters: list[list[tuple[int, int, bool]]] = []
|
|
469
|
-
for item in attached:
|
|
470
|
-
if clusters and item[0] <= clusters[-1][-1][0] + 2:
|
|
471
|
-
clusters[-1].append(item)
|
|
472
|
-
else:
|
|
473
|
-
clusters.append([item])
|
|
474
|
-
for cluster in clusters:
|
|
475
|
-
span = cluster[-1][0] - cluster[0][0] + 1
|
|
476
|
-
weak = [line for _index, line, is_weak in cluster if is_weak]
|
|
477
|
-
if (
|
|
478
|
-
span < _WALL_MIN_STATEMENTS
|
|
479
|
-
or len(weak) < _WALL_MIN_COMMENTS
|
|
480
|
-
or len(cluster) / span < _WALL_MIN_COMMENTED_RATIO
|
|
481
|
-
or len(weak) / len(cluster) < _WALL_MIN_WEAK_RATIO
|
|
482
|
-
):
|
|
483
|
-
continue
|
|
484
|
-
leader = min(weak)
|
|
485
|
-
walls[leader] = frozenset(weak)
|
|
460
|
+
_collect_statement_walls(statements, comments, source, walls)
|
|
486
461
|
return walls
|
|
487
462
|
|
|
488
463
|
|
|
@@ -513,6 +488,32 @@ def _is_wall_statement(node: ast.stmt) -> bool:
|
|
|
513
488
|
)
|
|
514
489
|
|
|
515
490
|
|
|
491
|
+
def _collect_statement_walls(
|
|
492
|
+
statements: list[ast.stmt], comments: dict[int, tuple[int, str]], source: str, walls: dict[int, frozenset[int]]
|
|
493
|
+
) -> None:
|
|
494
|
+
attached: list[tuple[int, int, bool]] = []
|
|
495
|
+
for index, statement in enumerate(statements):
|
|
496
|
+
entry = comments.get(statement.lineno - 1)
|
|
497
|
+
if entry is None or entry[0] != statement.col_offset:
|
|
498
|
+
continue
|
|
499
|
+
line = statement.lineno - 1
|
|
500
|
+
segment = ast.get_source_segment(source, statement) or ""
|
|
501
|
+
attached.append((index, line, _weak_walkthrough_comment(entry[1], segment)))
|
|
502
|
+
clusters = _comment_clusters(attached)
|
|
503
|
+
for cluster in clusters:
|
|
504
|
+
span = cluster[-1][0] - cluster[0][0] + 1
|
|
505
|
+
weak = [line for _index, line, is_weak in cluster if is_weak]
|
|
506
|
+
if (
|
|
507
|
+
span < _WALL_MIN_STATEMENTS
|
|
508
|
+
or len(weak) < _WALL_MIN_COMMENTS
|
|
509
|
+
or len(cluster) / span < _WALL_MIN_COMMENTED_RATIO
|
|
510
|
+
or len(weak) / len(cluster) < _WALL_MIN_WEAK_RATIO
|
|
511
|
+
):
|
|
512
|
+
continue
|
|
513
|
+
leader = min(weak)
|
|
514
|
+
walls[leader] = frozenset(weak)
|
|
515
|
+
|
|
516
|
+
|
|
516
517
|
def _weak_walkthrough_comment(body: str, statement: str) -> bool:
|
|
517
518
|
if (
|
|
518
519
|
not body
|
|
@@ -533,3 +534,13 @@ def _weak_walkthrough_comment(body: str, statement: str) -> bool:
|
|
|
533
534
|
matched = sum(1 for word in described if restates([word], known))
|
|
534
535
|
novel = len(described) - matched
|
|
535
536
|
return matched / len(described) >= _WALL_MIN_MATCHED_RATIO and novel <= _WALL_MAX_NOVEL_WORDS
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _comment_clusters(attached: list[tuple[int, int, bool]]) -> list[list[tuple[int, int, bool]]]:
|
|
540
|
+
clusters: list[list[tuple[int, int, bool]]] = []
|
|
541
|
+
for item in attached:
|
|
542
|
+
if clusters and item[0] <= clusters[-1][-1][0] + 2:
|
|
543
|
+
clusters[-1].append(item)
|
|
544
|
+
else:
|
|
545
|
+
clusters.append([item])
|
|
546
|
+
return clusters
|