python-constricter 0.4.1__cp313-cp313-win32.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.
Files changed (261) hide show
  1. constricter/__init__.py +55 -0
  2. constricter/__main__.py +26 -0
  3. constricter/_version.py +4 -0
  4. constricter/cli/__init__.py +2 -0
  5. constricter/cli/baseline.py +107 -0
  6. constricter/cli/collecting.py +54 -0
  7. constricter/cli/command.py +547 -0
  8. constricter/cli/config.py +322 -0
  9. constricter/cli/explain.py +104 -0
  10. constricter/cli/lsp/__init__.py +2 -0
  11. constricter/cli/lsp/connection.py +475 -0
  12. constricter/cli/lsp/edits.py +188 -0
  13. constricter/cli/lsp/guard.py +133 -0
  14. constricter/cli/lsp/hints.py +391 -0
  15. constricter/cli/lsp/protocol.py +192 -0
  16. constricter/cli/lsp/remembered.py +81 -0
  17. constricter/cli/lsp/stored.py +200 -0
  18. constricter/cli/options.py +799 -0
  19. constricter/cli/paths.py +151 -0
  20. constricter/cli/reading.py +36 -0
  21. constricter/cli/report.py +397 -0
  22. constricter/cli/rounds.py +328 -0
  23. constricter/cli/runs.py +90 -0
  24. constricter/cli/schedule.py +463 -0
  25. constricter/cli/traced.py +401 -0
  26. constricter/cli/workers.py +286 -0
  27. constricter/empty.py +66 -0
  28. constricter/fix/__init__.cp313-win32.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp313-win32.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp313-win32.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp313-win32.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp313-win32.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp313-win32.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp313-win32.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp313-win32.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp313-win32.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp313-win32.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp313-win32.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp313-win32.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp313-win32.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp313-win32.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp313-win32.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp313-win32.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp313-win32.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp313-win32.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp313-win32.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp313-win32.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp313-win32.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp313-win32.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp313-win32.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp313-win32.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp313-win32.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp313-win32.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp313-win32.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp313-win32.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp313-win32.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp313-win32.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp313-win32.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp313-win32.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp313-win32.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp313-win32.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp313-win32.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp313-win32.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp313-win32.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp313-win32.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp313-win32.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp313-win32.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp313-win32.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp313-win32.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp313-win32.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp313-win32.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp313-win32.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp313-win32.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp313-win32.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp313-win32.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp313-win32.pyd +0 -0
  125. constricter/fix/libraries/walks.py +49 -0
  126. constricter/fix/tables/aliases.json +147 -0
  127. constricter/fix/tables/attributes.json +700 -0
  128. constricter/fix/tables/awaited.json +69 -0
  129. constricter/fix/tables/awaited_overloads.json +14 -0
  130. constricter/fix/tables/bases.json +888 -0
  131. constricter/fix/tables/classes.json +1941 -0
  132. constricter/fix/tables/elements.json +156 -0
  133. constricter/fix/tables/functions.json +58 -0
  134. constricter/fix/tables/generic_attributes.json +254 -0
  135. constricter/fix/tables/method_overloads.json +461 -0
  136. constricter/fix/tables/method_signatures.json +954 -0
  137. constricter/fix/tables/methods.json +954 -0
  138. constricter/fix/tables/overloads.json +540 -0
  139. constricter/fix/tables/platform.json +37 -0
  140. constricter/fix/tables/returns.json +1362 -0
  141. constricter/fix/tables/scalar_members.json +16 -0
  142. constricter/fix/tables/scalars.json +2707 -0
  143. constricter/fix/tables/source.json +28 -0
  144. constricter/fix/tables/subscriptable.json +77 -0
  145. constricter/fix/tables/type_parameters.json +178 -0
  146. constricter/fix/tables/variables.json +2962 -0
  147. constricter/fix/values/__init__.cp313-win32.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp313-win32.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp313-win32.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp313-win32.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp313-win32.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp313-win32.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp313-win32.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp313-win32.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp313-win32.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp313-win32.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp313-win32.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp313-win32.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp313-win32.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp313-win32.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp313-win32.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp313-win32.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp313-win32.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp313-win32.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp313-win32.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp313-win32.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp313-win32.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp313-win32.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp313-win32.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp313-win32.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp313-win32.pyd +0 -0
  196. constricter/fix/values/unchecked.py +278 -0
  197. constricter/jsonc.py +50 -0
  198. constricter/noqa.py +47 -0
  199. constricter/notebook.py +115 -0
  200. constricter/offences.py +436 -0
  201. constricter/overrides.py +24 -0
  202. constricter/plugins/__init__.py +2 -0
  203. constricter/plugins/flake8.py +133 -0
  204. constricter/plugins/pylint.py +186 -0
  205. constricter/py.typed +0 -0
  206. constricter/recording.py +660 -0
  207. constricter/rules/__init__.cp313-win32.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp313-win32.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp313-win32.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp313-win32.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp313-win32.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp313-win32.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp313-win32.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp313-win32.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp313-win32.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp313-win32.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp313-win32.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp313-win32.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp313-win32.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp313-win32.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp313-win32.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp313-win32.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp313-win32.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp313-win32.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp313-win32.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp313-win32.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp313-win32.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp313-win32.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp313-win32.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp313-win32.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp313-win32.pyd +0 -0
  257. python_constricter-0.4.1.dist-info/METADATA +400 -0
  258. python_constricter-0.4.1.dist-info/RECORD +261 -0
  259. python_constricter-0.4.1.dist-info/WHEEL +4 -0
  260. python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
  261. 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