sarj-python-lint 0.80.13__tar.gz → 0.80.14__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.13 → sarj_python_lint-0.80.14}/PKG-INFO +1 -1
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/pyproject.toml +1 -1
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/unused_mock_setup.py +202 -58
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/.gitignore +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/LICENSE +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/README.md +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/_analysis_session.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/_filesystem.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_fastapi.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_fixed_record.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_imports.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_project_index.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_registry.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/complex_postgres_query_requires_architecture_review.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/defect_xfail_requires_explicit_strict.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/excessive_commentary.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/fakes_in_shared_location.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/fastapi_explicit_openapi_contract.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_analytical_aggregation_in_postgres_store.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_copied_inherited_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_file_level_escape_hatch_suppression.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_positional_psycopg_row_escape.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_psycopg_execution_outside_injected_owner.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_raw_connection_in_tests.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_raw_source_text_test_oracle.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_repeated_test_body.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_vague_suppression_description.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_whole_request_response_payload_in_log.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_collection_comprehension.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_set_isdisjoint.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_walrus_awaited_none_guard.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/production_derived_test_cases.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/pytest_fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/repeated_kwarg_heavy_call_in_test.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/repeated_static_call_cases.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/store_get_delegates_to_bulk_read.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/timestamp_order_requires_tiebreaker.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/typed_error_reasons.py +0 -0
- {sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.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.14
|
|
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/
|
|
@@ -13,11 +13,12 @@ from sarj_python_lint.rule_base import (
|
|
|
13
13
|
RuleCategory,
|
|
14
14
|
RuleDocumentation,
|
|
15
15
|
RuleExample,
|
|
16
|
+
Severity,
|
|
16
17
|
parse_or_none,
|
|
17
18
|
)
|
|
18
19
|
from sarj_python_lint.rules._ast_index import children, nodes, walk
|
|
19
20
|
from sarj_python_lint.rules._imports import ImportIndex
|
|
20
|
-
from sarj_python_lint.rules._paths import is_test_path
|
|
21
|
+
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
@@ -72,18 +73,6 @@ _FUNC_NODES = (ast.FunctionDef, ast.AsyncFunctionDef)
|
|
|
72
73
|
# itself processed as a function.
|
|
73
74
|
_NESTED_SCOPES = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
|
|
74
75
|
|
|
75
|
-
# Expression nodes that can run arbitrary user code, so a statement containing
|
|
76
|
-
# one may have observed a mock configuration written above it.
|
|
77
|
-
_EFFECTFUL_NODES = (
|
|
78
|
-
ast.Attribute,
|
|
79
|
-
ast.Await,
|
|
80
|
-
ast.Call,
|
|
81
|
-
ast.NamedExpr,
|
|
82
|
-
ast.Subscript,
|
|
83
|
-
ast.Yield,
|
|
84
|
-
ast.YieldFrom,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
76
|
|
|
88
77
|
class _Finding(NamedTuple):
|
|
89
78
|
node: ast.stmt
|
|
@@ -91,17 +80,23 @@ class _Finding(NamedTuple):
|
|
|
91
80
|
|
|
92
81
|
|
|
93
82
|
class UnusedMockSetup(Rule):
|
|
94
|
-
id: str = "
|
|
83
|
+
id: str = "no-provably-dead-mock-configuration"
|
|
95
84
|
code: str = "SARJ067"
|
|
96
85
|
documentation: ClassVar[RuleDocumentation | None] = RuleDocumentation(
|
|
97
|
-
summary="
|
|
98
|
-
rationale=
|
|
99
|
-
|
|
86
|
+
summary="Remove mock behavior configuration proven unable to affect a test.",
|
|
87
|
+
rationale=(
|
|
88
|
+
"A mock behavior value is dead when a same-path write replaces it before observation, or when a "
|
|
89
|
+
"terminal exact-path assertion proves the configured mock was never called."
|
|
90
|
+
),
|
|
91
|
+
remediation=(
|
|
92
|
+
"Delete the earlier overwritten configuration, or place the intended action before reconfiguration; "
|
|
93
|
+
"delete behavior configuration contradicted by a terminal never-called assertion."
|
|
94
|
+
),
|
|
100
95
|
category=RuleCategory.TESTING,
|
|
101
96
|
autofix=AutofixPolicy.NONE,
|
|
102
97
|
limitations=(
|
|
103
|
-
"Only test paths are analyzed.",
|
|
104
|
-
"
|
|
98
|
+
"Only non-generated test paths and mock values proven from unittest.mock or an unshadowed pytest-mock fixture are analyzed.",
|
|
99
|
+
"Only same-block overwrites and exact-path terminal never-called assertions are checked; uncertain evaluation, control flow, reads, and escapes are excluded.",
|
|
105
100
|
),
|
|
106
101
|
examples=(
|
|
107
102
|
RuleExample(
|
|
@@ -111,7 +106,7 @@ class UnusedMockSetup(Rule):
|
|
|
111
106
|
files=(
|
|
112
107
|
ExampleFile.python(
|
|
113
108
|
"tests/test_billing.py",
|
|
114
|
-
"
|
|
109
|
+
"from unittest.mock import Mock\n\ndef test_charge():\n charge = Mock()\n charge.return_value = 1\n charge.return_value = 2\n assert charge() == 2\n",
|
|
115
110
|
),
|
|
116
111
|
),
|
|
117
112
|
focus_path=PurePosixPath("tests/test_billing.py"),
|
|
@@ -125,7 +120,7 @@ class UnusedMockSetup(Rule):
|
|
|
125
120
|
files=(
|
|
126
121
|
ExampleFile.python(
|
|
127
122
|
"tests/test_billing.py",
|
|
128
|
-
"
|
|
123
|
+
"from unittest.mock import Mock\n\ndef test_charge():\n charge = Mock()\n charge.return_value = 1\n assert charge() == 1\n charge.return_value = 2\n assert charge() == 2\n",
|
|
129
124
|
),
|
|
130
125
|
),
|
|
131
126
|
focus_path=PurePosixPath("tests/test_billing.py"),
|
|
@@ -133,12 +128,13 @@ class UnusedMockSetup(Rule):
|
|
|
133
128
|
public=True,
|
|
134
129
|
),
|
|
135
130
|
),
|
|
131
|
+
aliases=("unused-mock-setup",),
|
|
136
132
|
)
|
|
137
133
|
description: str = documentation.summary
|
|
138
134
|
|
|
139
135
|
@override
|
|
140
136
|
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
141
|
-
if not is_test_path(path):
|
|
137
|
+
if not is_test_path(path) or is_generated(path, source):
|
|
142
138
|
return []
|
|
143
139
|
tree = parse_or_none(path, source)
|
|
144
140
|
if tree is None:
|
|
@@ -159,6 +155,7 @@ class UnusedMockSetup(Rule):
|
|
|
159
155
|
line=position[0],
|
|
160
156
|
col=position[1],
|
|
161
157
|
code=self.code,
|
|
158
|
+
severity=Severity.WARNING,
|
|
162
159
|
message=finding.message,
|
|
163
160
|
)
|
|
164
161
|
)
|
|
@@ -167,19 +164,26 @@ class UnusedMockSetup(Rule):
|
|
|
167
164
|
|
|
168
165
|
|
|
169
166
|
def _dead_setups(fn: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> Iterator[_Finding]:
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
origins = _mock_origins(fn, imports)
|
|
168
|
+
if not origins:
|
|
169
|
+
return
|
|
170
|
+
yield from _overwritten_before_use(fn, imports, origins)
|
|
171
|
+
yield from _asserted_never_called(fn, origins)
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def _overwritten_before_use(
|
|
175
|
-
|
|
174
|
+
def _overwritten_before_use(
|
|
175
|
+
fn: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex, origins: frozenset[str]
|
|
176
|
+
) -> Iterator[_Finding]:
|
|
177
|
+
has_mocker_fixture = _has_unshadowed_mocker_fixture(fn)
|
|
176
178
|
for block in _blocks(fn):
|
|
177
179
|
pending: dict[str, ast.stmt] = {}
|
|
178
180
|
for stmt in block:
|
|
179
|
-
target = _config_target(stmt)
|
|
181
|
+
target = _config_target(stmt, origins)
|
|
180
182
|
if target is None:
|
|
181
183
|
initial = _initial_mock_configs(stmt, imports, has_mocker_fixture=has_mocker_fixture)
|
|
182
|
-
if initial:
|
|
184
|
+
if initial and initial[0].partition(".")[0] in origins:
|
|
185
|
+
if _constructor_observes_pending(stmt, pending):
|
|
186
|
+
pending.clear()
|
|
183
187
|
configured_name = initial[0].partition(".")[0]
|
|
184
188
|
pending = {
|
|
185
189
|
target: setup
|
|
@@ -191,6 +195,8 @@ def _overwritten_before_use(fn: ast.FunctionDef | ast.AsyncFunctionDef, imports:
|
|
|
191
195
|
if not _is_inert(stmt):
|
|
192
196
|
pending.clear()
|
|
193
197
|
continue
|
|
198
|
+
if _statement_observes_pending(stmt, pending):
|
|
199
|
+
pending.clear()
|
|
194
200
|
previous = pending.get(target)
|
|
195
201
|
if previous is not None:
|
|
196
202
|
yield _Finding(
|
|
@@ -204,11 +210,57 @@ def _overwritten_before_use(fn: ast.FunctionDef | ast.AsyncFunctionDef, imports:
|
|
|
204
210
|
pending[target] = stmt
|
|
205
211
|
|
|
206
212
|
|
|
213
|
+
def _has_unshadowed_mocker_fixture(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
214
|
+
return _has_parameter(fn, _MOCKER) and not any(
|
|
215
|
+
isinstance(node, ast.Name) and isinstance(node.ctx, (ast.Store, ast.Del)) and node.id == _MOCKER
|
|
216
|
+
for node in _owned_nodes(fn)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
207
220
|
def _has_parameter(fn: ast.FunctionDef | ast.AsyncFunctionDef, name: str) -> bool:
|
|
208
221
|
args = fn.args
|
|
209
222
|
return any(arg.arg == name for arg in (*args.posonlyargs, *args.args, *args.kwonlyargs))
|
|
210
223
|
|
|
211
224
|
|
|
225
|
+
def _owned_nodes(root: ast.AST) -> Iterator[ast.AST]:
|
|
226
|
+
stack = [root]
|
|
227
|
+
while stack:
|
|
228
|
+
node = stack.pop()
|
|
229
|
+
yield node
|
|
230
|
+
if node is not root and isinstance(node, _NESTED_SCOPES):
|
|
231
|
+
continue
|
|
232
|
+
stack.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _mock_origins(fn: ast.FunctionDef | ast.AsyncFunctionDef, imports: ImportIndex) -> frozenset[str]:
|
|
236
|
+
has_mocker_fixture = _has_unshadowed_mocker_fixture(fn)
|
|
237
|
+
binding_counts: dict[str, int] = {}
|
|
238
|
+
for argument in (*fn.args.posonlyargs, *fn.args.args, *fn.args.kwonlyargs):
|
|
239
|
+
binding_counts[argument.arg] = binding_counts.get(argument.arg, 0) + 1
|
|
240
|
+
for node in _owned_nodes(fn):
|
|
241
|
+
if isinstance(node, (ast.Name, ast.Attribute)) and isinstance(node.ctx, (ast.Store, ast.Del)):
|
|
242
|
+
path = _dotted(node)
|
|
243
|
+
if path is not None:
|
|
244
|
+
binding_counts[path] = binding_counts.get(path, 0) + 1
|
|
245
|
+
origins: set[str] = set()
|
|
246
|
+
for stmt in _scope_statements(fn):
|
|
247
|
+
match stmt:
|
|
248
|
+
case ast.Assign(targets=[target], value=ast.Call() as call):
|
|
249
|
+
pass
|
|
250
|
+
case ast.AnnAssign(target=target, value=ast.Call() as call):
|
|
251
|
+
pass
|
|
252
|
+
case _:
|
|
253
|
+
continue
|
|
254
|
+
path = _dotted(target)
|
|
255
|
+
if (
|
|
256
|
+
path is not None
|
|
257
|
+
and binding_counts.get(path) == 1
|
|
258
|
+
and _is_mock_configurator(call.func, imports, has_mocker_fixture=has_mocker_fixture)
|
|
259
|
+
):
|
|
260
|
+
origins.add(path)
|
|
261
|
+
return frozenset(origins)
|
|
262
|
+
|
|
263
|
+
|
|
212
264
|
def _initial_mock_configs(stmt: ast.stmt, imports: ImportIndex, *, has_mocker_fixture: bool) -> tuple[str, ...]:
|
|
213
265
|
match stmt:
|
|
214
266
|
case ast.Assign(targets=[ast.Name(id=name)], value=ast.Call() as call):
|
|
@@ -237,30 +289,66 @@ def _is_mock_configurator(func: ast.expr, imports: ImportIndex, *, has_mocker_fi
|
|
|
237
289
|
return any(imports.resolves(func, sources=_UNITTEST_MOCK, symbol=symbol) for symbol in _MOCK_CONSTRUCTORS)
|
|
238
290
|
|
|
239
291
|
|
|
240
|
-
def _asserted_never_called(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[_Finding]:
|
|
241
|
-
|
|
242
|
-
if not not_called:
|
|
292
|
+
def _asserted_never_called(fn: ast.FunctionDef | ast.AsyncFunctionDef, origins: frozenset[str]) -> Iterator[_Finding]:
|
|
293
|
+
if not fn.name.startswith("test_") or _is_fixture(fn):
|
|
243
294
|
return
|
|
244
295
|
introspected = _introspected_paths(fn)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
296
|
+
loaded_configs = _loaded_config_paths(fn)
|
|
297
|
+
last_effect = _last_effectful_line(fn, origins)
|
|
298
|
+
loop_statements = _loop_statement_ids(fn)
|
|
299
|
+
for block in _blocks(fn):
|
|
300
|
+
not_called = _not_called_assertions(block, origins)
|
|
301
|
+
for stmt in block:
|
|
302
|
+
target = _config_target(stmt, origins)
|
|
303
|
+
if target is None or id(stmt) in loop_statements or target in loaded_configs:
|
|
304
|
+
continue
|
|
305
|
+
configured = target.rsplit(".", 1)[0]
|
|
306
|
+
if any(_touches(configured, other) for other in introspected):
|
|
307
|
+
continue
|
|
308
|
+
for asserted, line in not_called.items():
|
|
309
|
+
if line > stmt.lineno and asserted == configured and last_effect <= line:
|
|
310
|
+
yield _Finding(
|
|
311
|
+
stmt,
|
|
312
|
+
(
|
|
313
|
+
f"`{target}` is configured here, but the test ends by proving `{asserted}` was never "
|
|
314
|
+
"called, so its behavior cannot affect the test. Delete the contradictory configuration"
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
break
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _is_fixture(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
|
|
321
|
+
return any(
|
|
322
|
+
(isinstance(decorator, ast.Name) and decorator.id == "fixture")
|
|
323
|
+
or (isinstance(decorator, ast.Attribute) and decorator.attr == "fixture")
|
|
324
|
+
or (
|
|
325
|
+
isinstance(decorator, ast.Call)
|
|
326
|
+
and isinstance(decorator.func, (ast.Name, ast.Attribute))
|
|
327
|
+
and (decorator.func.id if isinstance(decorator.func, ast.Name) else decorator.func.attr) == "fixture"
|
|
328
|
+
)
|
|
329
|
+
for decorator in fn.decorator_list
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _loop_statement_ids(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> set[int]:
|
|
334
|
+
found: set[int] = set()
|
|
335
|
+
for node in _owned_nodes(fn):
|
|
336
|
+
if not isinstance(node, (ast.For, ast.AsyncFor, ast.While)):
|
|
249
337
|
continue
|
|
250
|
-
|
|
251
|
-
|
|
338
|
+
for child in (*node.body, *node.orelse):
|
|
339
|
+
found.update(id(descendant) for descendant in _owned_nodes(child) if isinstance(descendant, ast.stmt))
|
|
340
|
+
return found
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _loaded_config_paths(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]:
|
|
344
|
+
found: set[str] = set()
|
|
345
|
+
for node in _owned_nodes(fn):
|
|
346
|
+
if not isinstance(node, ast.Attribute) or not isinstance(node.ctx, ast.Load) or node.attr not in _CONFIG_ATTRS:
|
|
252
347
|
continue
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
(
|
|
258
|
-
f"`{target}` is configured here, but the test asserts `{asserted}` was never "
|
|
259
|
-
"called and nothing runs after that assertion — the configured value can never "
|
|
260
|
-
"be observed. Delete the setup, or assert on the call it was written for"
|
|
261
|
-
),
|
|
262
|
-
)
|
|
263
|
-
break
|
|
348
|
+
path = _dotted(node)
|
|
349
|
+
if path is not None:
|
|
350
|
+
found.add(path)
|
|
351
|
+
return found
|
|
264
352
|
|
|
265
353
|
|
|
266
354
|
def _blocks(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[list[ast.stmt]]:
|
|
@@ -296,13 +384,17 @@ def _scope_statements(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[as
|
|
|
296
384
|
yield from block
|
|
297
385
|
|
|
298
386
|
|
|
299
|
-
def _config_target(stmt: ast.stmt) -> str | None:
|
|
387
|
+
def _config_target(stmt: ast.stmt, origins: frozenset[str]) -> str | None:
|
|
300
388
|
if not isinstance(stmt, ast.Assign) or len(stmt.targets) != 1:
|
|
301
389
|
return None
|
|
302
390
|
target = stmt.targets[0]
|
|
303
391
|
if not isinstance(target, ast.Attribute) or target.attr not in _CONFIG_ATTRS:
|
|
304
392
|
return None
|
|
305
|
-
|
|
393
|
+
path = _dotted(target)
|
|
394
|
+
if path is None:
|
|
395
|
+
return None
|
|
396
|
+
configured = path.rsplit(".", 1)[0]
|
|
397
|
+
return path if any(_is_prefix(origin, configured) for origin in origins) else None
|
|
306
398
|
|
|
307
399
|
|
|
308
400
|
def _dotted(node: ast.expr) -> str | None:
|
|
@@ -327,19 +419,66 @@ def _is_inert(stmt: ast.stmt) -> bool:
|
|
|
327
419
|
if not all(isinstance(target, ast.Name) for target in stmt.targets):
|
|
328
420
|
# `obj.attr = 1` can hit a property setter, `d[k] = 1` a `__setitem__`.
|
|
329
421
|
return False
|
|
330
|
-
return
|
|
422
|
+
return _is_safe_value(stmt.value)
|
|
423
|
+
|
|
331
424
|
|
|
425
|
+
def _is_safe_value(node: ast.expr) -> bool:
|
|
426
|
+
match node:
|
|
427
|
+
case ast.Constant() | ast.Name():
|
|
428
|
+
return True
|
|
429
|
+
case ast.List() | ast.Tuple() | ast.Set():
|
|
430
|
+
return all(not isinstance(element, ast.Starred) and _is_safe_value(element) for element in node.elts)
|
|
431
|
+
case ast.Dict():
|
|
432
|
+
return all(key is not None and _is_safe_value(key) for key in node.keys) and all(
|
|
433
|
+
_is_safe_value(value) for value in node.values
|
|
434
|
+
)
|
|
435
|
+
case _:
|
|
436
|
+
return False
|
|
332
437
|
|
|
333
|
-
|
|
438
|
+
|
|
439
|
+
def _statement_observes_pending(stmt: ast.stmt, pending: dict[str, ast.stmt]) -> bool:
|
|
440
|
+
if not pending:
|
|
441
|
+
return False
|
|
442
|
+
value = stmt.value if isinstance(stmt, (ast.Assign, ast.AnnAssign)) else None
|
|
443
|
+
if value is None:
|
|
444
|
+
return False
|
|
445
|
+
for node in walk(value):
|
|
446
|
+
if not isinstance(node, (ast.Name, ast.Attribute)) or not isinstance(node.ctx, ast.Load):
|
|
447
|
+
continue
|
|
448
|
+
path = _dotted(node)
|
|
449
|
+
if path is not None and any(_touches(path, target.rsplit(".", 1)[0]) for target in pending):
|
|
450
|
+
return True
|
|
451
|
+
return not _is_safe_value(value)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _constructor_observes_pending(stmt: ast.stmt, pending: dict[str, ast.stmt]) -> bool:
|
|
455
|
+
value = stmt.value if isinstance(stmt, (ast.Assign, ast.AnnAssign)) else None
|
|
456
|
+
if not isinstance(value, ast.Call):
|
|
457
|
+
return True
|
|
458
|
+
expressions = (*value.args, *(keyword.value for keyword in value.keywords))
|
|
459
|
+
return any(
|
|
460
|
+
not _is_safe_value(expression)
|
|
461
|
+
or any(
|
|
462
|
+
isinstance(node, (ast.Name, ast.Attribute))
|
|
463
|
+
and isinstance(node.ctx, ast.Load)
|
|
464
|
+
and (path := _dotted(node)) is not None
|
|
465
|
+
and any(_touches(path, target.rsplit(".", 1)[0]) for target in pending)
|
|
466
|
+
for node in walk(expression)
|
|
467
|
+
)
|
|
468
|
+
for expression in expressions
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _not_called_assertions(statements: list[ast.stmt], origins: frozenset[str]) -> dict[str, int]:
|
|
334
473
|
found: dict[str, int] = {}
|
|
335
|
-
for stmt in
|
|
474
|
+
for stmt in statements:
|
|
336
475
|
for node in _own_expressions(stmt):
|
|
337
476
|
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
|
338
477
|
continue
|
|
339
478
|
if node.func.attr not in _NOT_CALLED_ASSERTIONS:
|
|
340
479
|
continue
|
|
341
480
|
asserted = _dotted(node.func.value)
|
|
342
|
-
if asserted is not None:
|
|
481
|
+
if asserted is not None and any(_is_prefix(origin, asserted) for origin in origins):
|
|
343
482
|
found[asserted] = max(found.get(asserted, 0), node.lineno)
|
|
344
483
|
return found
|
|
345
484
|
|
|
@@ -368,18 +507,23 @@ def _is_introspection(attr: str) -> bool:
|
|
|
368
507
|
return attr in _INTROSPECTION_ATTRS or attr.startswith(_ASSERT_PREFIX)
|
|
369
508
|
|
|
370
509
|
|
|
371
|
-
def _last_effectful_line(fn: ast.FunctionDef | ast.AsyncFunctionDef) -> int:
|
|
510
|
+
def _last_effectful_line(fn: ast.FunctionDef | ast.AsyncFunctionDef, origins: frozenset[str]) -> int:
|
|
372
511
|
last = 0
|
|
373
512
|
for node in walk(fn):
|
|
374
|
-
if isinstance(node, ast.Call) and not _is_assertion_call(node):
|
|
513
|
+
if isinstance(node, ast.Call) and not _is_assertion_call(node, origins):
|
|
375
514
|
last = max(last, node.lineno)
|
|
376
515
|
return last
|
|
377
516
|
|
|
378
517
|
|
|
379
|
-
def _is_assertion_call(node: ast.Call) -> bool:
|
|
518
|
+
def _is_assertion_call(node: ast.Call, origins: frozenset[str]) -> bool:
|
|
380
519
|
if not isinstance(node.func, ast.Attribute) or not node.func.attr.startswith(_ASSERT_PREFIX):
|
|
381
520
|
return False
|
|
382
|
-
|
|
521
|
+
asserted = _dotted(node.func.value)
|
|
522
|
+
return (
|
|
523
|
+
asserted is not None
|
|
524
|
+
and any(_is_prefix(origin, asserted) for origin in origins)
|
|
525
|
+
and not any(isinstance(child, ast.Call) for arg in (*node.args, *node.keywords) for child in walk(arg))
|
|
526
|
+
)
|
|
383
527
|
|
|
384
528
|
|
|
385
529
|
def _is_prefix(prefix: str, path: str) -> bool:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/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.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/__init__.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_comments.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_fastapi.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_first_party.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_fixed_record.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_imports.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_project_index.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_prose_budget.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/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
|
|
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.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/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
|
{sarj_python_lint-0.80.13 → sarj_python_lint-0.80.14}/src/sarj_python_lint/rules/stepdown.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|