sarj-python-lint 0.35.0__tar.gz → 0.36.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/PKG-INFO +22 -7
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/README.md +21 -6
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/pyproject.toml +1 -1
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_registry.py +4 -0
- sarj_python_lint-0.36.1/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +115 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +32 -9
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_comment_cruft.py +38 -1
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_implicit_attribute_access.py +67 -1
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/.gitignore +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_docstrings.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/kwonly_same_type_params.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_fstring_in_log.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/single_public_export.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/xfail_requires_strict.py +0 -0
- {sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/zero_assertion_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sarj-python-lint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.1
|
|
4
4
|
Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
|
|
5
5
|
Project-URL: Homepage, https://github.com/sarj-ai/standards/tree/main/packages/python
|
|
6
6
|
Project-URL: Repository, https://github.com/sarj-ai/standards
|
|
@@ -137,12 +137,27 @@ SARJ050 flags none of them. The remedy deletes the section and keeps the
|
|
|
137
137
|
summary, which was checked against the shipped strict config: ruff's D417 does
|
|
138
138
|
not fire on a docstring with no parameter section.
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
Two more shapes were rejected on volume — property docstrings restating the
|
|
141
|
+
property name and reST/epydoc type duplication (`:type x: int`, `:rtype:`) both
|
|
142
|
+
measure **0** first-party findings outside generated code.
|
|
143
|
+
|
|
144
|
+
### The `Returns:` half (0.36.0)
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
- id: sarj-docstring-returns-restate-signature # SARJ087
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`SARJ087` is the `Returns:` sibling of SARJ086, and it was **rejected once**:
|
|
151
|
+
deleting a `Returns:` section used to make ruff's DOC201 fire, so the only
|
|
152
|
+
compliant remedy was deleting the whole docstring. #164 then removed DOC201 from
|
|
153
|
+
`ruff.strict.toml` as a rule that DEMANDS prose, and the premise expired — under
|
|
154
|
+
the shipped config the section goes and the summary stays.
|
|
155
|
+
|
|
156
|
+
756 findings over 33 OSS repos / 35,254 files; two seeded samples of 40 and 20
|
|
157
|
+
read against source gave **~2%** false positives, the whole of which was one
|
|
158
|
+
family (`Returns: A new X` — whether the value is a copy is the one thing
|
|
159
|
+
`-> Self` cannot say) now guarded. Three findings on this repo's own source, all
|
|
160
|
+
true, all deleted. Measurements: [docs/rules/SARJ087.md](../../docs/rules/SARJ087.md).
|
|
146
161
|
|
|
147
162
|
### House conventions moved out of consumer repos (0.21.0)
|
|
148
163
|
|
|
@@ -119,12 +119,27 @@ SARJ050 flags none of them. The remedy deletes the section and keeps the
|
|
|
119
119
|
summary, which was checked against the shipped strict config: ruff's D417 does
|
|
120
120
|
not fire on a docstring with no parameter section.
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
Two more shapes were rejected on volume — property docstrings restating the
|
|
123
|
+
property name and reST/epydoc type duplication (`:type x: int`, `:rtype:`) both
|
|
124
|
+
measure **0** first-party findings outside generated code.
|
|
125
|
+
|
|
126
|
+
### The `Returns:` half (0.36.0)
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
- id: sarj-docstring-returns-restate-signature # SARJ087
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`SARJ087` is the `Returns:` sibling of SARJ086, and it was **rejected once**:
|
|
133
|
+
deleting a `Returns:` section used to make ruff's DOC201 fire, so the only
|
|
134
|
+
compliant remedy was deleting the whole docstring. #164 then removed DOC201 from
|
|
135
|
+
`ruff.strict.toml` as a rule that DEMANDS prose, and the premise expired — under
|
|
136
|
+
the shipped config the section goes and the summary stays.
|
|
137
|
+
|
|
138
|
+
756 findings over 33 OSS repos / 35,254 files; two seeded samples of 40 and 20
|
|
139
|
+
read against source gave **~2%** false positives, the whole of which was one
|
|
140
|
+
family (`Returns: A new X` — whether the value is a copy is the one thing
|
|
141
|
+
`-> Self` cannot say) now guarded. Three findings on this repo's own source, all
|
|
142
|
+
true, all deleted. Measurements: [docs/rules/SARJ087.md](../../docs/rules/SARJ087.md).
|
|
128
143
|
|
|
129
144
|
### House conventions moved out of consumer repos (0.21.0)
|
|
130
145
|
|
|
@@ -6,6 +6,9 @@ from sarj_python_lint.rules.conditional_assertion_in_test import ConditionalAsse
|
|
|
6
6
|
from sarj_python_lint.rules.docstring_args_restate_signature import (
|
|
7
7
|
DocstringArgsRestateSignature,
|
|
8
8
|
)
|
|
9
|
+
from sarj_python_lint.rules.docstring_returns_restate_signature import (
|
|
10
|
+
DocstringReturnsRestateSignature,
|
|
11
|
+
)
|
|
9
12
|
from sarj_python_lint.rules.duplicate_test_body import DuplicateTestBody
|
|
10
13
|
from sarj_python_lint.rules.duplicated_override_docstring import (
|
|
11
14
|
DuplicatedOverrideDocstring,
|
|
@@ -184,6 +187,7 @@ REGISTRY: dict[str, type[Rule]] = {
|
|
|
184
187
|
DuplicatedOverrideDocstring.id: DuplicatedOverrideDocstring,
|
|
185
188
|
RedundantClassDocstring.id: RedundantClassDocstring,
|
|
186
189
|
DocstringArgsRestateSignature.id: DocstringArgsRestateSignature,
|
|
190
|
+
DocstringReturnsRestateSignature.id: DocstringReturnsRestateSignature,
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
__all__ = ["REGISTRY"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""SARJ087 — A `Returns:` block that only re-spells the name and the return annotation.
|
|
2
|
+
|
|
3
|
+
Examples: https://github.com/sarj-ai/standards/blob/main/packages/python/tests/rules/test_docstring_returns_restate_signature.py
|
|
4
|
+
Evidence: https://github.com/sarj-ai/standards/blob/main/docs/rules/SARJ087.md
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import re
|
|
11
|
+
from typing import TYPE_CHECKING, override
|
|
12
|
+
|
|
13
|
+
from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
|
|
14
|
+
from sarj_python_lint.rules._ast_index import children
|
|
15
|
+
from sarj_python_lint.rules._comments import is_protected
|
|
16
|
+
from sarj_python_lint.rules._docstrings import (
|
|
17
|
+
PROMPT_DECORATOR_MARKERS,
|
|
18
|
+
VALUE_MARKER_RE,
|
|
19
|
+
decorator_markers,
|
|
20
|
+
restates,
|
|
21
|
+
sections,
|
|
22
|
+
signature_stems,
|
|
23
|
+
)
|
|
24
|
+
from sarj_python_lint.rules._paths import is_generated
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_RETURN_SECTIONS = ("Returns", "Return", "Yields", "Yield")
|
|
32
|
+
|
|
33
|
+
# Identity semantics: whether the value handed back is a FRESH object or the
|
|
34
|
+
# receiver itself is the one fact `-> Self` and `-> Foo` cannot carry, and the
|
|
35
|
+
# words that state it (`new`, `copy`, `same`) are stopwords for the restatement
|
|
36
|
+
# tokenizer, so the block reads as pure ceremony without this.
|
|
37
|
+
_IDENTITY_RE = re.compile(
|
|
38
|
+
r"\b(?:new|copy|copies|copied|clone[ds]?|fresh|same|itself|self|shallow|deep|"
|
|
39
|
+
r"in[- ]place|unchanged|original)\b",
|
|
40
|
+
re.IGNORECASE,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _return_block(docstring: str) -> str | None:
|
|
45
|
+
found = sections(docstring)
|
|
46
|
+
for name in _RETURN_SECTIONS:
|
|
47
|
+
if name in found:
|
|
48
|
+
return found[name]
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DocstringReturnsRestateSignature(Rule):
|
|
53
|
+
id: str = "docstring-returns-restate-signature"
|
|
54
|
+
code: str = "SARJ087"
|
|
55
|
+
has_evidence: bool = True
|
|
56
|
+
description: str = (
|
|
57
|
+
"`Returns:` block adds nothing the name and the return annotation do not "
|
|
58
|
+
"already say — delete the section and keep the summary."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@override
|
|
62
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
63
|
+
if is_generated(path, source):
|
|
64
|
+
return []
|
|
65
|
+
tree = parse_or_none(path, source)
|
|
66
|
+
if tree is None:
|
|
67
|
+
return []
|
|
68
|
+
diags: list[Diagnostic] = []
|
|
69
|
+
self._walk(tree, None, path, diags)
|
|
70
|
+
return sorted(diags, key=lambda diag: diag.line)
|
|
71
|
+
|
|
72
|
+
def _walk(self, node: ast.AST, class_name: str | None, path: Path, diags: list[Diagnostic]) -> None:
|
|
73
|
+
for child in children(node):
|
|
74
|
+
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
75
|
+
self._check_function(child, class_name, path, diags)
|
|
76
|
+
self._walk(child, class_name, path, diags)
|
|
77
|
+
elif isinstance(child, ast.ClassDef):
|
|
78
|
+
self._walk(child, child.name, path, diags)
|
|
79
|
+
else:
|
|
80
|
+
self._walk(child, class_name, path, diags)
|
|
81
|
+
|
|
82
|
+
def _check_function(
|
|
83
|
+
self,
|
|
84
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
85
|
+
class_name: str | None,
|
|
86
|
+
path: Path,
|
|
87
|
+
diags: list[Diagnostic],
|
|
88
|
+
) -> None:
|
|
89
|
+
docstring = ast.get_docstring(node, clean=True)
|
|
90
|
+
if not docstring:
|
|
91
|
+
return
|
|
92
|
+
block = _return_block(docstring)
|
|
93
|
+
if block is None:
|
|
94
|
+
return
|
|
95
|
+
if VALUE_MARKER_RE.search(block) or is_protected(block) or _IDENTITY_RE.search(block):
|
|
96
|
+
return
|
|
97
|
+
if decorator_markers(node) & PROMPT_DECORATOR_MARKERS:
|
|
98
|
+
return
|
|
99
|
+
stems = signature_stems(node, class_name)
|
|
100
|
+
# The whole-docstring case is SARJ050's; reporting it here too would
|
|
101
|
+
# make one deletion look like two findings.
|
|
102
|
+
if restates(docstring, stems):
|
|
103
|
+
return
|
|
104
|
+
if not restates(block, stems):
|
|
105
|
+
return
|
|
106
|
+
expr = node.body[0]
|
|
107
|
+
diags.append(
|
|
108
|
+
Diagnostic(
|
|
109
|
+
path=path,
|
|
110
|
+
line=expr.lineno,
|
|
111
|
+
col=expr.col_offset + 1,
|
|
112
|
+
code=self.code,
|
|
113
|
+
message=self.description,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
@@ -19,6 +19,29 @@ if TYPE_CHECKING:
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _src(node: ast.expr) -> str:
|
|
23
|
+
"""`ast.unparse` for the shapes this rule compares, without `ast.unparse`'s cost.
|
|
24
|
+
|
|
25
|
+
The rule identifies an accumulator by the SOURCE TEXT of its target, so it unparses
|
|
26
|
+
once per Name/Attribute in every loop body. `ast.unparse` builds and runs a full
|
|
27
|
+
unparser on each call. For a bare `Name` the answer is the identifier, and for a
|
|
28
|
+
dotted chain over one it is the chain — byte-identical, far cheaper. Everything
|
|
29
|
+
else falls through to `ast.unparse`, so no comparison changes meaning.
|
|
30
|
+
|
|
31
|
+
This is why the rule stopped being the registry's performance outlier; see
|
|
32
|
+
`tests/test_perf.py`.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The node's source text.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(node, ast.Name):
|
|
39
|
+
return node.id
|
|
40
|
+
if isinstance(node, ast.Attribute) and isinstance(node.value, (ast.Name, ast.Attribute)):
|
|
41
|
+
return f"{_src(node.value)}.{node.attr}"
|
|
42
|
+
return ast.unparse(node)
|
|
43
|
+
|
|
44
|
+
|
|
22
45
|
class InefficientStringConcatInLoop(Rule):
|
|
23
46
|
id: str = "inefficient-string-concat-in-loop"
|
|
24
47
|
code: str = "SARJ002"
|
|
@@ -99,7 +122,7 @@ class _ConcatVisitor(ast.NodeVisitor):
|
|
|
99
122
|
|
|
100
123
|
def _is_probe_target(self, node: ast.AugAssign | ast.Assign) -> bool:
|
|
101
124
|
"""Report whether the concat target's intermediate values are consumed."""
|
|
102
|
-
target_src =
|
|
125
|
+
target_src = _src(self._accumulation_target(node))
|
|
103
126
|
if any(target_src in names for names in self._while_probe_names):
|
|
104
127
|
return True
|
|
105
128
|
# Only the INNERMOST loop's reads consume the growth per iteration; a
|
|
@@ -109,7 +132,7 @@ class _ConcatVisitor(ast.NodeVisitor):
|
|
|
109
132
|
def _is_loop_local_target(self, node: ast.AugAssign | ast.Assign) -> bool:
|
|
110
133
|
"""Report whether the concat target is freshly rebound earlier this iteration."""
|
|
111
134
|
target = self._accumulation_target(node)
|
|
112
|
-
rebinds = self._loop_reassigns[-1].get(
|
|
135
|
+
rebinds = self._loop_reassigns[-1].get(_src(target), ())
|
|
113
136
|
return any(line < node.lineno for line in rebinds)
|
|
114
137
|
|
|
115
138
|
def _accumulation_target(self, node: ast.AugAssign | ast.Assign) -> ast.expr:
|
|
@@ -149,7 +172,7 @@ class _ConcatVisitor(ast.NodeVisitor):
|
|
|
149
172
|
|
|
150
173
|
def _test_names(test: ast.expr) -> set[str]:
|
|
151
174
|
"""Collect the source text of every Name/Attribute read in a while test."""
|
|
152
|
-
return {
|
|
175
|
+
return {_src(n) for n in walk(test) if isinstance(n, (ast.Name, ast.Attribute))}
|
|
153
176
|
|
|
154
177
|
|
|
155
178
|
def _loop_read_names(loop: ast.For | ast.AsyncFor | ast.While) -> frozenset[str]:
|
|
@@ -170,7 +193,7 @@ def _loop_read_names(loop: ast.For | ast.AsyncFor | ast.While) -> frozenset[str]
|
|
|
170
193
|
continue
|
|
171
194
|
stack.extend(children(node))
|
|
172
195
|
if isinstance(node, (ast.Name, ast.Attribute)) and isinstance(node.ctx, ast.Load):
|
|
173
|
-
reads.add(
|
|
196
|
+
reads.add(_src(node))
|
|
174
197
|
return frozenset(reads)
|
|
175
198
|
|
|
176
199
|
|
|
@@ -192,13 +215,13 @@ def _collect_reassignments(node: ast.AST, reassigns: dict[str, list[int]]) -> No
|
|
|
192
215
|
for target in node.targets:
|
|
193
216
|
for bound in _iter_binding_targets(target):
|
|
194
217
|
if not _is_accumulation_assign(bound, node.value):
|
|
195
|
-
reassigns.setdefault(
|
|
218
|
+
reassigns.setdefault(_src(bound), []).append(bound.lineno)
|
|
196
219
|
elif (
|
|
197
220
|
isinstance(node, ast.AnnAssign)
|
|
198
221
|
and node.value is not None
|
|
199
222
|
and not _is_accumulation_assign(node.target, node.value)
|
|
200
223
|
):
|
|
201
|
-
reassigns.setdefault(
|
|
224
|
+
reassigns.setdefault(_src(node.target), []).append(node.target.lineno)
|
|
202
225
|
for child in children(node):
|
|
203
226
|
_collect_reassignments(child, reassigns)
|
|
204
227
|
|
|
@@ -222,10 +245,10 @@ def _is_accumulation_assign(target: ast.expr, value: ast.expr) -> bool:
|
|
|
222
245
|
|
|
223
246
|
def _other_add_operand(target: ast.expr, binop: ast.BinOp) -> ast.expr | None:
|
|
224
247
|
"""Return the non-target operand of `target + x` / `x + target`."""
|
|
225
|
-
target_src =
|
|
226
|
-
if
|
|
248
|
+
target_src = _src(target)
|
|
249
|
+
if _src(binop.left) == target_src:
|
|
227
250
|
return binop.right
|
|
228
|
-
if
|
|
251
|
+
if _src(binop.right) == target_src:
|
|
229
252
|
return binop.left
|
|
230
253
|
return None
|
|
231
254
|
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
@@ -56,6 +56,22 @@ _LICENSE_RE = re.compile(
|
|
|
56
56
|
re.IGNORECASE,
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
+
# A licence header is a legally required block of text a contributor cannot
|
|
60
|
+
# restructure, and the `# ---------` rules that box it in are part of it. The
|
|
61
|
+
# rule already knows this -- `_flag_leading_preamble` returns early on
|
|
62
|
+
# `_LICENSE_RE` -- but the BANNER branch did not, so "structure code with
|
|
63
|
+
# functions, not ASCII rules" was reported against the one comment in the file
|
|
64
|
+
# that is not about structure at all. 831 findings across 590 corpus files sat
|
|
65
|
+
# on a punctuation rule in the first 8 lines of a file whose header carries a
|
|
66
|
+
# copyright or SPDX line: `bokeh/docs/bokeh/docserver.py:1`,
|
|
67
|
+
# `bokeh/release/checks.py:1`, `bokeh/release/__main__.py:1` are the shape --
|
|
68
|
+
# `# ---…---` immediately above `# Copyright (c) Anaconda, Inc.`.
|
|
69
|
+
#
|
|
70
|
+
# Scoped to the file HEADER, not to every copyright mention: a banner beside a
|
|
71
|
+
# licence reference in the middle of a file is an ordinary section rule.
|
|
72
|
+
_LICENSE_HEADER_MAX_LINE = 8
|
|
73
|
+
_LICENSE_HEADER_RADIUS = 4
|
|
74
|
+
|
|
59
75
|
_BANNER_FULL_RE = re.compile(r"^[-=#*~_+.\s]{4,}$")
|
|
60
76
|
# `[\u2500-\u257f]` is the Unicode box-drawing block. A `────────` rule is the
|
|
61
77
|
# same section separator as `--------`, just prettier; 34 of them were sitting in
|
|
@@ -441,6 +457,24 @@ def _is_assign_or_call(snippet: str) -> bool:
|
|
|
441
457
|
return isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call)
|
|
442
458
|
|
|
443
459
|
|
|
460
|
+
def _license_header_lines(standalone: list[tuple[int, int, str]]) -> frozenset[int]:
|
|
461
|
+
"""Collect the lines belonging to a file-header licence block.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
Every line within `_LICENSE_HEADER_RADIUS` of a licence comment that
|
|
465
|
+
sits in the first `_LICENSE_HEADER_MAX_LINE` lines of the file.
|
|
466
|
+
|
|
467
|
+
"""
|
|
468
|
+
anchors = [
|
|
469
|
+
line for line, _, body in standalone if line <= _LICENSE_HEADER_MAX_LINE and _LICENSE_RE.search(body)
|
|
470
|
+
]
|
|
471
|
+
return frozenset(
|
|
472
|
+
line
|
|
473
|
+
for anchor in anchors
|
|
474
|
+
for line in range(anchor - _LICENSE_HEADER_RADIUS, anchor + _LICENSE_HEADER_RADIUS + 1)
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
|
|
444
478
|
class NoCommentCruft(Rule):
|
|
445
479
|
id: str = "no-comment-cruft"
|
|
446
480
|
code: str = "SARJ016"
|
|
@@ -468,6 +502,7 @@ class NoCommentCruft(Rule):
|
|
|
468
502
|
referenced = _externally_referenced_lines(standalone)
|
|
469
503
|
nested = nested_comment_lines(source)
|
|
470
504
|
enumerated = [line for line, _, body in standalone if _ENUMERATION_RE.match(body)]
|
|
505
|
+
license_header = _license_header_lines(standalone)
|
|
471
506
|
for line, col, body in standalone:
|
|
472
507
|
if _is_directive(body) or _is_coding_cookie(body) or line in skip:
|
|
473
508
|
continue
|
|
@@ -478,6 +513,7 @@ class NoCommentCruft(Rule):
|
|
|
478
513
|
narration_protected=line in referenced,
|
|
479
514
|
isolated_enumeration=enumerated == [line],
|
|
480
515
|
nested=line in nested,
|
|
516
|
+
in_license_header=line in license_header,
|
|
481
517
|
)
|
|
482
518
|
if msg is not None:
|
|
483
519
|
diags[line] = Diagnostic(path=path, line=line, col=col + 1, code=self.code, message=msg)
|
|
@@ -492,6 +528,7 @@ class NoCommentCruft(Rule):
|
|
|
492
528
|
narration_protected: bool,
|
|
493
529
|
isolated_enumeration: bool,
|
|
494
530
|
nested: bool,
|
|
531
|
+
in_license_header: bool,
|
|
495
532
|
) -> str | None:
|
|
496
533
|
if _CODE_REGEN_CALL_RE.match(body):
|
|
497
534
|
return None
|
|
@@ -500,7 +537,7 @@ class NoCommentCruft(Rule):
|
|
|
500
537
|
return "Untracked TODO/FIXME marker — add an issue ticket or context link."
|
|
501
538
|
return None
|
|
502
539
|
if _is_banner(body):
|
|
503
|
-
if _is_heading_underline(body, prev_body):
|
|
540
|
+
if _is_heading_underline(body, prev_body) or in_license_header:
|
|
504
541
|
return None
|
|
505
542
|
return "Section-banner / region comment — structure code with functions, not ASCII rules."
|
|
506
543
|
if _looks_like_code(body):
|
|
@@ -28,6 +28,13 @@ _EXCLUDED_BASES = {
|
|
|
28
28
|
"config",
|
|
29
29
|
"kwargs",
|
|
30
30
|
"env",
|
|
31
|
+
# A deliberately OPEN extension bag: the framework guarantees the mapping
|
|
32
|
+
# exists and guarantees nothing about its keys, because third-party code is
|
|
33
|
+
# what puts them there. `scrapy/core/downloader/handlers/http11.py:531`
|
|
34
|
+
# (`request.meta.get("download_maxsize", self._maxsize)`) is the shape --
|
|
35
|
+
# Scrapy documents `Request.meta` as the per-request extension dict, and no
|
|
36
|
+
# model can enumerate keys that downstream middlewares invent.
|
|
37
|
+
"meta",
|
|
31
38
|
"os",
|
|
32
39
|
"sys",
|
|
33
40
|
}
|
|
@@ -114,6 +121,7 @@ class _FileFacts:
|
|
|
114
121
|
"""The whole-file context a single subscript cannot answer for itself."""
|
|
115
122
|
|
|
116
123
|
annotation_nodes: frozenset[int]
|
|
124
|
+
decorator_nodes: frozenset[int]
|
|
117
125
|
mutation_receivers: frozenset[int]
|
|
118
126
|
schema_bound_names: frozenset[str]
|
|
119
127
|
constant_tables: frozenset[str]
|
|
@@ -242,6 +250,8 @@ def _is_exempt(node: ast.Call | ast.Subscript, key: str, facts: _FileFacts) -> b
|
|
|
242
250
|
return True
|
|
243
251
|
if id(node) in facts.annotation_nodes:
|
|
244
252
|
return True
|
|
253
|
+
if id(node) in facts.decorator_nodes:
|
|
254
|
+
return True
|
|
245
255
|
if isinstance(node, ast.Subscript) and id(node) in facts.mutation_receivers:
|
|
246
256
|
return True
|
|
247
257
|
receiver = _receiver(node)
|
|
@@ -278,9 +288,10 @@ def _file_facts(tree: ast.Module) -> _FileFacts:
|
|
|
278
288
|
The four indexes, each built from the memoized per-file node index.
|
|
279
289
|
|
|
280
290
|
"""
|
|
281
|
-
typed_dicts = _typed_dict_class_names(tree)
|
|
291
|
+
typed_dicts = _typed_dict_class_names(tree) | _declared_type_names(tree)
|
|
282
292
|
return _FileFacts(
|
|
283
293
|
annotation_nodes=_annotation_nodes(tree),
|
|
294
|
+
decorator_nodes=_decorator_nodes(tree),
|
|
284
295
|
mutation_receivers=_mutation_receivers(tree),
|
|
285
296
|
schema_bound_names=_schema_bound_names(tree, typed_dicts) if typed_dicts else frozenset(),
|
|
286
297
|
constant_tables=_constant_tables(tree),
|
|
@@ -308,6 +319,27 @@ def _annotation_nodes(tree: ast.Module) -> frozenset[int]:
|
|
|
308
319
|
return frozenset(id(inner) for root in roots for inner in walk(root))
|
|
309
320
|
|
|
310
321
|
|
|
322
|
+
def _decorator_nodes(tree: ast.Module) -> frozenset[int]:
|
|
323
|
+
"""Collect the identity of every node inside a decorator expression.
|
|
324
|
+
|
|
325
|
+
A decorator is never a mapping lookup, so `@router.get("")` is not one --
|
|
326
|
+
but `_looks_like_route_or_url` only recognises a value starting with `/` or
|
|
327
|
+
containing `://`, and the EMPTY-STRING route (the router-root registration
|
|
328
|
+
FastAPI projects write) slips through both. 23 findings across airflow,
|
|
329
|
+
litellm and prefect were exactly `@<router>.get("")` on the decorator line.
|
|
330
|
+
Position answers it exactly where the argument cannot, and costs no recall:
|
|
331
|
+
nothing in `decorator_list` is a payload field read.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
`id()` of each node in a decorator subtree.
|
|
335
|
+
|
|
336
|
+
"""
|
|
337
|
+
roots: list[ast.expr] = []
|
|
338
|
+
for node in nodes(tree, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef):
|
|
339
|
+
roots.extend(node.decorator_list)
|
|
340
|
+
return frozenset(id(inner) for root in roots for inner in walk(root))
|
|
341
|
+
|
|
342
|
+
|
|
311
343
|
def _mutation_receivers(tree: ast.Module) -> frozenset[int]:
|
|
312
344
|
"""Collect subscripts that are the receiver of an in-place collection method.
|
|
313
345
|
|
|
@@ -403,6 +435,40 @@ def _parse_type_text(text: str) -> ast.expr | None:
|
|
|
403
435
|
return None
|
|
404
436
|
|
|
405
437
|
|
|
438
|
+
# Modules whose exports are containers and escape hatches rather than schemas.
|
|
439
|
+
# `from typing import Any` followed by `payload: Any` declares nothing.
|
|
440
|
+
_STRUCTURELESS_IMPORT_MODULES = frozenset(
|
|
441
|
+
{"builtins", "collections", "collections.abc", "typing", "typing_extensions"}
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _declared_type_names(tree: ast.Module) -> frozenset[str]:
|
|
446
|
+
"""Collect names imported into this module that could name a declared shape.
|
|
447
|
+
|
|
448
|
+
A receiver annotated with a type this file IMPORTS has already been given a
|
|
449
|
+
schema by its author -- most often a TypedDict, which subscripts by design:
|
|
450
|
+
`AllMessageValues` in `litellm/…/prompt_templates/factory.py` is OpenAI's
|
|
451
|
+
message TypedDict, and `current_message["role"]` is the DECLARATIVE form,
|
|
452
|
+
not a substitute for one. `_typed_dict_class_names` sees only `class X
|
|
453
|
+
(TypedDict)` written in the same file, so every cross-module TypedDict --
|
|
454
|
+
which is nearly all of them -- was invisible.
|
|
455
|
+
|
|
456
|
+
This cannot resolve the import to check what it really is, so it is
|
|
457
|
+
deliberately generous: `typing`/`collections` exports are excluded because
|
|
458
|
+
`Any` and `dict` declare nothing, and everything else is taken at its word.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
The imported names usable as an annotation head.
|
|
462
|
+
|
|
463
|
+
"""
|
|
464
|
+
declared: set[str] = set()
|
|
465
|
+
for node in nodes(tree, ast.Import, ast.ImportFrom):
|
|
466
|
+
if isinstance(node, ast.ImportFrom) and node.module in _STRUCTURELESS_IMPORT_MODULES:
|
|
467
|
+
continue
|
|
468
|
+
declared.update(alias.asname or alias.name.split(".")[0] for alias in node.names)
|
|
469
|
+
return frozenset(name for name in declared if name[:1].isupper())
|
|
470
|
+
|
|
471
|
+
|
|
406
472
|
def _schema_bound_names(tree: ast.Module, typed_dicts: frozenset[str]) -> frozenset[str]:
|
|
407
473
|
"""Collect names whose declared type is one of this file's TypedDicts.
|
|
408
474
|
|
|
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.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/_first_party.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
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_fat_try_blocks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_fstring_in_log.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.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_select_star.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/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
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/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
|
{sarj_python_lint-0.35.0 → sarj_python_lint-0.36.1}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|