python-linters 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
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.
- python_linters/config_check.py +40 -13
- python_linters/lint_report.py +93 -8
- python_linters/references/ruff.reference.toml +35 -19
- python_linters/references/ty.reference.toml +31 -19
- {python_linters-0.2.0.dist-info → python_linters-0.2.2.dist-info}/METADATA +6 -6
- python_linters-0.2.2.dist-info/RECORD +13 -0
- {python_linters-0.2.0.dist-info → python_linters-0.2.2.dist-info}/WHEEL +1 -1
- python_linters-0.2.0.dist-info/RECORD +0 -13
- {python_linters-0.2.0.dist-info → python_linters-0.2.2.dist-info}/entry_points.txt +0 -0
python_linters/config_check.py
CHANGED
|
@@ -169,7 +169,9 @@ def _covers_rule(
|
|
|
169
169
|
return False
|
|
170
170
|
if selector in meta_prefixes:
|
|
171
171
|
return True
|
|
172
|
-
selector_linters = {
|
|
172
|
+
selector_linters = {
|
|
173
|
+
r["linter"] for r in rules if r["code"] and r["code"].startswith(selector)
|
|
174
|
+
}
|
|
173
175
|
return selector_linters == {linter}
|
|
174
176
|
|
|
175
177
|
|
|
@@ -221,9 +223,14 @@ def _get_new_categories(
|
|
|
221
223
|
for prefix, linter in sorted(categories.items()):
|
|
222
224
|
if any(_covers(k, prefix, category_linters, meta_prefixes) for k in known):
|
|
223
225
|
continue
|
|
224
|
-
prefix_rules = [
|
|
226
|
+
prefix_rules = [
|
|
227
|
+
r for r in rules if r["code"] and r["code"].rstrip(string.digits) == prefix
|
|
228
|
+
]
|
|
225
229
|
if all(
|
|
226
|
-
any(
|
|
230
|
+
any(
|
|
231
|
+
_covers_rule(k, r["code"], r["linter"], rules, meta_prefixes)
|
|
232
|
+
for k in known
|
|
233
|
+
)
|
|
227
234
|
for r in prefix_rules
|
|
228
235
|
):
|
|
229
236
|
continue
|
|
@@ -255,9 +262,13 @@ def check_ruff_config(toml_path: Path) -> RuffConfigCheck:
|
|
|
255
262
|
commented = _parse_toml_comments(toml_path)
|
|
256
263
|
|
|
257
264
|
known = selected | commented
|
|
258
|
-
meta_prefixes = {"PL"} | {
|
|
265
|
+
meta_prefixes = {"PL"} | {
|
|
266
|
+
p for p, linters in category_linters.items() if len(linters) > 1
|
|
267
|
+
}
|
|
259
268
|
|
|
260
|
-
new_categories = _get_new_categories(
|
|
269
|
+
new_categories = _get_new_categories(
|
|
270
|
+
rules, categories, category_linters, known, meta_prefixes
|
|
271
|
+
)
|
|
261
272
|
removed = _get_removed(rules, selected)
|
|
262
273
|
|
|
263
274
|
untriaged_codeless = [n for n in _get_codeless_rules(rules) if n not in known]
|
|
@@ -293,24 +304,36 @@ def format_ruff_report(toml_path: Path, check: RuffConfigCheck) -> str:
|
|
|
293
304
|
if check.new_categories:
|
|
294
305
|
lines.append(f"NEW categories not yet triaged ({len(check.new_categories)}):")
|
|
295
306
|
lines.append(" Add to select or comment out with '# excluded' in ruff.toml:\n")
|
|
296
|
-
lines.extend(
|
|
307
|
+
lines.extend(
|
|
308
|
+
f' "{prefix}",\t# {linter}' for prefix, linter in check.new_categories
|
|
309
|
+
)
|
|
297
310
|
lines.append("")
|
|
298
311
|
|
|
299
312
|
if check.removed:
|
|
300
|
-
lines.append(
|
|
313
|
+
lines.append(
|
|
314
|
+
f"REMOVED/RENAMED categories still in select ({len(check.removed)}):"
|
|
315
|
+
)
|
|
301
316
|
lines.extend(f" {code}" for code in check.removed)
|
|
302
317
|
lines.append("")
|
|
303
318
|
|
|
304
319
|
if check.untriaged_codeless:
|
|
305
|
-
lines.append(
|
|
306
|
-
|
|
320
|
+
lines.append(
|
|
321
|
+
f"Code-less rules not yet triaged ({len(check.untriaged_codeless)}):"
|
|
322
|
+
)
|
|
323
|
+
lines.append(
|
|
324
|
+
" Add to select by name or comment out with '# excluded' in ruff.toml:\n"
|
|
325
|
+
)
|
|
307
326
|
lines.extend(f' "{name}",' for name in check.untriaged_codeless)
|
|
308
327
|
lines.append("")
|
|
309
328
|
|
|
310
329
|
if check.untriaged_preview:
|
|
311
330
|
lines.append(f"Preview rules not yet triaged ({len(check.untriaged_preview)}):")
|
|
312
|
-
lines.append(
|
|
313
|
-
|
|
331
|
+
lines.append(
|
|
332
|
+
" Add to select by name or cover with an '# excluded' line in ruff.toml:\n"
|
|
333
|
+
)
|
|
334
|
+
lines.extend(
|
|
335
|
+
f' "{name}",\t# {code}' for code, name in check.untriaged_preview
|
|
336
|
+
)
|
|
314
337
|
lines.append("")
|
|
315
338
|
|
|
316
339
|
if check.is_clean:
|
|
@@ -395,7 +418,9 @@ def format_ty_report(config_path: Path, check: TyConfigCheck) -> str:
|
|
|
395
418
|
]
|
|
396
419
|
|
|
397
420
|
if check.untriaged:
|
|
398
|
-
lines.append(
|
|
421
|
+
lines.append(
|
|
422
|
+
f"UNTRIAGED rules ty knows that the config omits ({len(check.untriaged)}):"
|
|
423
|
+
)
|
|
399
424
|
lines.extend(
|
|
400
425
|
f' {name} = "???" # default: {check.default_levels[name]}'
|
|
401
426
|
for name in check.untriaged
|
|
@@ -403,7 +428,9 @@ def format_ty_report(config_path: Path, check: TyConfigCheck) -> str:
|
|
|
403
428
|
lines.append("")
|
|
404
429
|
|
|
405
430
|
if check.stale:
|
|
406
|
-
lines.append(
|
|
431
|
+
lines.append(
|
|
432
|
+
f"STALE rules named in config that ty no longer knows ({len(check.stale)}):"
|
|
433
|
+
)
|
|
407
434
|
lines.extend(f" {name}" for name in check.stale)
|
|
408
435
|
lines.append("")
|
|
409
436
|
|
python_linters/lint_report.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Run ruff, ty and pymodularity and group their findings by rule.
|
|
1
|
+
"""Run ruff, ruff format --check, ty and pymodularity and group their findings by rule.
|
|
2
2
|
|
|
3
3
|
Ported from lint_grouped.py (quality-and-security-tools howtos), minus eslint.
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ Output structure:
|
|
|
7
7
|
"project": "/abs/path",
|
|
8
8
|
"summary": {
|
|
9
9
|
"ruff": {"total": 100, "rules": 12},
|
|
10
|
+
"ruff-format": {"total": 2, "rules": 1},
|
|
10
11
|
"ty": {"total": 50, "rules": 8},
|
|
11
12
|
"pymodularity": {"total": 5, "rules": 2}
|
|
12
13
|
},
|
|
@@ -17,6 +18,12 @@ Output structure:
|
|
|
17
18
|
"locations": [{"file": "rel/path.py", "line": 12, "col": 5, "message": "..."}, ...]
|
|
18
19
|
}
|
|
19
20
|
},
|
|
21
|
+
"ruff-format": {
|
|
22
|
+
"unformatted": {
|
|
23
|
+
"count": 2,
|
|
24
|
+
"locations": [{"file": "rel/path.py", "line": 1, "col": 1, "message": "would be reformatted"}, ...]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
20
27
|
"ty": {
|
|
21
28
|
"invalid-return-type": {
|
|
22
29
|
"severity": "major",
|
|
@@ -40,18 +47,23 @@ from __future__ import annotations
|
|
|
40
47
|
|
|
41
48
|
import json
|
|
42
49
|
import os
|
|
50
|
+
import re
|
|
43
51
|
import sys
|
|
44
52
|
from dataclasses import dataclass, field
|
|
45
53
|
from pathlib import Path
|
|
46
54
|
from typing import TYPE_CHECKING
|
|
47
55
|
|
|
48
|
-
from python_linters.tool_errors import run_checked
|
|
56
|
+
from python_linters.tool_errors import ToolFailedError, run_checked
|
|
49
57
|
|
|
50
58
|
if TYPE_CHECKING:
|
|
51
59
|
from collections.abc import Iterable
|
|
52
60
|
|
|
53
61
|
_TY_SEVERITY_MAP = {"major": "error", "minor": "warning", "info": "info"}
|
|
54
62
|
|
|
63
|
+
# Matches ruff format --check's per-file location line, e.g.:
|
|
64
|
+
# --> my_package/main_module.py:1:2
|
|
65
|
+
_RUFF_FORMAT_LOCATION_RE = re.compile(r"^ --> (?P<file>.+):(?P<line>\d+):(?P<col>\d+)$")
|
|
66
|
+
|
|
55
67
|
|
|
56
68
|
@dataclass
|
|
57
69
|
class Location:
|
|
@@ -61,7 +73,12 @@ class Location:
|
|
|
61
73
|
message: str
|
|
62
74
|
|
|
63
75
|
def to_dict(self) -> dict[str, object]:
|
|
64
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
"file": self.file,
|
|
78
|
+
"line": self.line,
|
|
79
|
+
"col": self.col,
|
|
80
|
+
"message": self.message,
|
|
81
|
+
}
|
|
65
82
|
|
|
66
83
|
|
|
67
84
|
@dataclass
|
|
@@ -115,7 +132,7 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
|
|
|
115
132
|
# Resolution: the python-linters environment's own pinned ruff, run as
|
|
116
133
|
# `sys.executable -m ruff` so it never falls through to PATH. In
|
|
117
134
|
# dev-dependency mode that environment is the linted project's venv, so
|
|
118
|
-
# ruff==0.17.
|
|
135
|
+
# ruff==0.17.5 (pinned in python-linters' own pyproject.toml) is what
|
|
119
136
|
# actually runs — never a PATH ruff of another version, which lints
|
|
120
137
|
# with other rules and may refuse the reference config.
|
|
121
138
|
#
|
|
@@ -123,7 +140,16 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
|
|
|
123
140
|
# selector, bad config, ...). Empty stdout on exit 2 must never read
|
|
124
141
|
# as "0 findings" — `run_checked` raises instead of returning in that case.
|
|
125
142
|
result = run_checked(
|
|
126
|
-
[
|
|
143
|
+
[
|
|
144
|
+
sys.executable,
|
|
145
|
+
"-m",
|
|
146
|
+
"ruff",
|
|
147
|
+
"check",
|
|
148
|
+
"--no-cache",
|
|
149
|
+
"--output-format",
|
|
150
|
+
"json",
|
|
151
|
+
".",
|
|
152
|
+
],
|
|
127
153
|
project_path,
|
|
128
154
|
"ruff",
|
|
129
155
|
ok_codes=(0, 1),
|
|
@@ -147,6 +173,53 @@ def run_ruff(project_path: Path) -> dict[str, FindingGroup]:
|
|
|
147
173
|
return _group_findings(findings())
|
|
148
174
|
|
|
149
175
|
|
|
176
|
+
def run_ruff_format(project_path: Path) -> dict[str, FindingGroup]:
|
|
177
|
+
# Resolution: same as run_ruff above — `sys.executable -m ruff`, never
|
|
178
|
+
# PATH, `--no-cache` so a read-only linted project tree is never an
|
|
179
|
+
# issue.
|
|
180
|
+
#
|
|
181
|
+
# `ruff format --check` has no JSON output mode (unlike `ruff check`):
|
|
182
|
+
# `ruff format --help` lists `--output-format` only under `check`, not
|
|
183
|
+
# under `format`. Its human-readable stdout does carry one `-->
|
|
184
|
+
# path:line:col` line per unformatted file, which is parsed below
|
|
185
|
+
# instead.
|
|
186
|
+
#
|
|
187
|
+
# ruff format's exit codes: 0 all formatted, 1 some file would be
|
|
188
|
+
# reformatted, 2 tool failure (bad config, crash, ...). A failure must
|
|
189
|
+
# never read as "0 findings" — `run_checked` raises instead of
|
|
190
|
+
# returning in that case.
|
|
191
|
+
result = run_checked(
|
|
192
|
+
[sys.executable, "-m", "ruff", "format", "--check", "--no-cache", "."],
|
|
193
|
+
project_path,
|
|
194
|
+
"ruff-format",
|
|
195
|
+
ok_codes=(0, 1),
|
|
196
|
+
)
|
|
197
|
+
if not result.stdout.strip():
|
|
198
|
+
return {}
|
|
199
|
+
|
|
200
|
+
def findings() -> Iterable[_Finding]:
|
|
201
|
+
for line in result.stdout.splitlines():
|
|
202
|
+
match = _RUFF_FORMAT_LOCATION_RE.match(line)
|
|
203
|
+
if match is None:
|
|
204
|
+
continue
|
|
205
|
+
location = Location(
|
|
206
|
+
file=_relative_to(match["file"], project_path),
|
|
207
|
+
line=int(match["line"]),
|
|
208
|
+
col=int(match["col"]),
|
|
209
|
+
message="would be reformatted",
|
|
210
|
+
)
|
|
211
|
+
yield "unformatted", None, None, location
|
|
212
|
+
|
|
213
|
+
groups = _group_findings(findings())
|
|
214
|
+
# Exit 1 with nothing parsed means ruff changed its text output: fail
|
|
215
|
+
# loudly instead of reporting a clean project.
|
|
216
|
+
if result.returncode == 1 and not groups:
|
|
217
|
+
detail = f"exit 1 but no ' --> path:line:col' line parsed:\n{result.stdout}"
|
|
218
|
+
tool = "ruff-format"
|
|
219
|
+
raise ToolFailedError(tool, detail)
|
|
220
|
+
return groups
|
|
221
|
+
|
|
222
|
+
|
|
150
223
|
def run_ty(project_path: Path) -> dict[str, FindingGroup]:
|
|
151
224
|
# Resolution: `sys.executable -m ty` from the python-linters
|
|
152
225
|
# environment, never a bare `ty` entry point (that one falls through to
|
|
@@ -226,8 +299,15 @@ def run_pymodularity(project_path: Path) -> dict[str, FindingGroup]:
|
|
|
226
299
|
# 2 `uv` missing or an uncaught exception (docs/rules.md "JSON output").
|
|
227
300
|
# A failure must never read as "0 findings".
|
|
228
301
|
result = run_checked(
|
|
229
|
-
[
|
|
230
|
-
|
|
302
|
+
[
|
|
303
|
+
sys.executable,
|
|
304
|
+
"-m",
|
|
305
|
+
"pymodularity",
|
|
306
|
+
"check",
|
|
307
|
+
"--output-format",
|
|
308
|
+
"json",
|
|
309
|
+
str(project_path),
|
|
310
|
+
],
|
|
231
311
|
project_path,
|
|
232
312
|
"pymodularity",
|
|
233
313
|
ok_codes=(0, 1),
|
|
@@ -259,7 +339,12 @@ def build_report(project_path: Path) -> tuple[dict[str, object], int]:
|
|
|
259
339
|
sections: dict[str, object] = {}
|
|
260
340
|
grand_total = 0
|
|
261
341
|
|
|
262
|
-
for tool, run in (
|
|
342
|
+
for tool, run in (
|
|
343
|
+
("ruff", run_ruff),
|
|
344
|
+
("ruff-format", run_ruff_format),
|
|
345
|
+
("ty", run_ty),
|
|
346
|
+
("pymodularity", run_pymodularity),
|
|
347
|
+
):
|
|
263
348
|
print(f"Running {tool} on {project_path} ...", file=sys.stderr)
|
|
264
349
|
violations = run(project_path)
|
|
265
350
|
total = sum(group.count for group in violations.values())
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# 5. flake8-type-checking — list your runtime-evaluated base classes and
|
|
13
13
|
# decorators (pydantic, FastAPI preset)
|
|
14
14
|
#
|
|
15
|
-
# Last triaged against: ruff 0.17.
|
|
15
|
+
# Last triaged against: ruff 0.17.5
|
|
16
16
|
# Rule names in `select`/`ignore` and in `# ruff: ignore[...]` comments need
|
|
17
17
|
# `preview = true`: without it ruff rejects the config and treats name-based
|
|
18
18
|
# suppression comments as inert.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
# Older ruff fails on the first rule name it does not know, with a cryptic
|
|
22
22
|
# `Unknown rule selector`. This guard names the version instead.
|
|
23
|
-
required-version = ">=0.17.
|
|
23
|
+
required-version = ">=0.17.5"
|
|
24
24
|
|
|
25
25
|
target-version = "py312"
|
|
26
26
|
|
|
@@ -94,6 +94,8 @@ select = [
|
|
|
94
94
|
|
|
95
95
|
# ── Security ──
|
|
96
96
|
"S", # flake8-bandit — security antipatterns (hardcoded passwords, exec, …)
|
|
97
|
+
"tarfile-ignore-zeros-true", # code-less rule: `ignore_zeros=True` reads past the
|
|
98
|
+
# end-of-archive marker, so appended members get extracted too
|
|
97
99
|
"EXE", # flake8-executable — shebang correctness
|
|
98
100
|
|
|
99
101
|
# ── Error handling ──
|
|
@@ -170,11 +172,11 @@ select = [
|
|
|
170
172
|
|
|
171
173
|
# ── Preview rules, selected by name ──────────────────────────────────
|
|
172
174
|
# `explicit-preview-rules = true`: a category select above turns on only its
|
|
173
|
-
# stable rules. Each preview rule below was triaged on ruff 0.17.
|
|
175
|
+
# stable rules. Each preview rule below was triaged on ruff 0.17.5.
|
|
174
176
|
# A preview rule in a selected category that is neither listed
|
|
175
177
|
# here nor covered by an `# excluded` line below is untriaged.
|
|
176
178
|
|
|
177
|
-
# Ruff-specific (
|
|
179
|
+
# Ruff-specific (26)
|
|
178
180
|
# bugs: the code runs but does the wrong thing
|
|
179
181
|
"missing-f-string-syntax", # `"{x}"` without `f` prints the braces
|
|
180
182
|
"unraw-re-pattern", # `"\b"` in a non-raw pattern is a backspace, not a word boundary, and nothing else warns
|
|
@@ -204,13 +206,15 @@ select = [
|
|
|
204
206
|
"noqa-comments",
|
|
205
207
|
"rule-codes-in-suppression-comments",
|
|
206
208
|
"rule-codes-in-selectors",
|
|
209
|
+
"renamed-rule", # a config selector that only works through a rename redirect
|
|
207
210
|
|
|
208
|
-
# Pylint (
|
|
211
|
+
# Pylint (19)
|
|
209
212
|
# bugs: runtime errors or silently lost behavior
|
|
210
213
|
"dict-iter-missing-items", # `for k, v in d` unpacks keys, not items
|
|
211
214
|
"modified-iterating-set", # RuntimeError
|
|
212
215
|
"redefined-slots-in-subclass", # makes the base class slot unreachable
|
|
213
216
|
"bad-dunder-method-name", # a misspelled dunder is never called
|
|
217
|
+
"no-method-argument", # a method without `self`/`cls` raises TypeError when called on an instance
|
|
214
218
|
"unspecified-encoding", # `open()` without encoding uses the platform locale
|
|
215
219
|
# size and nesting limits, every threshold set under [lint.pylint]
|
|
216
220
|
"too-many-public-methods",
|
|
@@ -262,9 +266,11 @@ select = [
|
|
|
262
266
|
"deprecated-abc-decorator", # deprecated since 3.3
|
|
263
267
|
"context-manager-iterator", # @contextmanager needs Generator, Iterator lacks `throw`
|
|
264
268
|
|
|
265
|
-
# flake8-async (
|
|
266
|
-
|
|
267
|
-
|
|
269
|
+
# flake8-async (2)
|
|
270
|
+
"yield-in-context-manager-in-async-generator", # the context manager's cleanup waits until
|
|
271
|
+
# the generator closes, when `await` is no longer allowed
|
|
272
|
+
"pytest-raises-exception-group", # `pytest.RaisesGroup` checks the grouped exceptions,
|
|
273
|
+
# `pytest.raises(ExceptionGroup)` only the wrapper
|
|
268
274
|
|
|
269
275
|
# flake8-pytest-style (1): `pytest.warns()` without a warning fails at runtime
|
|
270
276
|
"pytest-warns-without-warning",
|
|
@@ -321,9 +327,9 @@ ignore = [
|
|
|
321
327
|
# ANN401 (typing.Any disallowed) stays ON: with basedpyright dropped
|
|
322
328
|
# in favor of ty, ANN401 is the gate's only coverage for explicit
|
|
323
329
|
# Any (basedpyright's reportExplicitAny): ty has no equivalent rule.
|
|
324
|
-
# missing
|
|
325
|
-
#
|
|
326
|
-
|
|
330
|
+
# missing-return-type-special-method stays ON: ty types the target of
|
|
331
|
+
# `with X() as x` as Unknown when `__enter__` has no return type. ruff
|
|
332
|
+
# fixes `__init__ -> None` and `__str__ -> str` itself (safe fix).
|
|
327
333
|
"missing-type-args", # missing type on *args — typically pass-through,
|
|
328
334
|
# type depends on caller
|
|
329
335
|
"missing-type-kwargs", # missing type on **kwargs — same reasoning as
|
|
@@ -339,18 +345,18 @@ ignore = [
|
|
|
339
345
|
# than max_snr
|
|
340
346
|
|
|
341
347
|
# ── Code style ──
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
348
|
+
# lambda-assignment stays ON: a lambda cannot annotate its parameters,
|
|
349
|
+
# so ty types `strip = lambda x: x.strip()` as `(x) -> Unknown` and
|
|
350
|
+
# accepts `strip(1)`.
|
|
345
351
|
"in-dict-keys", # use `key in dict` — `key in dict.keys()` is more
|
|
346
352
|
# explicit about intent; Python Zen says explicit >
|
|
347
353
|
# implicit
|
|
348
354
|
"superfluous-else-return", # the else branch makes the two paths visually
|
|
349
355
|
# symmetric and easier to read
|
|
350
356
|
"superfluous-else-raise", # same reasoning as superfluous-else-return
|
|
351
|
-
# super
|
|
352
|
-
#
|
|
353
|
-
|
|
357
|
+
# super-call-with-parameters stays ON: zero-argument super() fails in a
|
|
358
|
+
# dataclass(slots=True) before Python 3.14, but ruff skips slots
|
|
359
|
+
# dataclasses for this rule (checked on ruff 0.17.3).
|
|
354
360
|
"compare-to-empty-string", # `s == ""` is more explicit than `not s` (the
|
|
355
361
|
# latter also matches None, 0, [])
|
|
356
362
|
|
|
@@ -391,11 +397,16 @@ ignore = [
|
|
|
391
397
|
# callers, so its method count is not an
|
|
392
398
|
# interface
|
|
393
399
|
# missing return type — test functions always return None
|
|
394
|
-
"missing-return-type-
|
|
400
|
+
"missing-return-type-public-function",
|
|
395
401
|
"import-private-name", # testing internal functions is legitimate
|
|
396
402
|
"unused-async", # async test doubles mirror the awaited signature they replace
|
|
397
403
|
"float-equality-comparison", # asserting an exactly representable result is correct
|
|
398
404
|
"too-many-locals", # an integration test sets up many real collaborators
|
|
405
|
+
"missing-type-function-argument", # pytest fixtures: the fixture defines the type
|
|
406
|
+
"missing-return-type-private-function", # helpers and fakes local to a test module
|
|
407
|
+
"missing-return-type-special-method", # `__init__` / `__enter__` on test doubles
|
|
408
|
+
"import-outside-top-level", # imports inside a test patch or isolate the import
|
|
409
|
+
"private-member-access", # tests inspect internal state on purpose
|
|
399
410
|
]
|
|
400
411
|
"test_*.py" = [
|
|
401
412
|
"assert",
|
|
@@ -403,12 +414,17 @@ ignore = [
|
|
|
403
414
|
"magic-value-comparison",
|
|
404
415
|
"no-self-use",
|
|
405
416
|
"too-many-public-methods",
|
|
406
|
-
"missing-return-type-
|
|
417
|
+
"missing-return-type-public-function",
|
|
407
418
|
"implicit-namespace-package", # standalone test scripts
|
|
408
419
|
"import-private-name",
|
|
409
420
|
"unused-async",
|
|
410
421
|
"float-equality-comparison",
|
|
411
422
|
"too-many-locals",
|
|
423
|
+
"missing-type-function-argument",
|
|
424
|
+
"missing-return-type-private-function",
|
|
425
|
+
"missing-return-type-special-method",
|
|
426
|
+
"import-outside-top-level",
|
|
427
|
+
"private-member-access",
|
|
412
428
|
]
|
|
413
429
|
|
|
414
430
|
# ── isort ───────────────────────────────────────────────────────────────────
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# ty Reference Configuration
|
|
3
3
|
# ============================================================================
|
|
4
4
|
#
|
|
5
|
-
# Canonical rule table for new Python projects. Every rule ty 0.0.
|
|
5
|
+
# Canonical rule table for new Python projects. Every rule ty 0.0.103 knows is
|
|
6
6
|
# listed below, one line each, with no `all = "error"` base. Each line is
|
|
7
7
|
# "error" or "ignore". A "warn" setting is never used, because a warning
|
|
8
8
|
# does not fail a CI gate, so a green run hides it.
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
# - into `pyproject.toml` under `[tool.ty.rules]` (rename the table header,
|
|
17
17
|
# keep every line).
|
|
18
18
|
#
|
|
19
|
-
# Last triaged against: ty 0.0.
|
|
20
|
-
#
|
|
19
|
+
# Last triaged against: ty 0.0.103 (152 rules: 107 error-by-default,
|
|
20
|
+
# 27 warn-by-default, 18 ignore-by-default).
|
|
21
21
|
#
|
|
22
22
|
# Re-check the copied table after every ty version bump:
|
|
23
23
|
# python-linters check-config ty <path/to/ty.toml>
|
|
@@ -51,7 +51,7 @@ redundant-condition-strict = "ignore"
|
|
|
51
51
|
# `re.Match.group()`) goes unflagged.
|
|
52
52
|
# Re-triage on each ty bump: ty is 0.0.x and its inference changes.
|
|
53
53
|
unsound-assignment = "ignore"
|
|
54
|
-
unsound-return
|
|
54
|
+
unsound-return = "ignore"
|
|
55
55
|
unsound-yield = "ignore"
|
|
56
56
|
|
|
57
57
|
# not applicable: checks classes built with `type()`, which the doctrine
|
|
@@ -80,66 +80,69 @@ truthiness-test-of-none-union = "error"
|
|
|
80
80
|
# --- Enforced rules: on by default in ty, alphabetical ----------------------
|
|
81
81
|
|
|
82
82
|
abstract-and-final-method = "error"
|
|
83
|
+
abstract-class-instantiation = "error"
|
|
84
|
+
abstract-method-call = "error"
|
|
83
85
|
abstract-method-in-final-class = "error"
|
|
84
86
|
ambiguous-protocol-member = "error"
|
|
85
87
|
assert-type-unspellable-subtype = "error"
|
|
86
|
-
call-
|
|
87
|
-
call-non-callable = "error"
|
|
88
|
-
call-top-callable = "error"
|
|
88
|
+
call-to-function-with-unknown-signature = "error"
|
|
89
89
|
conflicting-declarations = "error"
|
|
90
90
|
conflicting-metaclass = "error"
|
|
91
91
|
cyclic-class-definition = "error"
|
|
92
92
|
cyclic-type-alias-definition = "error"
|
|
93
|
-
dataclass-field-order = "error"
|
|
94
93
|
deprecated = "error"
|
|
95
94
|
duplicate-base = "error"
|
|
96
95
|
duplicate-kw-only = "error"
|
|
97
|
-
empty-body = "error"
|
|
98
96
|
escape-character-in-forward-annotation = "error"
|
|
99
97
|
experimental-syntax = "error"
|
|
100
98
|
final-on-non-method = "error"
|
|
101
99
|
final-without-value = "error"
|
|
102
100
|
ignore-comment-unknown-rule = "error"
|
|
103
|
-
implicit-
|
|
101
|
+
implicit-string-concatenation-in-annotation = "error"
|
|
102
|
+
import-not-found = "error"
|
|
103
|
+
incompatible-attribute-override = "error"
|
|
104
|
+
incompatible-method-override = "error"
|
|
105
|
+
incompatible-mutable-override = "error"
|
|
106
|
+
incompatible-property-override = "error"
|
|
104
107
|
inconsistent-mro = "error"
|
|
108
|
+
inconsistent-overload-decorator = "error"
|
|
109
|
+
inconsistent-overload-implementation = "error"
|
|
105
110
|
index-out-of-bounds = "error"
|
|
106
111
|
ineffective-final = "error"
|
|
107
112
|
instance-layout-conflict = "error"
|
|
108
113
|
invalid-argument-type = "error"
|
|
109
114
|
invalid-assignment = "error"
|
|
110
115
|
invalid-attribute-access = "error"
|
|
111
|
-
invalid-attribute-override = "error"
|
|
112
116
|
invalid-await = "error"
|
|
113
117
|
invalid-base = "error"
|
|
114
118
|
invalid-context-manager = "error"
|
|
115
119
|
invalid-dataclass = "error"
|
|
120
|
+
invalid-dataclass-field-order = "error"
|
|
116
121
|
invalid-dataclass-override = "error"
|
|
117
122
|
invalid-declaration = "error"
|
|
118
123
|
invalid-enum-member-annotation = "error"
|
|
119
124
|
invalid-exception-caught = "error"
|
|
120
125
|
invalid-explicit-override = "error"
|
|
126
|
+
invalid-explicit-return = "error"
|
|
121
127
|
invalid-frozen-dataclass-subclass = "error"
|
|
128
|
+
invalid-generator-return-type = "error"
|
|
122
129
|
invalid-generic-class = "error"
|
|
123
130
|
invalid-generic-enum = "error"
|
|
124
131
|
invalid-ignore-comment = "error"
|
|
132
|
+
invalid-implicit-return = "error"
|
|
133
|
+
invalid-implicit-return-in-function-with-stub-body = "error"
|
|
125
134
|
invalid-init-type-variable = "error"
|
|
126
|
-
invalid-key = "error"
|
|
127
135
|
invalid-legacy-type-variable = "error"
|
|
128
136
|
invalid-match-pattern = "error"
|
|
129
137
|
invalid-metaclass = "error"
|
|
130
|
-
invalid-method-override = "error"
|
|
131
138
|
invalid-module-getattr-call = "error"
|
|
132
|
-
invalid-mutable-override = "error"
|
|
133
139
|
invalid-named-tuple = "error"
|
|
134
140
|
invalid-named-tuple-override = "error"
|
|
135
141
|
invalid-newtype = "error"
|
|
136
|
-
invalid-overload = "error"
|
|
137
142
|
invalid-parameter-default = "error"
|
|
138
143
|
invalid-paramspec = "error"
|
|
139
|
-
invalid-property-type-override = "error"
|
|
140
144
|
invalid-protocol = "error"
|
|
141
145
|
invalid-raise = "error"
|
|
142
|
-
invalid-return-type = "error"
|
|
143
146
|
invalid-super-argument = "error"
|
|
144
147
|
invalid-syntax-in-forward-annotation = "error"
|
|
145
148
|
invalid-total-ordering = "error"
|
|
@@ -153,16 +156,24 @@ invalid-type-variable-constraints = "error"
|
|
|
153
156
|
invalid-type-variable-default = "error"
|
|
154
157
|
invalid-typed-dict-field = "error"
|
|
155
158
|
invalid-typed-dict-header = "error"
|
|
159
|
+
invalid-typed-dict-key = "error"
|
|
156
160
|
invalid-typed-dict-statement = "error"
|
|
157
161
|
invalid-yield = "error"
|
|
158
162
|
isinstance-against-protocol = "error"
|
|
159
163
|
isinstance-against-typed-dict = "error"
|
|
164
|
+
lost-keyword-for-regular-parameter = "error"
|
|
160
165
|
mismatched-type-name = "error"
|
|
166
|
+
misplaced-overload-decorator = "error"
|
|
161
167
|
missing-argument = "error"
|
|
168
|
+
missing-attribute = "error"
|
|
169
|
+
missing-overload-implementation = "error"
|
|
162
170
|
missing-slot = "error"
|
|
163
171
|
missing-typed-dict-key = "error"
|
|
172
|
+
missing-union-attribute = "error"
|
|
164
173
|
no-matching-overload = "error"
|
|
165
174
|
non-callable-init-subclass = "error"
|
|
175
|
+
non-literal-typed-dict-key = "error"
|
|
176
|
+
not-callable = "error"
|
|
166
177
|
not-iterable = "error"
|
|
167
178
|
not-subscriptable = "error"
|
|
168
179
|
override-of-final-method = "error"
|
|
@@ -176,7 +187,9 @@ raw-string-type-annotation = "error"
|
|
|
176
187
|
redundant-cast = "error"
|
|
177
188
|
redundant-condition = "error"
|
|
178
189
|
redundant-final-classvar = "error"
|
|
190
|
+
renamed-rule-in-ignore-comment = "error"
|
|
179
191
|
shadowed-type-variable = "error"
|
|
192
|
+
single-overload = "error"
|
|
180
193
|
static-assert-error = "error"
|
|
181
194
|
subclass-of-dataclass-with-order = "error"
|
|
182
195
|
subclass-of-final-class = "error"
|
|
@@ -189,9 +202,8 @@ unavailable-implicit-super-arguments = "error"
|
|
|
189
202
|
unbound-type-variable = "error"
|
|
190
203
|
undefined-reveal = "error"
|
|
191
204
|
unknown-argument = "error"
|
|
192
|
-
|
|
205
|
+
unknown-typed-dict-constructor-key = "error"
|
|
193
206
|
unresolved-global = "error"
|
|
194
|
-
unresolved-import = "error"
|
|
195
207
|
unresolved-reference = "error"
|
|
196
208
|
unsupported-base = "error"
|
|
197
209
|
unsupported-bool-conversion = "error"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-linters
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Run ruff, ty and pymodularity on a Python project and print one JSON report grouped by rule.
|
|
5
5
|
Author: Tilo Himmelsbach
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Requires-Dist: ruff==0.17.
|
|
8
|
-
Requires-Dist: ty==0.0.
|
|
7
|
+
Requires-Dist: ruff==0.17.5
|
|
8
|
+
Requires-Dist: ty==0.0.103
|
|
9
9
|
Requires-Dist: pymodularity==0.5.0
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
@@ -25,9 +25,9 @@ uv add --dev python-linters
|
|
|
25
25
|
|
|
26
26
|
## Commands
|
|
27
27
|
|
|
28
|
-
- `uv run python-linters lint [path] [--out FILE]` — runs ruff,
|
|
29
|
-
pymodularity (default path: `.`) and prints the
|
|
30
|
-
stdout, or writes it to `FILE` with `--out`.
|
|
28
|
+
- `uv run python-linters lint [path] [--out FILE]` — runs ruff, `ruff
|
|
29
|
+
format --check`, ty and pymodularity (default path: `.`) and prints the
|
|
30
|
+
merged JSON report to stdout, or writes it to `FILE` with `--out`.
|
|
31
31
|
- `uv run python-linters reference ruff|ty` — prints this package's shipped
|
|
32
32
|
reference config for the given tool to stdout. The package never writes
|
|
33
33
|
it into your project — copying it into `ruff.toml`/`ty.toml` (or
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
python_linters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
python_linters/__main__.py,sha256=Xkx-N0S-OWOvqBBgDA0HgCwrW324UrwBEQbR8PUayJI,158
|
|
3
|
+
python_linters/cli_commands.py,sha256=tVneEJJd6bgBrzunto3UJoEJHupwc5wNjMfTGwFCmN0,3122
|
|
4
|
+
python_linters/config_check.py,sha256=gFsI8KVBRkY_x7cREdRE3G51se_sfQAMAacTWR7pdxk,14687
|
|
5
|
+
python_linters/lint_report.py,sha256=TgJEsKCDulBtC8Mx-40emK_ewgGJPkaJNGwgEl-EVBw,13066
|
|
6
|
+
python_linters/reference_files.py,sha256=pD0fGW-PJiV09LFIy9-hZKD7EVmHIUjkE3SfD-NeiI4,764
|
|
7
|
+
python_linters/references/ruff.reference.toml,sha256=Q6kKaeekpeiGcVQoqPDdFfWA-jUij66s8q33EZWWZQ0,29668
|
|
8
|
+
python_linters/references/ty.reference.toml,sha256=AKwhaBhs75QwbtrgqhuyoAYFTQX9Y7ns7TtwF8reQ4E,8244
|
|
9
|
+
python_linters/tool_errors.py,sha256=Fw3byFD0kk8QKJQg85weKB11_CRrnu2h_N2XQId6Abc,1572
|
|
10
|
+
python_linters-0.2.2.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
|
|
11
|
+
python_linters-0.2.2.dist-info/entry_points.txt,sha256=2qemc7DiLCP7wVNw-8KENf-v5L62Jx40tHgVXC0s_FM,65
|
|
12
|
+
python_linters-0.2.2.dist-info/METADATA,sha256=UEZArPlda0mJSwVW2ySXc91uCD8tKW9tsA8_nWjNvU8,1521
|
|
13
|
+
python_linters-0.2.2.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
python_linters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
python_linters/__main__.py,sha256=Xkx-N0S-OWOvqBBgDA0HgCwrW324UrwBEQbR8PUayJI,158
|
|
3
|
-
python_linters/cli_commands.py,sha256=tVneEJJd6bgBrzunto3UJoEJHupwc5wNjMfTGwFCmN0,3122
|
|
4
|
-
python_linters/config_check.py,sha256=Tj92OWNaVcQ81SXiN13pMfbvFctI1iJnzF0sK5JqU6s,14401
|
|
5
|
-
python_linters/lint_report.py,sha256=GfEowFIJx4BOQtSnjya2k31w_wVumscix-NB4yAN0qs,10340
|
|
6
|
-
python_linters/reference_files.py,sha256=pD0fGW-PJiV09LFIy9-hZKD7EVmHIUjkE3SfD-NeiI4,764
|
|
7
|
-
python_linters/references/ruff.reference.toml,sha256=1nzYkfphwmJdUPBo6WIg3DbuVfCvvVDxf17jcX6GIAY,28330
|
|
8
|
-
python_linters/references/ty.reference.toml,sha256=674u63jOImBHrY92lAa27ZcilevZj6GSqxWywp6kQTw,7677
|
|
9
|
-
python_linters/tool_errors.py,sha256=Fw3byFD0kk8QKJQg85weKB11_CRrnu2h_N2XQId6Abc,1572
|
|
10
|
-
python_linters-0.2.0.dist-info/WHEEL,sha256=3NO1oYsF7mANFUWVdyEQLM-zmecczWWEmmF5rEbXrWk,80
|
|
11
|
-
python_linters-0.2.0.dist-info/entry_points.txt,sha256=2qemc7DiLCP7wVNw-8KENf-v5L62Jx40tHgVXC0s_FM,65
|
|
12
|
-
python_linters-0.2.0.dist-info/METADATA,sha256=AocHZGXEE-8jYTT1bwXE9gJJCw--ZwJQ5_xQ2rzNqig,1497
|
|
13
|
-
python_linters-0.2.0.dist-info/RECORD,,
|
|
File without changes
|