sarj-python-lint 0.80.3__tar.gz → 0.80.5__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.80.3 → sarj_python_lint-0.80.5}/PKG-INFO +2 -2
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/pyproject.toml +3 -3
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_ratchet_cli.py +7 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_registry.py +4 -0
- sarj_python_lint-0.80.5/src/sarj_python_lint/rules/excessive_commentary.py +218 -0
- sarj_python_lint-0.80.5/src/sarj_python_lint/rules/typed_error_reasons.py +228 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/.gitignore +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/LICENSE +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/README.md +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_one_for_required_row.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/preserve_declared_nominal_id.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/preserve_enum_types.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/require_validated_row_factory.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/source_coupled_test.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/sql_requires_injected_pool_owner.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
- {sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.80.
|
|
3
|
+
Version: 0.80.5
|
|
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/
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
18
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
19
|
Requires-Python: >=3.14
|
|
20
|
-
Requires-Dist: ruff>=0.16.
|
|
20
|
+
Requires-Dist: ruff>=0.16.5
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
<!-- Generated by `code-standards maintain docs sync`; do not edit. -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sarj-python-lint"
|
|
3
|
-
version = "0.80.
|
|
3
|
+
version = "0.80.5"
|
|
4
4
|
description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "sarj-ai" }]
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Topic :: Software Development :: Quality Assurance",
|
|
17
17
|
]
|
|
18
|
-
dependencies = ["ruff>=0.16.
|
|
18
|
+
dependencies = ["ruff>=0.16.5"]
|
|
19
19
|
|
|
20
20
|
[project.scripts]
|
|
21
21
|
sarj-python-lint = "sarj_python_lint.__main__:main"
|
|
@@ -31,7 +31,7 @@ Issues = "https://github.com/sarj-ai/code-standards/issues"
|
|
|
31
31
|
[dependency-groups]
|
|
32
32
|
dev = [
|
|
33
33
|
"pytest>=9.1.1",
|
|
34
|
-
"ruff>=0.16.
|
|
34
|
+
"ruff>=0.16.5",
|
|
35
35
|
"basedpyright>=1.39.10",
|
|
36
36
|
]
|
|
37
37
|
|
|
@@ -155,6 +155,13 @@ def _ruff_selector_aliases() -> dict[str, str]:
|
|
|
155
155
|
raise TypeError(msg)
|
|
156
156
|
code = raw_item.get("code")
|
|
157
157
|
name = raw_item.get("name")
|
|
158
|
+
if code is None and isinstance(name, str):
|
|
159
|
+
# Ruff accepts the name as the stable selector until it allocates
|
|
160
|
+
# a code, so preserve that suppression identity rather than
|
|
161
|
+
# rejecting a valid Ruff directive.
|
|
162
|
+
canonical = name.lower()
|
|
163
|
+
aliases[canonical] = canonical
|
|
164
|
+
continue
|
|
158
165
|
if not isinstance(code, str) or not isinstance(name, str):
|
|
159
166
|
msg = "Ruff rule catalog entry lacks a string code or name"
|
|
160
167
|
raise TypeError(msg)
|
|
@@ -10,6 +10,7 @@ from sarj_python_lint.rules.docstring_args_restate_signature import (
|
|
|
10
10
|
from sarj_python_lint.rules.docstring_returns_restate_signature import (
|
|
11
11
|
DocstringReturnsRestateSignature,
|
|
12
12
|
)
|
|
13
|
+
from sarj_python_lint.rules.excessive_commentary import ExcessiveCommentary
|
|
13
14
|
from sarj_python_lint.rules.fakes_in_shared_location import FakesInSharedLocation
|
|
14
15
|
from sarj_python_lint.rules.fastapi_explicit_openapi_contract import FastapiExplicitOpenapiContract
|
|
15
16
|
from sarj_python_lint.rules.iac_source_coupled_test import IacSourceCoupledTest
|
|
@@ -152,6 +153,7 @@ from sarj_python_lint.rules.store_insert_requires_on_conflict import (
|
|
|
152
153
|
from sarj_python_lint.rules.timestamp_order_requires_tiebreaker import TimestampOrderRequiresTiebreaker
|
|
153
154
|
from sarj_python_lint.rules.trailing_value_narration import TrailingValueNarration
|
|
154
155
|
from sarj_python_lint.rules.trivially_true_assertion import TriviallyTrueAssertion
|
|
156
|
+
from sarj_python_lint.rules.typed_error_reasons import TypedErrorReasons
|
|
155
157
|
from sarj_python_lint.rules.uncontrolled_randomness_in_test import UncontrolledRandomnessInTest
|
|
156
158
|
from sarj_python_lint.rules.unused_mock_setup import UnusedMockSetup
|
|
157
159
|
|
|
@@ -163,6 +165,7 @@ if TYPE_CHECKING:
|
|
|
163
165
|
|
|
164
166
|
REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
165
167
|
{
|
|
168
|
+
ExcessiveCommentary.id: ExcessiveCommentary,
|
|
166
169
|
PreferWalrusAwaitedNoneGuard.id: PreferWalrusAwaitedNoneGuard,
|
|
167
170
|
TimestampOrderRequiresTiebreaker.id: TimestampOrderRequiresTiebreaker,
|
|
168
171
|
NoStringConcatInLoop.id: NoStringConcatInLoop,
|
|
@@ -260,6 +263,7 @@ REGISTRY: Mapping[str, type[Rule]] = MappingProxyType(
|
|
|
260
263
|
RequirePydanticOrdinalLowerBound.id: RequirePydanticOrdinalLowerBound,
|
|
261
264
|
RequireNoDecodeForSplittingSettingsField.id: RequireNoDecodeForSplittingSettingsField,
|
|
262
265
|
NoVagueSuppressionDescription.id: NoVagueSuppressionDescription,
|
|
266
|
+
TypedErrorReasons.id: TypedErrorReasons,
|
|
263
267
|
}
|
|
264
268
|
)
|
|
265
269
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING, final, override
|
|
7
|
+
|
|
8
|
+
from sarj_python_lint.rule_base import (
|
|
9
|
+
AutofixPolicy,
|
|
10
|
+
Diagnostic,
|
|
11
|
+
ExampleFile,
|
|
12
|
+
ExampleOutcome,
|
|
13
|
+
Rule,
|
|
14
|
+
RuleCategory,
|
|
15
|
+
RuleDocumentation,
|
|
16
|
+
RuleExample,
|
|
17
|
+
parse_or_none,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._prose_budget import (
|
|
20
|
+
groups,
|
|
21
|
+
has_technical_anchor,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_MIN_LINES = 4
|
|
30
|
+
_MIN_WORDS = 28
|
|
31
|
+
_RATIONALE_RE = re.compile(
|
|
32
|
+
r"\b(?:because|otherwise|therefore|must|never|cannot|can't|required?|invariant|"
|
|
33
|
+
r"compatibility|security|race|atomic|deadlock|rollback|lock|data loss)\b",
|
|
34
|
+
re.IGNORECASE,
|
|
35
|
+
)
|
|
36
|
+
_RATIONALE_SO_RE = re.compile(r"\bso\s+(?:that|a|an|the|this|it|we|they)\b", re.IGNORECASE)
|
|
37
|
+
_BULLET_RE = re.compile(r"^\s*(?:[-*+] |\d+[.)] )", re.MULTILINE)
|
|
38
|
+
_ACCUMULATOR_METHODS = frozenset({"add", "append", "extend", "update"})
|
|
39
|
+
_MIN_ACCUMULATOR_CALLS = 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@final
|
|
43
|
+
class ExcessiveCommentary(Rule):
|
|
44
|
+
id = "excessive-commentary"
|
|
45
|
+
code = "SARJ434"
|
|
46
|
+
documentation = RuleDocumentation(
|
|
47
|
+
summary="Long standalone implementation commentary — make the code self-documenting and retain only durable constraints.",
|
|
48
|
+
rationale=(
|
|
49
|
+
"A paragraph that narrates nearby implementation behavior competes with the code and can drift independently "
|
|
50
|
+
"from it."
|
|
51
|
+
),
|
|
52
|
+
remediation=(
|
|
53
|
+
"Delete the narration and clarify names, types, or structure. Keep concise comments that record a durable "
|
|
54
|
+
"constraint or externally owned contract."
|
|
55
|
+
),
|
|
56
|
+
category=RuleCategory.MAINTAINABILITY,
|
|
57
|
+
autofix=AutofixPolicy.NONE,
|
|
58
|
+
limitations=(
|
|
59
|
+
"Only contiguous standalone line-comment blocks with at least four non-empty lines and 28 words are inspected.",
|
|
60
|
+
"The paragraph must immediately precede an empty local collection that is then populated by at least two consecutive accumulator calls.",
|
|
61
|
+
"Generated files, directives, licenses, structured lists, docstrings, inline comments, rationale markers, and concrete technical anchors are excluded.",
|
|
62
|
+
),
|
|
63
|
+
examples=(
|
|
64
|
+
RuleExample(
|
|
65
|
+
example_id="activation-narration",
|
|
66
|
+
title="Implementation paragraph narrates a validation helper",
|
|
67
|
+
outcome=ExampleOutcome.MATCH,
|
|
68
|
+
files=(
|
|
69
|
+
ExampleFile.python(
|
|
70
|
+
"app.py",
|
|
71
|
+
"def activation_reasons():\n"
|
|
72
|
+
" # Everything standing between this integration and being usable.\n"
|
|
73
|
+
" # Returns all the reasons rather than the first failure.\n"
|
|
74
|
+
" # Someone activating a half-built integration wants the complete list.\n"
|
|
75
|
+
" # That avoids discovering one problem per round trip.\n"
|
|
76
|
+
" reasons = []\n"
|
|
77
|
+
" reasons.extend(integration_reasons())\n"
|
|
78
|
+
" reasons.extend(endpoint_reasons())\n"
|
|
79
|
+
" return reasons\n",
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
focus_path=PurePosixPath("app.py"),
|
|
83
|
+
expected_count=1,
|
|
84
|
+
public=True,
|
|
85
|
+
),
|
|
86
|
+
RuleExample(
|
|
87
|
+
example_id="durable-constraint",
|
|
88
|
+
title="A concrete compatibility constraint remains local",
|
|
89
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
90
|
+
files=(
|
|
91
|
+
ExampleFile.python(
|
|
92
|
+
"app.py",
|
|
93
|
+
"# Legacy clients send `execution_phase` until API-812 is retired.\n"
|
|
94
|
+
"# Keep the adapter at this boundary so internal models stay camelCase.\n"
|
|
95
|
+
'phase = payload["execution_phase"]\n',
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
focus_path=PurePosixPath("app.py"),
|
|
99
|
+
expected_count=0,
|
|
100
|
+
public=True,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
description = documentation.summary
|
|
105
|
+
|
|
106
|
+
@override
|
|
107
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
108
|
+
tree = parse_or_none(path, source)
|
|
109
|
+
if tree is None:
|
|
110
|
+
return []
|
|
111
|
+
accumulator_lines = _narrated_accumulator_lines(tree)
|
|
112
|
+
findings: list[Diagnostic] = []
|
|
113
|
+
for group in groups(path, source):
|
|
114
|
+
lines = tuple(stripped for line in group.text.splitlines() if (stripped := line.strip()))
|
|
115
|
+
if group.kind != "comment" or len(lines) < _MIN_LINES or len(group.text.split()) < _MIN_WORDS:
|
|
116
|
+
continue
|
|
117
|
+
comment_end_line = group.line + len(group.text.splitlines()) - 1
|
|
118
|
+
if comment_end_line + 1 not in accumulator_lines:
|
|
119
|
+
continue
|
|
120
|
+
if any(
|
|
121
|
+
(
|
|
122
|
+
_BULLET_RE.search(group.text),
|
|
123
|
+
has_technical_anchor(group.text),
|
|
124
|
+
_RATIONALE_RE.search(group.text),
|
|
125
|
+
_RATIONALE_SO_RE.search(group.text),
|
|
126
|
+
)
|
|
127
|
+
):
|
|
128
|
+
continue
|
|
129
|
+
findings.append(
|
|
130
|
+
Diagnostic(
|
|
131
|
+
path,
|
|
132
|
+
group.line,
|
|
133
|
+
group.col,
|
|
134
|
+
self.code,
|
|
135
|
+
self.description,
|
|
136
|
+
column_encoding=group.column_encoding,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
return findings
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _narrated_accumulator_lines(tree: ast.Module) -> set[int]:
|
|
143
|
+
lines: set[int] = set()
|
|
144
|
+
for function in ast.walk(tree):
|
|
145
|
+
if not isinstance(function, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
146
|
+
continue
|
|
147
|
+
_collect_accumulators(function.body, lines)
|
|
148
|
+
return lines
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _collect_accumulators(statements: list[ast.stmt], lines: set[int]) -> None:
|
|
152
|
+
for index, statement in enumerate(statements):
|
|
153
|
+
name = _empty_collection_name(statement)
|
|
154
|
+
if name is not None:
|
|
155
|
+
mutations = 0
|
|
156
|
+
for following in statements[index + 1 :]:
|
|
157
|
+
if not _is_accumulator_call(following, name):
|
|
158
|
+
break
|
|
159
|
+
mutations += 1
|
|
160
|
+
if mutations >= _MIN_ACCUMULATOR_CALLS:
|
|
161
|
+
lines.add(statement.lineno)
|
|
162
|
+
for child_statements in _nested_statement_lists(statement):
|
|
163
|
+
_collect_accumulators(child_statements, lines)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _empty_collection_name(statement: ast.stmt) -> str | None:
|
|
167
|
+
target: ast.expr | None = None
|
|
168
|
+
value: ast.expr | None = None
|
|
169
|
+
if isinstance(statement, ast.AnnAssign):
|
|
170
|
+
target, value = statement.target, statement.value
|
|
171
|
+
elif isinstance(statement, ast.Assign) and len(statement.targets) == 1:
|
|
172
|
+
target, value = statement.targets[0], statement.value
|
|
173
|
+
if not isinstance(target, ast.Name) or value is None:
|
|
174
|
+
return None
|
|
175
|
+
if isinstance(value, (ast.List, ast.Set)) and not value.elts:
|
|
176
|
+
return target.id
|
|
177
|
+
if isinstance(value, ast.Dict) and not value.keys:
|
|
178
|
+
return target.id
|
|
179
|
+
if (
|
|
180
|
+
isinstance(value, ast.Call)
|
|
181
|
+
and isinstance(value.func, ast.Name)
|
|
182
|
+
and value.func.id in {"dict", "list", "set"}
|
|
183
|
+
and not value.args
|
|
184
|
+
and not value.keywords
|
|
185
|
+
):
|
|
186
|
+
return target.id
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _is_accumulator_call(statement: ast.stmt, name: str) -> bool:
|
|
191
|
+
if not isinstance(statement, ast.Expr) or not isinstance(statement.value, ast.Call):
|
|
192
|
+
return False
|
|
193
|
+
function = statement.value.func
|
|
194
|
+
return (
|
|
195
|
+
isinstance(function, ast.Attribute)
|
|
196
|
+
and function.attr in _ACCUMULATOR_METHODS
|
|
197
|
+
and isinstance(function.value, ast.Name)
|
|
198
|
+
and function.value.id == name
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _nested_statement_lists(statement: ast.stmt) -> list[list[ast.stmt]]:
|
|
203
|
+
match statement:
|
|
204
|
+
case ast.For() | ast.AsyncFor() | ast.While() | ast.If():
|
|
205
|
+
return [statement.body, statement.orelse]
|
|
206
|
+
case ast.With() | ast.AsyncWith():
|
|
207
|
+
return [statement.body]
|
|
208
|
+
case ast.Try() | ast.TryStar():
|
|
209
|
+
return [
|
|
210
|
+
statement.body,
|
|
211
|
+
statement.orelse,
|
|
212
|
+
statement.finalbody,
|
|
213
|
+
*(handler.body for handler in statement.handlers),
|
|
214
|
+
]
|
|
215
|
+
case ast.Match():
|
|
216
|
+
return [case.body for case in statement.cases]
|
|
217
|
+
case _:
|
|
218
|
+
return []
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING, ClassVar, TypeGuard, final, override
|
|
6
|
+
|
|
7
|
+
from sarj_python_lint.rule_base import (
|
|
8
|
+
AutofixPolicy,
|
|
9
|
+
Diagnostic,
|
|
10
|
+
ExampleFile,
|
|
11
|
+
ExampleOutcome,
|
|
12
|
+
Rule,
|
|
13
|
+
RuleCategory,
|
|
14
|
+
RuleDocumentation,
|
|
15
|
+
RuleExample,
|
|
16
|
+
Severity,
|
|
17
|
+
parse_or_none,
|
|
18
|
+
)
|
|
19
|
+
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_ERROR_SUFFIXES = ("Error", "Exception")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@final
|
|
31
|
+
class TypedErrorReasons(Rule):
|
|
32
|
+
id = "typed-error-reasons"
|
|
33
|
+
code = "SARJ435"
|
|
34
|
+
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
35
|
+
summary="Exception aggregates presentation strings instead of typed error reasons.",
|
|
36
|
+
rationale=(
|
|
37
|
+
"A collection of rendered sentences has no stable identity for API clients, UI formatting, telemetry, "
|
|
38
|
+
"or exhaustive handling; consumers must display or parse text that should be presentation-only."
|
|
39
|
+
),
|
|
40
|
+
remediation=(
|
|
41
|
+
"Replace `list[str]` with a nominal reason type: usually a record containing a `StrEnum` code and typed "
|
|
42
|
+
"context. Format that structure only at the presentation boundary."
|
|
43
|
+
),
|
|
44
|
+
category=RuleCategory.ARCHITECTURE,
|
|
45
|
+
autofix=AutofixPolicy.NONE,
|
|
46
|
+
limitations=(
|
|
47
|
+
"Only an Error/Exception subclass whose direct Error/Exception base is visible in the class declaration is checked.",
|
|
48
|
+
"The constructor must have exactly one non-self parameter, annotated exactly `list[str]`, and join that same parameter inside `super().__init__(...)`.",
|
|
49
|
+
"Formatting delegated to another function, legacy `typing.List`, mixed constructor context, tests, and generated files are intentionally not inferred.",
|
|
50
|
+
),
|
|
51
|
+
examples=(
|
|
52
|
+
RuleExample(
|
|
53
|
+
example_id="rendered-reason-list",
|
|
54
|
+
title="An exception renders raw reason strings",
|
|
55
|
+
outcome=ExampleOutcome.MATCH,
|
|
56
|
+
files=(
|
|
57
|
+
ExampleFile.python(
|
|
58
|
+
"app/errors.py",
|
|
59
|
+
"class DomainError(Exception): ...\n\n"
|
|
60
|
+
"class IncompleteError(DomainError):\n"
|
|
61
|
+
" def __init__(self, reasons: list[str]) -> None:\n"
|
|
62
|
+
" self.reasons = reasons\n"
|
|
63
|
+
" super().__init__(f\"Incomplete: {'; '.join(reasons)}\")\n",
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
focus_path=PurePosixPath("app/errors.py"),
|
|
67
|
+
expected_count=1,
|
|
68
|
+
public=True,
|
|
69
|
+
),
|
|
70
|
+
RuleExample(
|
|
71
|
+
example_id="typed-reason-list",
|
|
72
|
+
title="An exception carries nominal reason records",
|
|
73
|
+
outcome=ExampleOutcome.NO_MATCH,
|
|
74
|
+
files=(
|
|
75
|
+
ExampleFile.python(
|
|
76
|
+
"app/errors.py",
|
|
77
|
+
"from dataclasses import dataclass\n"
|
|
78
|
+
"from enum import StrEnum\n\n"
|
|
79
|
+
"class ReasonCode(StrEnum):\n"
|
|
80
|
+
" MISSING_URL = 'missing_url'\n\n"
|
|
81
|
+
"@dataclass(frozen=True)\n"
|
|
82
|
+
"class Reason:\n"
|
|
83
|
+
" code: ReasonCode\n"
|
|
84
|
+
" field: str | None = None\n\n"
|
|
85
|
+
"class DomainError(Exception): ...\n\n"
|
|
86
|
+
"class IncompleteError(DomainError):\n"
|
|
87
|
+
" def __init__(self, reasons: list[Reason]) -> None:\n"
|
|
88
|
+
" self.reasons = reasons\n"
|
|
89
|
+
" super().__init__('Integration is incomplete')\n",
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
focus_path=PurePosixPath("app/errors.py"),
|
|
93
|
+
expected_count=0,
|
|
94
|
+
public=True,
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
description = documentation.summary
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
102
|
+
if path.suffix != ".py" or is_test_path(path) or is_generated(path, source):
|
|
103
|
+
return []
|
|
104
|
+
if "list[str]" not in source or ".join(" not in source or "super(" not in source:
|
|
105
|
+
return []
|
|
106
|
+
tree = parse_or_none(path, source)
|
|
107
|
+
if tree is None:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
diagnostics: list[Diagnostic] = []
|
|
111
|
+
for error_class in (node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)):
|
|
112
|
+
if not _is_conventional_exception(error_class):
|
|
113
|
+
continue
|
|
114
|
+
constructor = next(
|
|
115
|
+
(
|
|
116
|
+
member
|
|
117
|
+
for member in error_class.body
|
|
118
|
+
if isinstance(member, ast.FunctionDef) and member.name == "__init__"
|
|
119
|
+
),
|
|
120
|
+
None,
|
|
121
|
+
)
|
|
122
|
+
if constructor is None:
|
|
123
|
+
continue
|
|
124
|
+
parameter = _sole_string_list_parameter(constructor)
|
|
125
|
+
if parameter is None:
|
|
126
|
+
continue
|
|
127
|
+
joined = _joined_in_super_message(constructor, parameter.arg)
|
|
128
|
+
if joined is None:
|
|
129
|
+
continue
|
|
130
|
+
diagnostics.append(
|
|
131
|
+
Diagnostic(
|
|
132
|
+
path=path,
|
|
133
|
+
line=joined.lineno,
|
|
134
|
+
col=joined.col_offset + 1,
|
|
135
|
+
code=self.code,
|
|
136
|
+
severity=Severity.WARNING,
|
|
137
|
+
message=(
|
|
138
|
+
f"`{error_class.name}` joins `{parameter.arg}: list[str]` into its message; carry nominal "
|
|
139
|
+
"reason codes with typed context and format them at the presentation boundary"
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _is_conventional_exception(node: ast.ClassDef) -> bool:
|
|
147
|
+
return node.name.endswith(_ERROR_SUFFIXES) and any(_tail(base).endswith(_ERROR_SUFFIXES) for base in node.bases)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _tail(node: ast.expr) -> str:
|
|
151
|
+
match node:
|
|
152
|
+
case ast.Name(id=name) | ast.Attribute(attr=name):
|
|
153
|
+
return name
|
|
154
|
+
case _:
|
|
155
|
+
return ""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _sole_string_list_parameter(function: ast.FunctionDef) -> ast.arg | None:
|
|
159
|
+
parameters = [
|
|
160
|
+
argument
|
|
161
|
+
for argument in (
|
|
162
|
+
*function.args.posonlyargs,
|
|
163
|
+
*function.args.args,
|
|
164
|
+
*function.args.kwonlyargs,
|
|
165
|
+
)
|
|
166
|
+
if argument.arg not in {"self", "cls"}
|
|
167
|
+
]
|
|
168
|
+
if len(parameters) != 1:
|
|
169
|
+
return None
|
|
170
|
+
parameter = parameters[0]
|
|
171
|
+
return parameter if _is_string_list(parameter.annotation) else None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _is_string_list(annotation: ast.expr | None) -> bool:
|
|
175
|
+
return (
|
|
176
|
+
isinstance(annotation, ast.Subscript)
|
|
177
|
+
and isinstance(annotation.value, ast.Name)
|
|
178
|
+
and annotation.value.id == "list"
|
|
179
|
+
and isinstance(annotation.slice, ast.Name)
|
|
180
|
+
and annotation.slice.id == "str"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _joined_in_super_message(function: ast.FunctionDef, parameter: str) -> ast.Call | None:
|
|
185
|
+
for node in _own_scope_nodes(function):
|
|
186
|
+
if not _is_super_init_call(node):
|
|
187
|
+
continue
|
|
188
|
+
for argument in node.args:
|
|
189
|
+
joined = next(
|
|
190
|
+
(candidate for candidate in ast.walk(argument) if _joins_parameter(candidate, parameter)),
|
|
191
|
+
None,
|
|
192
|
+
)
|
|
193
|
+
if joined is not None:
|
|
194
|
+
return joined
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _own_scope_nodes(function: ast.FunctionDef) -> Iterator[ast.AST]:
|
|
199
|
+
stack: list[ast.AST] = list(function.body)
|
|
200
|
+
while stack:
|
|
201
|
+
node = stack.pop()
|
|
202
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)):
|
|
203
|
+
continue
|
|
204
|
+
yield node
|
|
205
|
+
stack.extend(ast.iter_child_nodes(node))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _is_super_init_call(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
209
|
+
return (
|
|
210
|
+
isinstance(node, ast.Call)
|
|
211
|
+
and isinstance(node.func, ast.Attribute)
|
|
212
|
+
and node.func.attr == "__init__"
|
|
213
|
+
and isinstance(node.func.value, ast.Call)
|
|
214
|
+
and isinstance(node.func.value.func, ast.Name)
|
|
215
|
+
and node.func.value.func.id == "super"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _joins_parameter(node: ast.AST, parameter: str) -> TypeGuard[ast.Call]:
|
|
220
|
+
return (
|
|
221
|
+
isinstance(node, ast.Call)
|
|
222
|
+
and isinstance(node.func, ast.Attribute)
|
|
223
|
+
and node.func.attr == "join"
|
|
224
|
+
and len(node.args) == 1
|
|
225
|
+
and not node.keywords
|
|
226
|
+
and isinstance(node.args[0], ast.Name)
|
|
227
|
+
and node.args[0].id == parameter
|
|
228
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/_analysis_session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/_test_assertions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_comment_cruft.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
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_long_comment.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
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_class_row.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
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/prefer_str_enum.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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.3 → sarj_python_lint-0.80.5}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|