sarj-python-lint 0.38.0__tar.gz → 0.40.0__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.38.0 → sarj_python_lint-0.40.0}/PKG-INFO +47 -1
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/README.md +46 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/pyproject.toml +1 -1
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_docstrings.py +45 -1
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_registry.py +4 -0
- sarj_python_lint-0.40.0/src/sarj_python_lint/rules/phase_label_comment.py +57 -0
- sarj_python_lint-0.40.0/src/sarj_python_lint/rules/restated_test_docstring.py +175 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/zero_assertion_test.py +53 -1
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/.gitignore +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/__init__.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/__main__.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/_secret_names.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/_version.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/py.typed +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/ratchet.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rule_base.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/__init__.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_comments.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_logging.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_paths.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_sql.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/conditional_assertion_in_test.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/inefficient_string_concat_in_loop.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/interaction_only_test.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/kwonly_same_type_params.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_fat_try_blocks.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_file_level_suppression.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_fstring_in_log.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_raw_sql_in_tests.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_sequential_await.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_sleep_in_test_body.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_unreachable_after_terminal.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/parametrize_case_needs_id.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_match_pattern_destructuring.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_real_store_in_tests.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/single_public_export.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/sleep_with_computed_arg_in_test.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/tautological_mock_assertion.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/test_loops_over_literal_cases.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
- {sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/xfail_requires_strict.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.40.0
|
|
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
|
|
@@ -159,6 +159,52 @@ family (`Returns: A new X` — whether the value is a copy is the one thing
|
|
|
159
159
|
`-> Self` cannot say) now guarded. Three findings on this repo's own source, all
|
|
160
160
|
true, all deleted. Measurements: [docs/rules/SARJ087.md](../../docs/rules/SARJ087.md).
|
|
161
161
|
|
|
162
|
+
### Test ceremony, and the census it was chosen from (0.38.0)
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
- id: sarj-restated-test-docstring # SARJ088
|
|
166
|
+
- id: sarj-test-phase-label-comment # SARJ089
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Every comment GROUP in 19 repositories / 45,900 Python files was collected with
|
|
170
|
+
its adjacent code and classified: **451,482 groups, 1,293,022 lines**, of which
|
|
171
|
+
the seven comment/docstring rules that predate this release reached **4.9%**.
|
|
172
|
+
The full census table is in [docs/rules/SARJ088.md](../../docs/rules/SARJ088.md).
|
|
173
|
+
|
|
174
|
+
The largest precisely-detectable class left in it is the **test docstring**:
|
|
175
|
+
52,894 of them, 10.1% of every comment group, and SARJ050 reached 4.7%. It
|
|
176
|
+
reached so few because SARJ050 measures a docstring against its *signature*, and
|
|
177
|
+
a test's specification is its BODY. `SARJ088` measures it against the signature,
|
|
178
|
+
the identifiers in the test's own body, and the vocabulary a test docstring
|
|
179
|
+
spends on being a test. **5,382 findings; 98 read at source, 0 false positives
|
|
180
|
+
on the shipped predicate.**
|
|
181
|
+
|
|
182
|
+
`SARJ089` deletes the bare `# given` / `# when` / `# then` / `# Arrange` /
|
|
183
|
+
`# Act` / `# Assert` phase label. 27,714 findings, 36 read, 0 false positives —
|
|
184
|
+
but **94.5% come from one OSS suite**, and none from any first-party repo. It is
|
|
185
|
+
a fence against the convention arriving, not a cleanup; adopt it behind the
|
|
186
|
+
baseline ratchet.
|
|
187
|
+
|
|
188
|
+
Shipped with them, `_docstrings.STOPWORDS` gained `FILLER_QUALIFIERS`: 31
|
|
189
|
+
qualifiers that narrow nothing (`specific`, `appropriate`, `entire`, `overall`).
|
|
190
|
+
One of these was the commonest single reason a pure restatement survived the
|
|
191
|
+
whole family — `"""Get a specific account by ID."""` over
|
|
192
|
+
`get_account(self, account_id: str)`. **+683 findings across SARJ050/085/086/087,
|
|
193
|
+
-0; 58 of the delta read, ~3.4% false positives.** `main` was tried and rejected:
|
|
194
|
+
as filler it makes `"""Main function."""` content-free, hence unflaggable.
|
|
195
|
+
|
|
196
|
+
**Three shapes measured on the same census and rejected**, each on a seeded
|
|
197
|
+
12-finding read at source:
|
|
198
|
+
|
|
199
|
+
| shape | findings | true | why it fails |
|
|
200
|
+
| --- | ---: | ---: | --- |
|
|
201
|
+
| comment restates the `if` / `for` / `with` header below it | 225 | 3/12 | the population is BRANCH LABELS naming a case (`# PIL.Image` over `if isinstance(item, PILImage.Image)`), not narration |
|
|
202
|
+
| comment restates a plain assignment (no call on the RHS) | 308 | 2/12 | the population heads a multi-line literal or a 3-statement block — a section label, which is SARJ016's subject |
|
|
203
|
+
| multi-line comment run restating the block it heads | 119 | 1-2/12 | banners, Sphinx `#:` attribute docs, and worked calculations dominate |
|
|
204
|
+
|
|
205
|
+
Those three are why SARJ049 still excludes block openers, plain assignments and
|
|
206
|
+
multi-line runs. The exclusions are load-bearing, not unfinished work.
|
|
207
|
+
|
|
162
208
|
### House conventions moved out of consumer repos (0.21.0)
|
|
163
209
|
|
|
164
210
|
```yaml
|
|
@@ -141,6 +141,52 @@ family (`Returns: A new X` — whether the value is a copy is the one thing
|
|
|
141
141
|
`-> Self` cannot say) now guarded. Three findings on this repo's own source, all
|
|
142
142
|
true, all deleted. Measurements: [docs/rules/SARJ087.md](../../docs/rules/SARJ087.md).
|
|
143
143
|
|
|
144
|
+
### Test ceremony, and the census it was chosen from (0.38.0)
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
- id: sarj-restated-test-docstring # SARJ088
|
|
148
|
+
- id: sarj-test-phase-label-comment # SARJ089
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Every comment GROUP in 19 repositories / 45,900 Python files was collected with
|
|
152
|
+
its adjacent code and classified: **451,482 groups, 1,293,022 lines**, of which
|
|
153
|
+
the seven comment/docstring rules that predate this release reached **4.9%**.
|
|
154
|
+
The full census table is in [docs/rules/SARJ088.md](../../docs/rules/SARJ088.md).
|
|
155
|
+
|
|
156
|
+
The largest precisely-detectable class left in it is the **test docstring**:
|
|
157
|
+
52,894 of them, 10.1% of every comment group, and SARJ050 reached 4.7%. It
|
|
158
|
+
reached so few because SARJ050 measures a docstring against its *signature*, and
|
|
159
|
+
a test's specification is its BODY. `SARJ088` measures it against the signature,
|
|
160
|
+
the identifiers in the test's own body, and the vocabulary a test docstring
|
|
161
|
+
spends on being a test. **5,382 findings; 98 read at source, 0 false positives
|
|
162
|
+
on the shipped predicate.**
|
|
163
|
+
|
|
164
|
+
`SARJ089` deletes the bare `# given` / `# when` / `# then` / `# Arrange` /
|
|
165
|
+
`# Act` / `# Assert` phase label. 27,714 findings, 36 read, 0 false positives —
|
|
166
|
+
but **94.5% come from one OSS suite**, and none from any first-party repo. It is
|
|
167
|
+
a fence against the convention arriving, not a cleanup; adopt it behind the
|
|
168
|
+
baseline ratchet.
|
|
169
|
+
|
|
170
|
+
Shipped with them, `_docstrings.STOPWORDS` gained `FILLER_QUALIFIERS`: 31
|
|
171
|
+
qualifiers that narrow nothing (`specific`, `appropriate`, `entire`, `overall`).
|
|
172
|
+
One of these was the commonest single reason a pure restatement survived the
|
|
173
|
+
whole family — `"""Get a specific account by ID."""` over
|
|
174
|
+
`get_account(self, account_id: str)`. **+683 findings across SARJ050/085/086/087,
|
|
175
|
+
-0; 58 of the delta read, ~3.4% false positives.** `main` was tried and rejected:
|
|
176
|
+
as filler it makes `"""Main function."""` content-free, hence unflaggable.
|
|
177
|
+
|
|
178
|
+
**Three shapes measured on the same census and rejected**, each on a seeded
|
|
179
|
+
12-finding read at source:
|
|
180
|
+
|
|
181
|
+
| shape | findings | true | why it fails |
|
|
182
|
+
| --- | ---: | ---: | --- |
|
|
183
|
+
| comment restates the `if` / `for` / `with` header below it | 225 | 3/12 | the population is BRANCH LABELS naming a case (`# PIL.Image` over `if isinstance(item, PILImage.Image)`), not narration |
|
|
184
|
+
| comment restates a plain assignment (no call on the RHS) | 308 | 2/12 | the population heads a multi-line literal or a 3-statement block — a section label, which is SARJ016's subject |
|
|
185
|
+
| multi-line comment run restating the block it heads | 119 | 1-2/12 | banners, Sphinx `#:` attribute docs, and worked calculations dominate |
|
|
186
|
+
|
|
187
|
+
Those three are why SARJ049 still excludes block openers, plain assignments and
|
|
188
|
+
multi-line runs. The exclusions are load-bearing, not unfinished work.
|
|
189
|
+
|
|
144
190
|
### House conventions moved out of consumer repos (0.21.0)
|
|
145
191
|
|
|
146
192
|
```yaml
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_docstrings.py
RENAMED
|
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
|
|
38
38
|
# Docstring filler that says nothing about *which* thing is being described.
|
|
39
39
|
# `not` / `no` / `none` / `never` are deliberately ABSENT: a docstring that
|
|
40
40
|
# negates the obvious reading of a name is the most useful kind there is.
|
|
41
|
-
|
|
41
|
+
_BASE_STOPWORDS = frozenset(
|
|
42
42
|
{
|
|
43
43
|
"a",
|
|
44
44
|
"all",
|
|
@@ -107,6 +107,50 @@ STOPWORDS = frozenset(
|
|
|
107
107
|
}
|
|
108
108
|
)
|
|
109
109
|
|
|
110
|
+
# Qualifiers that NARROW NOTHING. "a specific account", "the appropriate
|
|
111
|
+
# config", "the entire widget" — strip the adjective and the sentence means
|
|
112
|
+
# exactly what it meant. Kept as its own name because a single one of these
|
|
113
|
+
# was the commonest reason a pure restatement survived the whole family; see
|
|
114
|
+
# docs/rules/SARJ088.md for the measurement. `main`, `new`, `same` and `copy`
|
|
115
|
+
# are NOT here: they name the thing or its identity, which is content.
|
|
116
|
+
FILLER_QUALIFIERS = frozenset(
|
|
117
|
+
{
|
|
118
|
+
"actual",
|
|
119
|
+
"already",
|
|
120
|
+
"appropriate",
|
|
121
|
+
"associated",
|
|
122
|
+
"available",
|
|
123
|
+
"basic",
|
|
124
|
+
"correct",
|
|
125
|
+
"correctly",
|
|
126
|
+
"corresponding",
|
|
127
|
+
"default",
|
|
128
|
+
"desired",
|
|
129
|
+
"entire",
|
|
130
|
+
"existing",
|
|
131
|
+
"full",
|
|
132
|
+
"general",
|
|
133
|
+
"necessary",
|
|
134
|
+
"optional",
|
|
135
|
+
"overall",
|
|
136
|
+
"particular",
|
|
137
|
+
"properly",
|
|
138
|
+
"relevant",
|
|
139
|
+
"required",
|
|
140
|
+
"simple",
|
|
141
|
+
"single",
|
|
142
|
+
"specific",
|
|
143
|
+
"standard",
|
|
144
|
+
"successfully",
|
|
145
|
+
"suitable",
|
|
146
|
+
"supported",
|
|
147
|
+
"various",
|
|
148
|
+
"whole",
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
STOPWORDS = _BASE_STOPWORDS | FILLER_QUALIFIERS
|
|
153
|
+
|
|
110
154
|
WORD_RE = re.compile(r"[A-Za-z][A-Za-z0-9']*")
|
|
111
155
|
|
|
112
156
|
_IDENTIFIER_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
@@ -57,6 +57,7 @@ from sarj_python_lint.rules.no_unreachable_after_terminal import (
|
|
|
57
57
|
)
|
|
58
58
|
from sarj_python_lint.rules.over_mocked_test import OverMockedTest
|
|
59
59
|
from sarj_python_lint.rules.parametrize_case_needs_id import ParametrizeCaseNeedsId
|
|
60
|
+
from sarj_python_lint.rules.phase_label_comment import TestPhaseLabelComment
|
|
60
61
|
from sarj_python_lint.rules.prefer_class_row import PreferClassRow
|
|
61
62
|
from sarj_python_lint.rules.prefer_constant_time_secret_compare import (
|
|
62
63
|
PreferConstantTimeSecretCompare,
|
|
@@ -94,6 +95,7 @@ from sarj_python_lint.rules.pydantic_at_boundaries import PydanticAtBoundaries
|
|
|
94
95
|
from sarj_python_lint.rules.redundant_class_docstring import RedundantClassDocstring
|
|
95
96
|
from sarj_python_lint.rules.redundant_docstring import RedundantDocstring
|
|
96
97
|
from sarj_python_lint.rules.require_port_for_service import RequirePortForService
|
|
98
|
+
from sarj_python_lint.rules.restated_test_docstring import RestatedTestDocstring
|
|
97
99
|
from sarj_python_lint.rules.single_public_export import SinglePublicExport
|
|
98
100
|
from sarj_python_lint.rules.sleep_with_computed_arg_in_test import SleepWithComputedArgInTest
|
|
99
101
|
from sarj_python_lint.rules.stepdown import Stepdown
|
|
@@ -186,6 +188,8 @@ REGISTRY: dict[str, type[Rule]] = {
|
|
|
186
188
|
RedundantClassDocstring.id: RedundantClassDocstring,
|
|
187
189
|
DocstringArgsRestateSignature.id: DocstringArgsRestateSignature,
|
|
188
190
|
DocstringReturnsRestateSignature.id: DocstringReturnsRestateSignature,
|
|
191
|
+
RestatedTestDocstring.id: RestatedTestDocstring,
|
|
192
|
+
TestPhaseLabelComment.id: TestPhaseLabelComment,
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
__all__ = ["REGISTRY"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""SARJ089 — A bare Arrange/Act/Assert (or Given/When/Then) phase label in a test.
|
|
2
|
+
|
|
3
|
+
Examples: https://github.com/sarj-ai/standards/blob/main/packages/python/tests/rules/test_phase_label_comment.py
|
|
4
|
+
Evidence: https://github.com/sarj-ai/standards/blob/main/docs/rules/SARJ089.md
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import tokenize
|
|
11
|
+
from typing import TYPE_CHECKING, override
|
|
12
|
+
|
|
13
|
+
from sarj_python_lint.rule_base import Diagnostic, Rule
|
|
14
|
+
from sarj_python_lint.rules._comments import nested_comment_lines, standalone_comments
|
|
15
|
+
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# The phase vocabulary, as a whole-body match. `setup` and `teardown` are
|
|
23
|
+
# deliberately ABSENT: SARJ016 already owns them through its section-label
|
|
24
|
+
# vocabulary, and reporting one deletion twice makes the finding look bigger
|
|
25
|
+
# than it is.
|
|
26
|
+
_PHASE_RE = re.compile(
|
|
27
|
+
r"^(?:arrange|act|assert(?:ion)?s?|given|when|then|exercise|execute|verif(?:y|ication)|"
|
|
28
|
+
r"cleanup|prepare|sanity(?:\s+check)?|arrange\s*[/&+]\s*act|act\s*[/&+]\s*assert|"
|
|
29
|
+
r"given\s*[/&+]\s*when|when\s*[/&+]\s*then)"
|
|
30
|
+
r"\s*[.:;!\-–—]*\s*$",
|
|
31
|
+
re.IGNORECASE,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestPhaseLabelComment(Rule):
|
|
36
|
+
id: str = "test-phase-label-comment"
|
|
37
|
+
code: str = "SARJ089"
|
|
38
|
+
has_evidence: bool = True
|
|
39
|
+
description: str = (
|
|
40
|
+
"Bare test-phase label — delete it; a test whose phases need signposting "
|
|
41
|
+
"wants a named helper or a smaller test, not a comment."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
46
|
+
if is_generated(path, source) or not is_test_path(path):
|
|
47
|
+
return []
|
|
48
|
+
try:
|
|
49
|
+
standalone, _ = standalone_comments(source)
|
|
50
|
+
nested = nested_comment_lines(source)
|
|
51
|
+
except tokenize.TokenError, IndentationError, SyntaxError:
|
|
52
|
+
return []
|
|
53
|
+
return [
|
|
54
|
+
Diagnostic(path=path, line=line, col=col + 1, code=self.code, message=self.description)
|
|
55
|
+
for line, col, body in standalone
|
|
56
|
+
if line not in nested and _PHASE_RE.match(body)
|
|
57
|
+
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""SARJ088 — A test docstring that only re-spells the test's own name and body.
|
|
2
|
+
|
|
3
|
+
Examples: https://github.com/sarj-ai/standards/blob/main/packages/python/tests/rules/test_restated_test_docstring.py
|
|
4
|
+
Evidence: https://github.com/sarj-ai/standards/blob/main/docs/rules/SARJ088.md
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
from typing import TYPE_CHECKING, override
|
|
11
|
+
|
|
12
|
+
from sarj_python_lint.rule_base import Diagnostic, Rule, parse_or_none
|
|
13
|
+
from sarj_python_lint.rules._ast_index import children
|
|
14
|
+
from sarj_python_lint.rules._comments import is_protected, split_identifier, stem
|
|
15
|
+
from sarj_python_lint.rules._docstrings import (
|
|
16
|
+
VALUE_MARKER_RE,
|
|
17
|
+
restates,
|
|
18
|
+
sections,
|
|
19
|
+
signature_stems,
|
|
20
|
+
)
|
|
21
|
+
from sarj_python_lint.rules._paths import is_generated, is_test_path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# The vocabulary a test docstring spends on *being a test*. Folded into the
|
|
29
|
+
# known-stem set rather than into `_docstrings.STOPWORDS`, so it widens this
|
|
30
|
+
# rule alone and cannot silently move SARJ050/085/086/087.
|
|
31
|
+
_TEST_CEREMONY = (
|
|
32
|
+
"assert", "asserted", "asserts", "behavior", "behaviour", "case", "cases", "check", "checked",
|
|
33
|
+
"checking", "checks", "confirm", "confirmed", "confirms", "correctly", "coverage", "covered", "covers",
|
|
34
|
+
"ensure", "ensured", "ensures", "ensuring", "exercise", "exercises", "expect", "expected", "expects",
|
|
35
|
+
"happy", "integration", "path", "properly", "regression", "scenario", "scenarios", "successful",
|
|
36
|
+
"successfully", "test", "tested", "testing", "tests", "unit", "validate", "validated", "validates",
|
|
37
|
+
"verified", "verifies", "verify", "verifying"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_CEREMONY_STEMS = frozenset(stem(word) for word in _TEST_CEREMONY)
|
|
41
|
+
|
|
42
|
+
# Sections other than the summary are SARJ086/087's subject. A test docstring
|
|
43
|
+
# carrying one is a different artefact and is left whole.
|
|
44
|
+
_SUMMARY_ONLY = frozenset({"summary"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# The keyword singletons. `assert x is None` puts the word "None" on the screen
|
|
48
|
+
# as surely as an identifier does, and `... is None` is the single most common
|
|
49
|
+
# thing a test docstring re-spells.
|
|
50
|
+
_SINGLETONS: dict[object, str] = {None: "none", True: "true", False: "false"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _body_stems(node: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]:
|
|
54
|
+
"""Collect the stemmed word parts of every IDENTIFIER in the test body.
|
|
55
|
+
|
|
56
|
+
Identifiers and keyword singletons only — never string literals. A test body
|
|
57
|
+
is full of prose in strings ("user not found"), and letting those count as
|
|
58
|
+
"the code already says it" is what would turn an explanatory docstring into
|
|
59
|
+
a finding.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
tokens: list[str] = []
|
|
63
|
+
for child in ast.walk(node):
|
|
64
|
+
match child:
|
|
65
|
+
case ast.Name():
|
|
66
|
+
tokens.extend(split_identifier(child.id))
|
|
67
|
+
case ast.Attribute():
|
|
68
|
+
tokens.extend(split_identifier(child.attr))
|
|
69
|
+
case ast.keyword() if child.arg is not None:
|
|
70
|
+
tokens.extend(split_identifier(child.arg))
|
|
71
|
+
case ast.arg():
|
|
72
|
+
tokens.extend(split_identifier(child.arg))
|
|
73
|
+
case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
|
|
74
|
+
tokens.extend(split_identifier(child.name))
|
|
75
|
+
case ast.Constant():
|
|
76
|
+
word = next((w for key, w in _SINGLETONS.items() if child.value is key), None)
|
|
77
|
+
if word is not None:
|
|
78
|
+
tokens.append(word)
|
|
79
|
+
case _:
|
|
80
|
+
continue
|
|
81
|
+
return {stem(token) for token in tokens}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_test(node: ast.FunctionDef | ast.AsyncFunctionDef, class_name: str | None) -> bool:
|
|
85
|
+
if node.name.startswith("test_") or node.name == "test":
|
|
86
|
+
return True
|
|
87
|
+
return class_name is not None and class_name.startswith("Test") and node.name.startswith("test")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RestatedTestDocstring(Rule):
|
|
91
|
+
id: str = "restated-test-docstring"
|
|
92
|
+
code: str = "SARJ088"
|
|
93
|
+
has_evidence: bool = True
|
|
94
|
+
description: str = (
|
|
95
|
+
"Test docstring only re-spells the test's own name and body — delete it; "
|
|
96
|
+
"rename the test if the name does not already say it."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@override
|
|
100
|
+
def check(self, path: Path, source: str) -> list[Diagnostic]:
|
|
101
|
+
if is_generated(path, source) or not is_test_path(path):
|
|
102
|
+
return []
|
|
103
|
+
tree = parse_or_none(path, source)
|
|
104
|
+
if tree is None:
|
|
105
|
+
return []
|
|
106
|
+
diags: list[Diagnostic] = []
|
|
107
|
+
self._walk(tree, None, path, diags)
|
|
108
|
+
return sorted(diags, key=lambda diag: diag.line)
|
|
109
|
+
|
|
110
|
+
def _walk(self, node: ast.AST, class_name: str | None, path: Path, diags: list[Diagnostic]) -> None:
|
|
111
|
+
for child in children(node):
|
|
112
|
+
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
113
|
+
self._check_function(child, class_name, path, diags)
|
|
114
|
+
elif isinstance(child, ast.ClassDef):
|
|
115
|
+
self._check_class(child, path, diags)
|
|
116
|
+
self._walk(child, child.name, path, diags)
|
|
117
|
+
else:
|
|
118
|
+
self._walk(child, class_name, path, diags)
|
|
119
|
+
|
|
120
|
+
def _check_class(self, node: ast.ClassDef, path: Path, diags: list[Diagnostic]) -> None:
|
|
121
|
+
"""Flag a `Test*` class whose docstring only re-spells its name and method names."""
|
|
122
|
+
if not node.name.startswith("Test") or node.bases or node.keywords:
|
|
123
|
+
return
|
|
124
|
+
docstring = ast.get_docstring(node, clean=True)
|
|
125
|
+
if not docstring or not self._is_plain_summary(docstring):
|
|
126
|
+
return
|
|
127
|
+
known = {stem(part) for part in split_identifier(node.name)} | _CEREMONY_STEMS
|
|
128
|
+
for child in node.body:
|
|
129
|
+
if isinstance(child, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
130
|
+
known |= signature_stems(child, node.name)
|
|
131
|
+
if not restates(docstring, known):
|
|
132
|
+
return
|
|
133
|
+
expr = node.body[0]
|
|
134
|
+
diags.append(
|
|
135
|
+
Diagnostic(
|
|
136
|
+
path=path,
|
|
137
|
+
line=expr.lineno,
|
|
138
|
+
col=expr.col_offset + 1,
|
|
139
|
+
code=self.code,
|
|
140
|
+
message=self.description,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _is_plain_summary(docstring: str) -> bool:
|
|
146
|
+
"""Report whether the docstring is a bare summary with nothing protected in it."""
|
|
147
|
+
if frozenset(sections(docstring)) != _SUMMARY_ONLY:
|
|
148
|
+
return False
|
|
149
|
+
return not is_protected(docstring) and not VALUE_MARKER_RE.search(docstring)
|
|
150
|
+
|
|
151
|
+
def _check_function(
|
|
152
|
+
self,
|
|
153
|
+
node: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
154
|
+
class_name: str | None,
|
|
155
|
+
path: Path,
|
|
156
|
+
diags: list[Diagnostic],
|
|
157
|
+
) -> None:
|
|
158
|
+
if not _is_test(node, class_name):
|
|
159
|
+
return
|
|
160
|
+
docstring = ast.get_docstring(node, clean=True)
|
|
161
|
+
if not docstring or not self._is_plain_summary(docstring):
|
|
162
|
+
return
|
|
163
|
+
known = signature_stems(node, class_name) | _body_stems(node) | _CEREMONY_STEMS
|
|
164
|
+
if not restates(docstring, known):
|
|
165
|
+
return
|
|
166
|
+
expr = node.body[0]
|
|
167
|
+
diags.append(
|
|
168
|
+
Diagnostic(
|
|
169
|
+
path=path,
|
|
170
|
+
line=expr.lineno,
|
|
171
|
+
col=expr.col_offset + 1,
|
|
172
|
+
code=self.code,
|
|
173
|
+
message=self.description,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
@@ -32,6 +32,56 @@ _RAISES_TOKEN_RE = re.compile(r"(^|_)(raises|warns|deprecated_call)", re.IGNOREC
|
|
|
32
32
|
|
|
33
33
|
_RAISES_NAMES = frozenset({"raises", "warns", "fail"})
|
|
34
34
|
|
|
35
|
+
# Library assertion helpers whose names contain none of the four tokens above.
|
|
36
|
+
# The name heuristic is a good default and it has a blind spot: a widely used
|
|
37
|
+
# assertion API is free to spell itself without the word "assert".
|
|
38
|
+
#
|
|
39
|
+
# MEASURED, 2026-07-31, over 39,893 content-deduplicated `.py` files: 4,386 of
|
|
40
|
+
# this rule's 7,352 findings — 59.7% — were on test functions whose body calls
|
|
41
|
+
# one of these, i.e. tests that assert perfectly well through a helper this rule
|
|
42
|
+
# could not see. Three read at source (`test_run_without_stepwise`,
|
|
43
|
+
# `test_repr_params_unknown_list`, `test_terminal_report_failedfirst`) are
|
|
44
|
+
# assertion-complete; the flag was wrong in each.
|
|
45
|
+
#
|
|
46
|
+
# Two vocabularies, both exact names rather than a pattern, because the shapes
|
|
47
|
+
# are too generic to match loosely:
|
|
48
|
+
#
|
|
49
|
+
# * pytest's own `LineMatcher` (`_pytest.pytester`). `result.stdout
|
|
50
|
+
# .fnmatch_lines([...])` RAISES `Failed` on a mismatch — it is the canonical
|
|
51
|
+
# way to assert on CLI output, and any repo that tests a console script with
|
|
52
|
+
# the `pytester` fixture uses it. 438 findings.
|
|
53
|
+
# * `sqlalchemy.testing.assertions`, whose trailing underscore exists to dodge
|
|
54
|
+
# Python keywords (`is_`, `in_`). 3,948 findings.
|
|
55
|
+
#
|
|
56
|
+
# Kept as a closed set: a wildcard for "ends in an underscore" or "starts with
|
|
57
|
+
# eq" would swallow unrelated user functions, and the whole point of this rule
|
|
58
|
+
# is that a test which really does assert nothing stays flagged.
|
|
59
|
+
_LIBRARY_ASSERTION_NAMES = frozenset({
|
|
60
|
+
# _pytest.pytester.LineMatcher
|
|
61
|
+
"fnmatch_lines",
|
|
62
|
+
"fnmatch_lines_random",
|
|
63
|
+
"no_fnmatch_line",
|
|
64
|
+
"no_re_match_line",
|
|
65
|
+
"re_match_lines",
|
|
66
|
+
"re_match_lines_random",
|
|
67
|
+
# sqlalchemy.testing.assertions
|
|
68
|
+
"eq_",
|
|
69
|
+
"eq_ignore_whitespace",
|
|
70
|
+
"eq_regex",
|
|
71
|
+
"in_",
|
|
72
|
+
"is_",
|
|
73
|
+
"is_false",
|
|
74
|
+
"is_instance_of",
|
|
75
|
+
"is_none",
|
|
76
|
+
"is_not",
|
|
77
|
+
"is_not_",
|
|
78
|
+
"is_not_none",
|
|
79
|
+
"is_true",
|
|
80
|
+
"ne_",
|
|
81
|
+
"not_in",
|
|
82
|
+
"not_in_",
|
|
83
|
+
})
|
|
84
|
+
|
|
35
85
|
_TEST_PREFIX = "test_"
|
|
36
86
|
|
|
37
87
|
# Fluent verification DSLs reached through an attribute rather than a call name.
|
|
@@ -307,7 +357,9 @@ def _names_verification(func: ast.expr) -> bool:
|
|
|
307
357
|
|
|
308
358
|
|
|
309
359
|
def _reads_as_verification(name: str) -> bool:
|
|
310
|
-
return
|
|
360
|
+
return name in _LIBRARY_ASSERTION_NAMES or bool(
|
|
361
|
+
_ASSERTION_NAME_RE.search(name) or _RAISES_TOKEN_RE.search(name)
|
|
362
|
+
)
|
|
311
363
|
|
|
312
364
|
|
|
313
365
|
def _chain_has_fluent_marker(node: ast.expr) -> bool:
|
|
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.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/_ast_index.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/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
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/mock_without_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_comment_cruft.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/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.38.0 → sarj_python_lint-0.40.0}/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.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_secret_in_log.py
RENAMED
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/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.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/no_stdlib_logging.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/over_mocked_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/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.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/prefer_or_pattern.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sarj_python_lint-0.38.0 → sarj_python_lint-0.40.0}/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.38.0 → sarj_python_lint-0.40.0}/src/sarj_python_lint/rules/unused_mock_setup.py
RENAMED
|
File without changes
|
|
File without changes
|