python-constricter 0.4.1__cp312-cp312-win_amd64.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.
- constricter/__init__.py +55 -0
- constricter/__main__.py +26 -0
- constricter/_version.py +4 -0
- constricter/cli/__init__.py +2 -0
- constricter/cli/baseline.py +107 -0
- constricter/cli/collecting.py +54 -0
- constricter/cli/command.py +547 -0
- constricter/cli/config.py +322 -0
- constricter/cli/explain.py +104 -0
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/lsp/connection.py +475 -0
- constricter/cli/lsp/edits.py +188 -0
- constricter/cli/lsp/guard.py +133 -0
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/protocol.py +192 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +799 -0
- constricter/cli/paths.py +151 -0
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +397 -0
- constricter/cli/rounds.py +328 -0
- constricter/cli/runs.py +90 -0
- constricter/cli/schedule.py +463 -0
- constricter/cli/traced.py +401 -0
- constricter/cli/workers.py +286 -0
- constricter/empty.py +66 -0
- constricter/fix/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/__init__.py +2 -0
- constricter/fix/core/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/__init__.py +2 -0
- constricter/fix/core/asked.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/asked.py +56 -0
- constricter/fix/core/fixes.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/fixes.py +317 -0
- constricter/fix/core/imports.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/imports.py +580 -0
- constricter/fix/core/inherited.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/inherited.py +311 -0
- constricter/fix/core/known.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/known.py +730 -0
- constricter/fix/core/signatures.cp312-win_amd64.pyd +0 -0
- constricter/fix/core/signatures.py +132 -0
- constricter/fix/index/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/__init__.py +2 -0
- constricter/fix/index/awaits.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/awaits.py +90 -0
- constricter/fix/index/beyond.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/beyond.py +149 -0
- constricter/fix/index/callers.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/callers.py +155 -0
- constricter/fix/index/crossed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/decorated.py +140 -0
- constricter/fix/index/fixtures.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/fixtures.py +176 -0
- constricter/fix/index/linked.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/linked.py +142 -0
- constricter/fix/index/loose.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/loose.py +103 -0
- constricter/fix/index/managed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/managed.py +63 -0
- constricter/fix/index/modules.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/modules.py +741 -0
- constricter/fix/index/offers.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/offers.py +252 -0
- constricter/fix/index/order.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/order.py +155 -0
- constricter/fix/index/origins.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_methods.py +91 -0
- constricter/fix/index/own_overloads.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_overloads.py +70 -0
- constricter/fix/index/own_types.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/own_types.py +330 -0
- constricter/fix/index/params.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/plain.py +221 -0
- constricter/fix/index/project.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/project.py +797 -0
- constricter/fix/index/sides.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/sides.py +160 -0
- constricter/fix/index/strings.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/strings.py +46 -0
- constricter/fix/index/stubs/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/stubs/atoms.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/atoms.py +425 -0
- constricter/fix/index/stubs/chained.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/chained.py +69 -0
- constricter/fix/index/stubs/classnames.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/classnames.py +124 -0
- constricter/fix/index/stubs/declared.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/declared.py +508 -0
- constricter/fix/index/stubs/installed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/installed.py +425 -0
- constricter/fix/index/stubs/names.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/stubs/read.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/read.py +70 -0
- constricter/fix/index/stubs/signatures.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/signatures.py +656 -0
- constricter/fix/index/stubs/stubbed.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/index/taken.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/taken.py +45 -0
- constricter/fix/index/tuples.cp312-win_amd64.pyd +0 -0
- constricter/fix/index/tuples.py +112 -0
- constricter/fix/libraries/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/__init__.py +2 -0
- constricter/fix/libraries/library.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/library.py +420 -0
- constricter/fix/libraries/opened.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/opened.py +147 -0
- constricter/fix/libraries/overloads.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/overloads.py +960 -0
- constricter/fix/libraries/receivers.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/stdlib.py +752 -0
- constricter/fix/libraries/walks.cp312-win_amd64.pyd +0 -0
- constricter/fix/libraries/walks.py +49 -0
- constricter/fix/tables/aliases.json +147 -0
- constricter/fix/tables/attributes.json +700 -0
- constricter/fix/tables/awaited.json +69 -0
- constricter/fix/tables/awaited_overloads.json +14 -0
- constricter/fix/tables/bases.json +888 -0
- constricter/fix/tables/classes.json +1941 -0
- constricter/fix/tables/elements.json +156 -0
- constricter/fix/tables/functions.json +58 -0
- constricter/fix/tables/generic_attributes.json +254 -0
- constricter/fix/tables/method_overloads.json +461 -0
- constricter/fix/tables/method_signatures.json +954 -0
- constricter/fix/tables/methods.json +954 -0
- constricter/fix/tables/overloads.json +540 -0
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/returns.json +1362 -0
- constricter/fix/tables/scalar_members.json +16 -0
- constricter/fix/tables/scalars.json +2707 -0
- constricter/fix/tables/source.json +28 -0
- constricter/fix/tables/subscriptable.json +77 -0
- constricter/fix/tables/type_parameters.json +178 -0
- constricter/fix/tables/variables.json +2962 -0
- constricter/fix/values/__init__.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/__init__.py +2 -0
- constricter/fix/values/aliased.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/aliased.py +195 -0
- constricter/fix/values/bodies.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/bodies.py +76 -0
- constricter/fix/values/callables.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/callables.py +323 -0
- constricter/fix/values/called.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/called.py +194 -0
- constricter/fix/values/classvars.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/classvars.py +629 -0
- constricter/fix/values/decided.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/decided.py +429 -0
- constricter/fix/values/displays.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/displays.py +101 -0
- constricter/fix/values/doubts.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/doubts.py +677 -0
- constricter/fix/values/entered.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/entered.py +332 -0
- constricter/fix/values/fills.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/fills.py +446 -0
- constricter/fix/values/guesses.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/guesses.py +376 -0
- constricter/fix/values/hinted.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/hinted.py +811 -0
- constricter/fix/values/inference.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/inference.py +698 -0
- constricter/fix/values/loops.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/members.py +578 -0
- constricter/fix/values/narrowed.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/narrowed.py +220 -0
- constricter/fix/values/operated.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/operated.py +202 -0
- constricter/fix/values/ordered.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/ordered.py +74 -0
- constricter/fix/values/returned.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/returned.py +479 -0
- constricter/fix/values/returns.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/returns.py +237 -0
- constricter/fix/values/shapes.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/shapes.py +654 -0
- constricter/fix/values/stored.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/targets.py +419 -0
- constricter/fix/values/unchecked.cp312-win_amd64.pyd +0 -0
- constricter/fix/values/unchecked.py +278 -0
- constricter/jsonc.py +50 -0
- constricter/noqa.py +47 -0
- constricter/notebook.py +115 -0
- constricter/offences.py +436 -0
- constricter/overrides.py +24 -0
- constricter/plugins/__init__.py +2 -0
- constricter/plugins/flake8.py +133 -0
- constricter/plugins/pylint.py +186 -0
- constricter/py.typed +0 -0
- constricter/recording.py +660 -0
- constricter/rules/__init__.cp312-win_amd64.pyd +0 -0
- constricter/rules/__init__.py +2 -0
- constricter/rules/annotations.cp312-win_amd64.pyd +0 -0
- constricter/rules/annotations.py +296 -0
- constricter/rules/binding.cp312-win_amd64.pyd +0 -0
- constricter/rules/binding.py +652 -0
- constricter/rules/calls.cp312-win_amd64.pyd +0 -0
- constricter/rules/calls.py +460 -0
- constricter/rules/checker.cp312-win_amd64.pyd +0 -0
- constricter/rules/checker.py +536 -0
- constricter/rules/declarations.cp312-win_amd64.pyd +0 -0
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.cp312-win_amd64.pyd +0 -0
- constricter/rules/decorators.py +351 -0
- constricter/rules/flow.cp312-win_amd64.pyd +0 -0
- constricter/rules/flow.py +502 -0
- constricter/rules/keys.cp312-win_amd64.pyd +0 -0
- constricter/rules/keys.py +79 -0
- constricter/rules/late.cp312-win_amd64.pyd +0 -0
- constricter/rules/late.py +969 -0
- constricter/rules/narrowing.cp312-win_amd64.pyd +0 -0
- constricter/rules/narrowing.py +98 -0
- constricter/rules/parsed.cp312-win_amd64.pyd +0 -0
- constricter/rules/parsed.py +136 -0
- constricter/rules/quoted.cp312-win_amd64.pyd +0 -0
- constricter/rules/quoted.py +141 -0
- constricter/rules/rebinding.cp312-win_amd64.pyd +0 -0
- constricter/rules/rebinding.py +169 -0
- constricter/rules/recorded.cp312-win_amd64.pyd +0 -0
- constricter/rules/recorded.py +168 -0
- constricter/rules/redundant.cp312-win_amd64.pyd +0 -0
- constricter/rules/redundant.py +160 -0
- constricter/rules/scope.cp312-win_amd64.pyd +0 -0
- constricter/rules/scope.py +802 -0
- constricter/rules/scoped.cp312-win_amd64.pyd +0 -0
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.cp312-win_amd64.pyd +0 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.cp312-win_amd64.pyd +0 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/syntax.cp312-win_amd64.pyd +0 -0
- constricter/rules/syntax.py +472 -0
- constricter/rules/tables.cp312-win_amd64.pyd +0 -0
- constricter/rules/tables.py +169 -0
- constricter/rules/walked.cp312-win_amd64.pyd +0 -0
- constricter/rules/walked.py +127 -0
- constricter/rules/widened.cp312-win_amd64.pyd +0 -0
- constricter/rules/widened.py +322 -0
- constricter/trace.py +374 -0
- e45ce06b2fdd5c43f782__mypyc.cp312-win_amd64.pyd +0 -0
- python_constricter-0.4.1.dist-info/METADATA +400 -0
- python_constricter-0.4.1.dist-info/RECORD +261 -0
- python_constricter-0.4.1.dist-info/WHEEL +4 -0
- python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
- python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The rules as a flake8 plugin (`LVA` prefix); reports the codes the level makes errors."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import ast
|
|
6
|
+
from collections.abc import Iterator, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar, Final, cast, final
|
|
8
|
+
|
|
9
|
+
from constricter import __version__
|
|
10
|
+
from constricter.offences import LEVELS, MAX_LENGTH, NESTING, OPT_IN, VAGUE, Checks, Level, Offence
|
|
11
|
+
from constricter.rules.checker import check_source, check_tree
|
|
12
|
+
from constricter.rules.flow import Narrower, parse_narrower
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from flake8.options.manager import OptionManager
|
|
16
|
+
|
|
17
|
+
_TYPE_COMMENT: Final = "type:"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@final
|
|
21
|
+
class ConstricterChecker:
|
|
22
|
+
"""flake8's checker: one instance per file."""
|
|
23
|
+
|
|
24
|
+
name: str = "constricter"
|
|
25
|
+
version: str = __version__
|
|
26
|
+
level: ClassVar[Level] = Level.STRICT
|
|
27
|
+
type_comments: ClassVar[bool] = False
|
|
28
|
+
all_scopes: ClassVar[bool] = False
|
|
29
|
+
nesting: ClassVar[int] = NESTING
|
|
30
|
+
max_length: ClassVar[int] = MAX_LENGTH
|
|
31
|
+
vague: ClassVar[int] = VAGUE
|
|
32
|
+
narrower: ClassVar[tuple[Narrower, ...]] = ()
|
|
33
|
+
can_be_final: ClassVar[bool] = False # report LVA012
|
|
34
|
+
|
|
35
|
+
def __init__(self, tree: ast.Module, lines: Sequence[str]) -> None:
|
|
36
|
+
"""Take the file flake8 parsed, and its lines."""
|
|
37
|
+
self.tree: ast.Module = tree
|
|
38
|
+
self.lines: Sequence[str] = lines
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def add_options(cls, parser: "OptionManager") -> None:
|
|
42
|
+
"""Register the options (flake8's plugin hook)."""
|
|
43
|
+
parser.add_option(
|
|
44
|
+
"--constricter-level",
|
|
45
|
+
choices=list(LEVELS),
|
|
46
|
+
default="strict",
|
|
47
|
+
parse_from_config=True,
|
|
48
|
+
help="which LVA codes are reported (default: strict)",
|
|
49
|
+
)
|
|
50
|
+
parser.add_option(
|
|
51
|
+
"--constricter-type-comments",
|
|
52
|
+
action="store_true",
|
|
53
|
+
parse_from_config=True,
|
|
54
|
+
help="count `x = 1 # type: int` as annotated",
|
|
55
|
+
)
|
|
56
|
+
parser.add_option(
|
|
57
|
+
"--constricter-all-scopes",
|
|
58
|
+
action="store_true",
|
|
59
|
+
parse_from_config=True,
|
|
60
|
+
help="also check module and class bodies (LVA004)",
|
|
61
|
+
)
|
|
62
|
+
parser.add_option(
|
|
63
|
+
"--constricter-nesting",
|
|
64
|
+
type=int,
|
|
65
|
+
default=NESTING,
|
|
66
|
+
parse_from_config=True,
|
|
67
|
+
help=f"report an annotation nested this deep (LVA006; default: {NESTING})",
|
|
68
|
+
)
|
|
69
|
+
parser.add_option(
|
|
70
|
+
"--constricter-max-length",
|
|
71
|
+
type=int,
|
|
72
|
+
default=MAX_LENGTH,
|
|
73
|
+
parse_from_config=True,
|
|
74
|
+
help=f"report a fixed-length tuple annotation listing more types (LVA011; default: {MAX_LENGTH})",
|
|
75
|
+
)
|
|
76
|
+
parser.add_option(
|
|
77
|
+
"--constricter-vague",
|
|
78
|
+
type=int,
|
|
79
|
+
default=VAGUE,
|
|
80
|
+
parse_from_config=True,
|
|
81
|
+
help=f"how vague an annotation may be before it's LVA005 (default: {VAGUE}, none)",
|
|
82
|
+
)
|
|
83
|
+
parser.add_option(
|
|
84
|
+
"--constricter-narrower",
|
|
85
|
+
default="",
|
|
86
|
+
parse_from_config=True,
|
|
87
|
+
help="your own type hierarchy for LVA008-LVA010, as `B=A, C=A, int=` (B is narrower than A)",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def parse_options(cls, options: argparse.Namespace) -> None:
|
|
92
|
+
"""Read the parsed options (flake8's plugin hook)."""
|
|
93
|
+
cls.level = LEVELS[cast("str", options.constricter_level)]
|
|
94
|
+
cls.type_comments = cast("bool", options.constricter_type_comments)
|
|
95
|
+
cls.all_scopes = cast("bool", options.constricter_all_scopes)
|
|
96
|
+
cls.nesting = cast("int", options.constricter_nesting)
|
|
97
|
+
cls.max_length = cast("int", options.constricter_max_length)
|
|
98
|
+
cls.vague = cast("int", options.constricter_vague)
|
|
99
|
+
cls.narrower = parse_narrower(cast("str", options.constricter_narrower))
|
|
100
|
+
# An opt-in code only when `select` or `extend-select` names it in full, as the CLI does.
|
|
101
|
+
chosen: list[str] = [
|
|
102
|
+
*(cast("list[str] | None", getattr(options, "select", None)) or []),
|
|
103
|
+
*(cast("list[str] | None", getattr(options, "extend_select", None)) or []),
|
|
104
|
+
]
|
|
105
|
+
cls.can_be_final = bool(OPT_IN & set(chosen))
|
|
106
|
+
|
|
107
|
+
def run(self) -> Iterator[tuple[int, int, str, type["ConstricterChecker"]]]:
|
|
108
|
+
"""Check the file.
|
|
109
|
+
|
|
110
|
+
Yields:
|
|
111
|
+
flake8's `(line, col, message, type)` per error-level offence.
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
source: str = "".join(self.lines)
|
|
115
|
+
# flake8's tree has no `# type:` comments; reparse only when the file might have one.
|
|
116
|
+
checks: Checks = Checks(
|
|
117
|
+
type_comments=self.type_comments,
|
|
118
|
+
all_scopes=self.all_scopes,
|
|
119
|
+
nesting=self.nesting,
|
|
120
|
+
max_length=self.max_length,
|
|
121
|
+
vague=self.vague,
|
|
122
|
+
narrower=self.narrower,
|
|
123
|
+
final=self.can_be_final,
|
|
124
|
+
)
|
|
125
|
+
offences: list[Offence] = (
|
|
126
|
+
check_source(source, checks=checks)
|
|
127
|
+
if _TYPE_COMMENT in source
|
|
128
|
+
else check_tree(self.tree, checks, lines=self.lines)
|
|
129
|
+
)
|
|
130
|
+
o: Offence
|
|
131
|
+
for o in offences:
|
|
132
|
+
if o.is_error(self.level):
|
|
133
|
+
yield o.line, o.col, f"{o.code} {o.message}", type(self)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The rules as a pylint plugin (C9101-C9112); reports the codes the level makes errors."""
|
|
3
|
+
|
|
4
|
+
from typing import IO, NamedTuple, Protocol, cast, final
|
|
5
|
+
|
|
6
|
+
from pylint.checkers import BaseRawFileChecker
|
|
7
|
+
from pylint.lint import PyLinter
|
|
8
|
+
from pylint.typing import MessageDefinitionTuple, Options
|
|
9
|
+
|
|
10
|
+
from constricter.jsonc import as_text
|
|
11
|
+
from constricter.noqa import lines, unsuppressed
|
|
12
|
+
from constricter.offences import (
|
|
13
|
+
CAN_BE_FINAL,
|
|
14
|
+
COMMENT_TYPED_TARGET,
|
|
15
|
+
LEVELS,
|
|
16
|
+
LONG_TUPLE,
|
|
17
|
+
MAX_LENGTH,
|
|
18
|
+
MESSAGES,
|
|
19
|
+
MISMATCHED_TYPE,
|
|
20
|
+
NARROWABLE_TYPE,
|
|
21
|
+
NESTED_TYPE,
|
|
22
|
+
NESTING,
|
|
23
|
+
OPT_IN,
|
|
24
|
+
REDUNDANT_TYPE,
|
|
25
|
+
UNANNOTATED,
|
|
26
|
+
UNANNOTATED_MEMBER,
|
|
27
|
+
UNTYPED_TARGET,
|
|
28
|
+
UNUSED_UNION_MEMBER,
|
|
29
|
+
VAGUE,
|
|
30
|
+
VAGUE_TYPE,
|
|
31
|
+
Checks,
|
|
32
|
+
Level,
|
|
33
|
+
Offence,
|
|
34
|
+
)
|
|
35
|
+
from constricter.overrides import override
|
|
36
|
+
from constricter.rules.checker import check_source
|
|
37
|
+
from constricter.rules.flow import parse_narrower
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Message(NamedTuple):
|
|
41
|
+
"""A code's pylint message id and symbol."""
|
|
42
|
+
|
|
43
|
+
msg_id: str
|
|
44
|
+
symbol: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
SYMBOLS: dict[str, Message] = {
|
|
48
|
+
UNANNOTATED: Message("C9101", "unannotated-local-variable"),
|
|
49
|
+
UNTYPED_TARGET: Message("C9102", "untyped-for-or-match-variable"),
|
|
50
|
+
COMMENT_TYPED_TARGET: Message("C9103", "comment-typed-for-variable"),
|
|
51
|
+
UNANNOTATED_MEMBER: Message("C9104", "unannotated-module-or-class-variable"),
|
|
52
|
+
VAGUE_TYPE: Message("C9105", "vague-annotation"),
|
|
53
|
+
NESTED_TYPE: Message("C9106", "deeply-nested-annotation"),
|
|
54
|
+
REDUNDANT_TYPE: Message("C9107", "redundant-annotation"),
|
|
55
|
+
NARROWABLE_TYPE: Message("C9108", "narrowable-annotation"),
|
|
56
|
+
MISMATCHED_TYPE: Message("C9109", "mismatched-value-type"),
|
|
57
|
+
UNUSED_UNION_MEMBER: Message("C9110", "unused-union-member"),
|
|
58
|
+
LONG_TUPLE: Message("C9111", "long-tuple-annotation"),
|
|
59
|
+
CAN_BE_FINAL: Message("C9112", "could-be-final"),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _Module(Protocol): # pylint: disable=too-few-public-methods
|
|
64
|
+
"""What the checker reads of a module astroid parsed: the file behind it, and its bytes.
|
|
65
|
+
|
|
66
|
+
astroid declares no types (no `py.typed`): naming its `Module` would leave the checker's
|
|
67
|
+
signature unknown to a type checker reading this package.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
file: str | None
|
|
71
|
+
|
|
72
|
+
def stream(self) -> IO[bytes] | None:
|
|
73
|
+
"""Open the module's bytes, or nothing where no file backs it."""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@final
|
|
78
|
+
class ConstricterChecker(BaseRawFileChecker):
|
|
79
|
+
"""pylint's checker, run once per module on its raw source."""
|
|
80
|
+
|
|
81
|
+
name: str = "constricter"
|
|
82
|
+
options: Options = (
|
|
83
|
+
(
|
|
84
|
+
"constricter-level",
|
|
85
|
+
{
|
|
86
|
+
"default": "strict",
|
|
87
|
+
"type": "choice",
|
|
88
|
+
"choices": list(LEVELS),
|
|
89
|
+
"metavar": "<level>",
|
|
90
|
+
"help": "Which codes are reported.",
|
|
91
|
+
},
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
"constricter-type-comments",
|
|
95
|
+
{"default": False, "type": "yn", "metavar": "<y or n>", "help": "Count `# type:` comments."},
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
"constricter-all-scopes",
|
|
99
|
+
{"default": False, "type": "yn", "metavar": "<y or n>", "help": "Check module and class bodies."},
|
|
100
|
+
),
|
|
101
|
+
(
|
|
102
|
+
"constricter-nesting",
|
|
103
|
+
{
|
|
104
|
+
"default": NESTING,
|
|
105
|
+
"type": "int",
|
|
106
|
+
"metavar": "<n>",
|
|
107
|
+
"help": "Report an annotation nested this deep.",
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"constricter-max-length",
|
|
112
|
+
{
|
|
113
|
+
"default": MAX_LENGTH,
|
|
114
|
+
"type": "int",
|
|
115
|
+
"metavar": "<n>",
|
|
116
|
+
"help": "Report a fixed-length tuple annotation listing more than this many types.",
|
|
117
|
+
},
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
"constricter-vague",
|
|
121
|
+
{
|
|
122
|
+
"default": VAGUE,
|
|
123
|
+
"type": "int",
|
|
124
|
+
"metavar": "<n>",
|
|
125
|
+
"help": "How vague an annotation may be before it's LVA005 (-1: none).",
|
|
126
|
+
},
|
|
127
|
+
),
|
|
128
|
+
(
|
|
129
|
+
"constricter-narrower",
|
|
130
|
+
{
|
|
131
|
+
"default": "",
|
|
132
|
+
"type": "string",
|
|
133
|
+
"metavar": "<B=A, ...>",
|
|
134
|
+
"help": "Your own type hierarchy (LVA008-LVA010): B=A, B narrower than A.",
|
|
135
|
+
},
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
msgs: dict[str, MessageDefinitionTuple]
|
|
140
|
+
|
|
141
|
+
def __init__(self, linter: PyLinter) -> None:
|
|
142
|
+
"""Register the messages with `linter`."""
|
|
143
|
+
super().__init__(linter)
|
|
144
|
+
# An opt-in code's message is off until enabled (`enable = could-be-final`).
|
|
145
|
+
self.msgs = {
|
|
146
|
+
message.msg_id: (
|
|
147
|
+
MESSAGES[code].format(name="%r", detail="%s"),
|
|
148
|
+
message.symbol,
|
|
149
|
+
f"See constricter's {code}.",
|
|
150
|
+
{"default_enabled": code not in OPT_IN},
|
|
151
|
+
)
|
|
152
|
+
for code, message in SYMBOLS.items()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
def process_module(self, node: _Module) -> None:
|
|
157
|
+
"""Report the module's error-level offences."""
|
|
158
|
+
opened: IO[bytes] | None
|
|
159
|
+
if (opened := node.stream()) is None: # no file behind the module
|
|
160
|
+
return
|
|
161
|
+
stream: IO[bytes]
|
|
162
|
+
with opened as stream:
|
|
163
|
+
source: bytes = stream.read()
|
|
164
|
+
level: Level = LEVELS[cast("str", self.linter.config.constricter_level)]
|
|
165
|
+
checks: Checks = Checks(
|
|
166
|
+
type_comments=cast("bool", self.linter.config.constricter_type_comments),
|
|
167
|
+
all_scopes=cast("bool", self.linter.config.constricter_all_scopes),
|
|
168
|
+
nesting=cast("int", self.linter.config.constricter_nesting),
|
|
169
|
+
max_length=cast("int", self.linter.config.constricter_max_length),
|
|
170
|
+
vague=cast("int", self.linter.config.constricter_vague),
|
|
171
|
+
narrower=parse_narrower(cast("str", self.linter.config.constricter_narrower)),
|
|
172
|
+
final=self.linter.is_message_enabled(SYMBOLS[CAN_BE_FINAL].symbol),
|
|
173
|
+
)
|
|
174
|
+
text: str = as_text(source)
|
|
175
|
+
offences: list[Offence] = check_source(text, node.file or "<unknown>", checks)
|
|
176
|
+
o: Offence
|
|
177
|
+
for o in unsuppressed(offences, lines(text)): # suppression comments, as the CLI reads them
|
|
178
|
+
if o.is_error(level):
|
|
179
|
+
# A message names the variable, then (LVA009's) its `detail`, as `msgs` spells them.
|
|
180
|
+
args: tuple[str, ...] = (o.name, o.detail) if o.detail else (o.name,)
|
|
181
|
+
self.add_message(SYMBOLS[o.code].symbol, line=o.line, col_offset=o.col, args=args)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def register(linter: PyLinter) -> None:
|
|
185
|
+
"""Register the checker (pylint's plugin hook)."""
|
|
186
|
+
linter.register_checker(ConstricterChecker(linter))
|
constricter/py.typed
ADDED
|
File without changes
|