python-constricter 0.3.5__py3-none-any.whl → 0.4.1__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.
- constricter/__init__.py +1 -1
- constricter/_version.py +4 -0
- constricter/cli/collecting.py +8 -1
- constricter/cli/command.py +28 -226
- constricter/cli/config.py +2 -1
- constricter/cli/lsp/__init__.py +2 -0
- constricter/cli/{hints.py → lsp/connection.py} +17 -363
- constricter/cli/{edits.py → lsp/edits.py} +1 -1
- constricter/cli/{guard.py → lsp/guard.py} +5 -1
- constricter/cli/lsp/hints.py +391 -0
- constricter/cli/lsp/remembered.py +81 -0
- constricter/cli/lsp/stored.py +200 -0
- constricter/cli/options.py +60 -7
- constricter/cli/paths.py +76 -7
- constricter/cli/reading.py +36 -0
- constricter/cli/report.py +5 -2
- constricter/cli/rounds.py +328 -0
- constricter/cli/schedule.py +84 -36
- constricter/cli/workers.py +78 -8
- constricter/empty.py +66 -0
- constricter/fix/core/fixes.py +26 -1
- constricter/fix/core/imports.py +110 -8
- constricter/fix/core/inherited.py +2 -1
- constricter/fix/core/known.py +101 -34
- constricter/fix/core/signatures.py +14 -7
- constricter/fix/index/beyond.py +3 -2
- constricter/fix/index/callers.py +3 -2
- constricter/fix/index/crossed.py +327 -0
- constricter/fix/index/decorated.py +29 -10
- constricter/fix/index/loose.py +11 -6
- constricter/fix/index/modules.py +90 -192
- constricter/fix/index/offers.py +123 -9
- constricter/fix/index/origins.py +238 -0
- constricter/fix/index/own_methods.py +31 -7
- constricter/fix/index/own_overloads.py +2 -2
- constricter/fix/index/own_types.py +183 -14
- constricter/fix/index/params.py +81 -0
- constricter/fix/index/plain.py +109 -13
- constricter/fix/index/project.py +86 -273
- constricter/fix/index/stubs/__init__.py +2 -0
- constricter/fix/index/{atoms.py → stubs/atoms.py} +3 -3
- constricter/fix/index/{chained.py → stubs/chained.py} +2 -2
- constricter/fix/index/{classnames.py → stubs/classnames.py} +2 -2
- constricter/fix/index/{declared.py → stubs/declared.py} +8 -6
- constricter/fix/index/{installed.py → stubs/installed.py} +84 -7
- constricter/fix/index/stubs/names.py +200 -0
- constricter/fix/index/{read.py → stubs/read.py} +2 -2
- constricter/fix/index/{stubbed.py → stubs/signatures.py} +28 -372
- constricter/fix/index/stubs/stubbed.py +208 -0
- constricter/fix/libraries/library.py +60 -14
- constricter/fix/libraries/overloads.py +315 -323
- constricter/fix/libraries/receivers.py +274 -0
- constricter/fix/libraries/stdlib.py +14 -2
- constricter/fix/tables/attributes.json +8 -8
- constricter/fix/tables/functions.json +15 -0
- constricter/fix/tables/method_overloads.json +91 -91
- constricter/fix/tables/method_signatures.json +60 -2
- constricter/fix/tables/overloads.json +5 -2
- constricter/fix/tables/platform.json +37 -0
- constricter/fix/tables/source.json +5 -3
- constricter/fix/tables/variables.json +3 -0
- constricter/fix/values/aliased.py +9 -4
- constricter/fix/values/callables.py +4 -2
- constricter/fix/values/called.py +89 -1
- constricter/fix/values/classvars.py +100 -12
- constricter/fix/values/decided.py +39 -6
- constricter/fix/values/displays.py +19 -5
- constricter/fix/values/doubts.py +21 -6
- constricter/fix/values/entered.py +2 -1
- constricter/fix/values/fills.py +21 -9
- constricter/fix/values/guesses.py +11 -5
- constricter/fix/values/hinted.py +175 -9
- constricter/fix/values/inference.py +24 -320
- constricter/fix/values/loops.py +335 -0
- constricter/fix/values/members.py +21 -7
- constricter/fix/values/operated.py +10 -8
- constricter/fix/values/returned.py +70 -558
- constricter/fix/values/shapes.py +192 -32
- constricter/fix/values/stored.py +564 -0
- constricter/fix/values/targets.py +49 -13
- constricter/fix/values/unchecked.py +119 -3
- constricter/notebook.py +6 -8
- constricter/offences.py +121 -3
- constricter/overrides.py +24 -0
- constricter/plugins/pylint.py +20 -17
- constricter/recording.py +109 -20
- constricter/rules/annotations.py +10 -688
- constricter/rules/binding.py +14 -8
- constricter/rules/calls.py +3 -2
- constricter/rules/checker.py +71 -527
- constricter/rules/declarations.py +730 -0
- constricter/rules/decorators.py +158 -28
- constricter/rules/keys.py +1 -1
- constricter/rules/late.py +331 -25
- constricter/rules/parsed.py +9 -3
- constricter/rules/quoted.py +21 -0
- constricter/rules/rebinding.py +5 -3
- constricter/rules/recorded.py +13 -12
- constricter/rules/scope.py +48 -240
- constricter/rules/scoped.py +232 -0
- constricter/rules/scoping.py +346 -0
- constricter/rules/settings.py +189 -0
- constricter/rules/tables.py +1 -1
- constricter/rules/widened.py +1 -1
- constricter/trace.py +5 -9
- {python_constricter-0.3.5.dist-info → python_constricter-0.4.1.dist-info}/METADATA +17 -9
- python_constricter-0.4.1.dist-info/RECORD +162 -0
- {python_constricter-0.3.5.dist-info → python_constricter-0.4.1.dist-info}/licenses/LICENSE.md +1 -1
- constricter/cli/remembered.py +0 -52
- python_constricter-0.3.5.dist-info/RECORD +0 -140
- /constricter/cli/{protocol.py → lsp/protocol.py} +0 -0
- {python_constricter-0.3.5.dist-info → python_constricter-0.4.1.dist-info}/WHEEL +0 -0
- {python_constricter-0.3.5.dist-info → python_constricter-0.4.1.dist-info}/entry_points.txt +0 -0
constricter/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
"""Every local variable typed where it's first bound."""
|
|
3
3
|
|
|
4
|
+
from constricter._version import __version__ # written by the build, from the git tag
|
|
4
5
|
from constricter.offences import (
|
|
5
6
|
COMMENT_TYPED_TARGET,
|
|
6
7
|
DEFAULT_CHECKS,
|
|
@@ -25,7 +26,6 @@ from constricter.offences import (
|
|
|
25
26
|
)
|
|
26
27
|
from constricter.rules.checker import Coverage, annotation_coverage, check_source, check_tree
|
|
27
28
|
|
|
28
|
-
__version__ = "0.3.5"
|
|
29
29
|
__all__ = [
|
|
30
30
|
"COMMENT_TYPED_TARGET",
|
|
31
31
|
"DEFAULT_CHECKS",
|
constricter/_version.py
ADDED
constricter/cli/collecting.py
CHANGED
|
@@ -6,15 +6,21 @@ cyclic. Left to itself, the collector looked through the kept trees again and ag
|
|
|
6
6
|
allocated: a third of the standard library's check (34s, 21s without). So while the command runs,
|
|
7
7
|
it's off: what indexing built is frozen (never looked through again), and after each file, only
|
|
8
8
|
what's been allocated since (the young generations) is collected, which bounds what a cycle can hold.
|
|
9
|
+
Not on a free-threaded build, whose collector has no generations: each collection there looks through
|
|
10
|
+
everything, and one after every file made a check several times as long. Its cycles wait for the run's
|
|
11
|
+
end (a check of pandas held 2.9 GB so, to 2.7 GB).
|
|
9
12
|
"""
|
|
10
13
|
|
|
11
14
|
import contextlib
|
|
12
15
|
import gc
|
|
16
|
+
import sysconfig
|
|
13
17
|
from collections.abc import Generator
|
|
14
18
|
from typing import Final
|
|
15
19
|
|
|
16
20
|
_YOUNG: Final = 1 # the generations collected after each file: 0 and 1, never the frozen or the old
|
|
17
21
|
_FREEZES: Final = hasattr(gc, "freeze") # PyPy's collector has no freezing
|
|
22
|
+
# Whether the collector has generations: a free-threaded build's has none, its GIL on or off.
|
|
23
|
+
_YOUNGER: Final = not sysconfig.get_config_var("Py_GIL_DISABLED")
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
@contextlib.contextmanager
|
|
@@ -44,4 +50,5 @@ def indexed() -> None:
|
|
|
44
50
|
|
|
45
51
|
def sweep() -> None:
|
|
46
52
|
"""Collect what the file just checked left behind (see the module docstring)."""
|
|
47
|
-
|
|
53
|
+
if _YOUNGER:
|
|
54
|
+
_ = gc.collect(_YOUNG)
|
constricter/cli/command.py
CHANGED
|
@@ -5,27 +5,26 @@ import codecs
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import difflib
|
|
7
7
|
import importlib
|
|
8
|
-
import io
|
|
9
8
|
import json
|
|
10
9
|
import sys
|
|
11
|
-
import
|
|
12
|
-
from collections.abc import Callable, Mapping, Sequence
|
|
10
|
+
from collections.abc import Callable, Sequence
|
|
13
11
|
from dataclasses import replace
|
|
14
12
|
from functools import partial
|
|
15
13
|
from pathlib import Path
|
|
16
14
|
from typing import TYPE_CHECKING, Final, NamedTuple, TextIO, TypeAlias, cast
|
|
17
15
|
|
|
18
16
|
from constricter import notebook
|
|
19
|
-
from constricter.cli import baseline, collecting, schedule
|
|
17
|
+
from constricter.cli import baseline, collecting, schedule
|
|
18
|
+
from constricter.cli.lsp.protocol import HintError
|
|
20
19
|
from constricter.cli.options import Mode, Options, Output
|
|
21
20
|
from constricter.cli.paths import STDIN, python_files
|
|
22
|
-
from constricter.cli.
|
|
21
|
+
from constricter.cli.reading import encoding_of, read_text
|
|
23
22
|
from constricter.cli.report import Format, Result, fix_reasons, render, statistics
|
|
24
|
-
from constricter.cli.
|
|
25
|
-
from constricter.cli.
|
|
23
|
+
from constricter.cli.rounds import checked_all, settled
|
|
24
|
+
from constricter.cli.runs import Added, BaselineRun, CheckRun, CoverageRun, FileRun
|
|
25
|
+
from constricter.cli.workers import kept
|
|
26
26
|
from constricter.fix.core import fixes
|
|
27
|
-
from constricter.fix.core.known import
|
|
28
|
-
from constricter.fix.index import callers, decorated, installed, loose, plain, project
|
|
27
|
+
from constricter.fix.core.known import Outside, Returns
|
|
29
28
|
from constricter.noqa import lines, unsuppressed
|
|
30
29
|
from constricter.offences import (
|
|
31
30
|
DEFAULT_CHECKS,
|
|
@@ -37,39 +36,12 @@ from constricter.rules import parsed
|
|
|
37
36
|
from constricter.rules.checker import Checked, Coverage, annotation_coverage, checked_source
|
|
38
37
|
|
|
39
38
|
if TYPE_CHECKING: # slow to import, and only needed for `--infer-with`
|
|
40
|
-
from constricter.cli import hints
|
|
39
|
+
from constricter.cli.lsp import hints
|
|
41
40
|
|
|
42
41
|
EXIT_CLEAN: Final = 0
|
|
43
42
|
EXIT_FOUND: Final = 1
|
|
44
43
|
EXIT_ERROR: Final = 2
|
|
45
44
|
_ALL: Final = 100 # percent
|
|
46
|
-
_PY: Final = ".py" # a module the index names (not a notebook)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _encoding(path: Path, data: bytes) -> str:
|
|
50
|
-
"""Find the encoding of `path`, whose bytes are `data`: a notebook's is UTF-8 (it's JSON).
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
A module's, as Python finds it: a PEP 263 declaration (`# -*- coding: latin-1 -*-`), a BOM
|
|
54
|
-
(`utf-8-sig`), or UTF-8. Raises `SyntaxError` for an unknown or contradictory one.
|
|
55
|
-
|
|
56
|
-
"""
|
|
57
|
-
return (
|
|
58
|
-
"utf-8" if path.suffix == notebook.SUFFIX else tokenize.detect_encoding(io.BytesIO(data).readline)[0]
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _read(path: Path) -> str:
|
|
63
|
-
"""Read `path`, in its encoding; `-` is standard input.
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Its text.
|
|
67
|
-
|
|
68
|
-
"""
|
|
69
|
-
if path == STDIN:
|
|
70
|
-
return sys.stdin.read()
|
|
71
|
-
data: bytes = path.read_bytes()
|
|
72
|
-
return data.decode(_encoding(path, data))
|
|
73
45
|
|
|
74
46
|
|
|
75
47
|
def _source(raw: str, name: Path) -> tuple[str, list[notebook.Line]]:
|
|
@@ -127,7 +99,6 @@ def _placed(offence: Offence, where: list[notebook.Line]) -> Offence:
|
|
|
127
99
|
|
|
128
100
|
|
|
129
101
|
# One changed part of a fixed file (the file, or a notebook's cell): its label, old and new lines.
|
|
130
|
-
_Hinted: TypeAlias = dict[Path, tuple[Hints, ...]] # each file's hints, a checker's each
|
|
131
102
|
_Change: TypeAlias = tuple[str, list[str], list[str]]
|
|
132
103
|
# One JSON value a coverage report holds, and one file's row of them.
|
|
133
104
|
_Scalar: TypeAlias = str | int | float
|
|
@@ -179,14 +150,11 @@ def fix_file(path: Path, offences: Sequence[Offence]) -> int:
|
|
|
179
150
|
if not (count := sum(1 for o in offences if o.edit is not None)):
|
|
180
151
|
return 0
|
|
181
152
|
data: bytes = path.read_bytes()
|
|
182
|
-
encoding: str =
|
|
153
|
+
encoding: str = encoding_of(path, data)
|
|
183
154
|
_ = path.write_bytes(_fixed(data.decode(encoding), path, offences).text.encode(encoding))
|
|
184
155
|
return count
|
|
185
156
|
|
|
186
157
|
|
|
187
|
-
HINT_ROUNDS: Final = 4 # with `--fix --infer-with`: how many times each file is fixed, at most
|
|
188
|
-
|
|
189
|
-
|
|
190
158
|
def _shown_lines(raw: str, name: Path) -> dict[tuple[int | None, int], str]:
|
|
191
159
|
"""Map each line of `raw` (a notebook's, in its cells) to its text, as offences are placed.
|
|
192
160
|
|
|
@@ -210,7 +178,7 @@ def _checked(path: Path, name: Path, checks: Checks, outside: Outside) -> tuple[
|
|
|
210
178
|
Its text, and what checking it found.
|
|
211
179
|
|
|
212
180
|
"""
|
|
213
|
-
raw: str =
|
|
181
|
+
raw: str = read_text(path)
|
|
214
182
|
return raw, checked_text(raw, name, checks, outside=outside)
|
|
215
183
|
|
|
216
184
|
|
|
@@ -288,9 +256,8 @@ def _handled(path: Path, text: tuple[str, Path], offences: list[Offence], option
|
|
|
288
256
|
baselined: int
|
|
289
257
|
offences, baselined = options.filter.unbaselined(name, offences)
|
|
290
258
|
results: list[Result] = _results(raw, name, offences, options)
|
|
291
|
-
unsafe: bool = options.unsafe_fixes
|
|
292
259
|
fixing: list[Offence] = [
|
|
293
|
-
r.offence for r in results if r.offence.edit is not None and (
|
|
260
|
+
r.offence for r in results if r.offence.edit is not None and options.guesses.applies(r.offence)
|
|
294
261
|
]
|
|
295
262
|
if options.mode is Mode.DIFF:
|
|
296
263
|
return CheckRun(text=_diff(raw, name, fixing))
|
|
@@ -357,7 +324,7 @@ def _cover_path(path: Path, _outside: Outside, options: Options) -> CoverageRun:
|
|
|
357
324
|
"""
|
|
358
325
|
name: Path = options.input.name(path)
|
|
359
326
|
try:
|
|
360
|
-
return CoverageRun(annotation_coverage(_source(
|
|
327
|
+
return CoverageRun(annotation_coverage(_source(read_text(path), name)[0], options.checks))
|
|
361
328
|
except (OSError, ValueError, SyntaxError) as error:
|
|
362
329
|
return CoverageRun(error=f"{name}: error: {error}")
|
|
363
330
|
|
|
@@ -369,6 +336,7 @@ def _check_all(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
369
336
|
The names, and what each file found.
|
|
370
337
|
|
|
371
338
|
"""
|
|
339
|
+
schedule.forget()
|
|
372
340
|
with kept():
|
|
373
341
|
return _check_rounds(options)
|
|
374
342
|
|
|
@@ -378,15 +346,16 @@ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
378
346
|
|
|
379
347
|
With `--infer-with`, the type checker's server runs throughout: it's asked for every file's
|
|
380
348
|
hints first, and with `--fix`, each file a round changed is asked again and checked again, as its
|
|
381
|
-
new annotations change what the checker infers
|
|
382
|
-
|
|
383
|
-
neither, only what `--fix` offers.
|
|
349
|
+
new annotations change what the checker infers (see `settled`). A baseline records offences,
|
|
350
|
+
and coverage counts annotations: hints change neither, only what `--fix` offers.
|
|
384
351
|
|
|
385
352
|
Returns:
|
|
386
353
|
The names, and what each file found.
|
|
387
354
|
|
|
388
355
|
"""
|
|
389
|
-
paths: list[Path] = list(
|
|
356
|
+
paths: list[Path] = list(
|
|
357
|
+
python_files(options.input.paths, options.input.exclude, tracked=options.input.gitignore),
|
|
358
|
+
)
|
|
390
359
|
check: Callable[[Path, Outside], FileRun]
|
|
391
360
|
if options.mode is Mode.COVERAGE:
|
|
392
361
|
check = partial(_cover_path, options=options)
|
|
@@ -396,47 +365,19 @@ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
396
365
|
check = partial(_check_path, options=options)
|
|
397
366
|
names: list[Path] = [options.input.name(path) for path in paths]
|
|
398
367
|
if options.mode in {Mode.COVERAGE, Mode.WRITE_BASELINE}:
|
|
399
|
-
return names,
|
|
368
|
+
return names, checked_all(paths, check, options)[0]
|
|
400
369
|
if not options.infer_with:
|
|
401
|
-
return names,
|
|
370
|
+
return names, settled(paths, check, options, None)
|
|
402
371
|
session: hints.Session
|
|
403
372
|
# Imported only for `--infer-with`: it's slow to import, for every run's start.
|
|
404
|
-
with cast("type[hints.Session]", importlib.import_module("constricter.cli.hints").Session)(
|
|
373
|
+
with cast("type[hints.Session]", importlib.import_module("constricter.cli.lsp.hints").Session)(
|
|
405
374
|
options.infer_with,
|
|
406
375
|
Path.cwd(),
|
|
407
376
|
options.jobs,
|
|
408
377
|
options.infer_memory,
|
|
378
|
+
kept=not options.infer_fresh,
|
|
409
379
|
) as session:
|
|
410
|
-
runs: list[FileRun]
|
|
411
|
-
modules: project.Index
|
|
412
|
-
runs, modules = _called_again(
|
|
413
|
-
paths,
|
|
414
|
-
_checked_all(paths, check, options, session),
|
|
415
|
-
check,
|
|
416
|
-
options,
|
|
417
|
-
session,
|
|
418
|
-
)
|
|
419
|
-
# The files the last round changed: only their hints can have changed.
|
|
420
|
-
again: list[int] = [
|
|
421
|
-
index
|
|
422
|
-
for index, run in enumerate(runs)
|
|
423
|
-
if options.mode is Mode.FIX and isinstance(run, CheckRun) and run.fixed and paths[index] != STDIN
|
|
424
|
-
]
|
|
425
|
-
for _ in range(HINT_ROUNDS - 1):
|
|
426
|
-
if not again:
|
|
427
|
-
break
|
|
428
|
-
redone: list[FileRun] = _checked_all(
|
|
429
|
-
[paths[i] for i in again],
|
|
430
|
-
check,
|
|
431
|
-
options,
|
|
432
|
-
session,
|
|
433
|
-
modules,
|
|
434
|
-
)[0]
|
|
435
|
-
index: int
|
|
436
|
-
run: FileRun
|
|
437
|
-
for index, run in zip(again, redone, strict=True):
|
|
438
|
-
runs[index] = merged(runs[index], run)
|
|
439
|
-
again = [index for index, run in zip(again, redone, strict=True) if cast("CheckRun", run).fixed]
|
|
380
|
+
runs: list[FileRun] = settled(paths, check, options, session)
|
|
440
381
|
checker: str
|
|
441
382
|
path: Path
|
|
442
383
|
for checker, path in session.abandoned:
|
|
@@ -445,148 +386,6 @@ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
445
386
|
return names, runs
|
|
446
387
|
|
|
447
388
|
|
|
448
|
-
def _called_again(
|
|
449
|
-
paths: Sequence[Path],
|
|
450
|
-
first: tuple[list[FileRun], project.Index],
|
|
451
|
-
check: Callable[[Path, Outside], FileRun],
|
|
452
|
-
options: Options,
|
|
453
|
-
session: "hints.Session | None" = None,
|
|
454
|
-
) -> tuple[list[FileRun], project.Index]:
|
|
455
|
-
"""Check again the files whose functions' unannotated parameters every call passes one type.
|
|
456
|
-
|
|
457
|
-
Knowing those types (see `callers`), as guesses; a file among several of one module name isn't
|
|
458
|
-
(which its callers call can't be told). Then the other files calling those functions, knowing
|
|
459
|
-
what they now return. With `--fix` a file's rounds are joined; otherwise the last, knowing more,
|
|
460
|
-
stands.
|
|
461
|
-
|
|
462
|
-
Returns:
|
|
463
|
-
What each file found, and the index, with the parameters' types.
|
|
464
|
-
|
|
465
|
-
"""
|
|
466
|
-
runs: list[FileRun]
|
|
467
|
-
modules: project.Index
|
|
468
|
-
runs, modules = first
|
|
469
|
-
found: dict[str, callers.Parameters] = callers.parameters(
|
|
470
|
-
(run.calls for run in runs if isinstance(run, CheckRun)),
|
|
471
|
-
modules,
|
|
472
|
-
)
|
|
473
|
-
names: list[str | None] = [project.module_name(path) if path.suffix == _PY else None for path in paths]
|
|
474
|
-
again: list[int]
|
|
475
|
-
if not (again := [at for at, name in enumerate(names) if name in found and names.count(name) == 1]):
|
|
476
|
-
return runs, modules
|
|
477
|
-
calling: list[int] = _calling(runs, found, again)
|
|
478
|
-
before: project.Index = callers.with_parameters(modules, found)
|
|
479
|
-
modules = _checked_more(paths, runs, again, (check, options, session), before)
|
|
480
|
-
# A caller is checked again only if a function it imports now returns something else.
|
|
481
|
-
calling = [
|
|
482
|
-
at
|
|
483
|
-
for at in calling
|
|
484
|
-
if loose.returned(modules, paths[at], {}) != loose.returned(before, paths[at], {})
|
|
485
|
-
]
|
|
486
|
-
return runs, _checked_more(paths, runs, calling, (check, options, session), modules)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
def _checked_more(
|
|
490
|
-
paths: Sequence[Path],
|
|
491
|
-
runs: list[FileRun],
|
|
492
|
-
which: Sequence[int],
|
|
493
|
-
how: "tuple[Callable[[Path, Outside], FileRun], Options, hints.Session | None]",
|
|
494
|
-
modules: project.Index,
|
|
495
|
-
) -> project.Index:
|
|
496
|
-
"""Check the files of `paths` at `which` again, knowing `modules`, into `runs`.
|
|
497
|
-
|
|
498
|
-
`how`: the check, the options and the type checkers' session. With `--fix` a file's rounds are
|
|
499
|
-
joined; otherwise the later one stands.
|
|
500
|
-
|
|
501
|
-
Returns:
|
|
502
|
-
The index, with what their functions now return.
|
|
503
|
-
|
|
504
|
-
"""
|
|
505
|
-
redone: list[FileRun]
|
|
506
|
-
redone, modules = _checked_all([paths[at] for at in which], how[0], how[1], how[2], modules)
|
|
507
|
-
at: int
|
|
508
|
-
run: FileRun
|
|
509
|
-
for at, run in zip(which, redone, strict=True):
|
|
510
|
-
runs[at] = merged(runs[at], run) if how[1].mode is Mode.FIX else run
|
|
511
|
-
return modules
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
def _calling(
|
|
515
|
-
runs: Sequence[FileRun],
|
|
516
|
-
found: Mapping[str, callers.Parameters],
|
|
517
|
-
again: Sequence[int],
|
|
518
|
-
) -> list[int]:
|
|
519
|
-
"""Find the files (not among `again`) calling a function whose parameters `found` types.
|
|
520
|
-
|
|
521
|
-
Returns:
|
|
522
|
-
Their indexes.
|
|
523
|
-
|
|
524
|
-
"""
|
|
525
|
-
typed: set[Callee] = {(module, function) for module, functions in found.items() for function in functions}
|
|
526
|
-
return [
|
|
527
|
-
at
|
|
528
|
-
for at, run in enumerate(runs)
|
|
529
|
-
if at not in again and isinstance(run, CheckRun) and typed & run.calls.calls.keys()
|
|
530
|
-
]
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
def _checked_all(
|
|
534
|
-
paths: Sequence[Path],
|
|
535
|
-
check: Callable[[Path, Outside], FileRun],
|
|
536
|
-
options: Options,
|
|
537
|
-
session: "hints.Session | None" = None,
|
|
538
|
-
modules: project.Index | None = None,
|
|
539
|
-
) -> tuple[list[FileRun], project.Index]:
|
|
540
|
-
"""Check `paths` (`--jobs` at a time), with the `session`'s hints, and `modules` (else indexed).
|
|
541
|
-
|
|
542
|
-
Returns:
|
|
543
|
-
What each file found, and the index of every file's module.
|
|
544
|
-
|
|
545
|
-
"""
|
|
546
|
-
# Before a worker starts: one forked while the servers' threads run could wait on a lock one holds.
|
|
547
|
-
hinted: _Hinted = {} if session is None else session.hints(_texts(paths))
|
|
548
|
-
if options.input.trace is not None:
|
|
549
|
-
hinted = traced.merged(hinted, traced.load(options.input.trace), paths)
|
|
550
|
-
coverage: bool = options.mode is Mode.COVERAGE # needs nothing from the other files
|
|
551
|
-
pool: Workers | None = started(paths, options.jobs) if options.jobs != 1 and len(paths) > 1 else None
|
|
552
|
-
if modules is None and pool:
|
|
553
|
-
modules = project.Index({}, []) if coverage else pool.index()
|
|
554
|
-
elif modules is None:
|
|
555
|
-
modules = project.Index({}, []) if coverage else project.index(paths)
|
|
556
|
-
collecting.indexed()
|
|
557
|
-
if not coverage:
|
|
558
|
-
modules = plain.settled(
|
|
559
|
-
decorated.passed(installed.with_installed(modules, installed.search_path())),
|
|
560
|
-
options.checks.plain_bases,
|
|
561
|
-
)
|
|
562
|
-
return schedule.checked(
|
|
563
|
-
paths,
|
|
564
|
-
check,
|
|
565
|
-
pool,
|
|
566
|
-
(modules, hinted),
|
|
567
|
-
merged if options.mode is Mode.FIX else None,
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
def _texts(paths: Sequence[Path]) -> dict[Path, str]:
|
|
572
|
-
"""Read each file to ask the type checker about (not a notebook, or standard input).
|
|
573
|
-
|
|
574
|
-
A file that can't be read is left out: checking it reports that.
|
|
575
|
-
|
|
576
|
-
Returns:
|
|
577
|
-
Each file's text.
|
|
578
|
-
|
|
579
|
-
"""
|
|
580
|
-
texts: dict[Path, str] = {}
|
|
581
|
-
path: Path
|
|
582
|
-
for path in paths:
|
|
583
|
-
if path != STDIN and path.suffix != notebook.SUFFIX:
|
|
584
|
-
# UnicodeDecodeError is a ValueError; a bad encoding declaration, a SyntaxError.
|
|
585
|
-
with contextlib.suppress(OSError, ValueError, SyntaxError):
|
|
586
|
-
texts[path] = _read(path)
|
|
587
|
-
return texts
|
|
588
|
-
|
|
589
|
-
|
|
590
389
|
def _report(options: Options, runs: Sequence[FileRun], files: int) -> int:
|
|
591
390
|
"""Print the results (`runs` is check/fix/diff mode's: every other mode has its own printing).
|
|
592
391
|
|
|
@@ -618,7 +417,10 @@ def _report(options: Options, runs: Sequence[FileRun], files: int) -> int:
|
|
|
618
417
|
parts.append(f"fixed {sum(run.fixed for run in checked)}")
|
|
619
418
|
guesses: int
|
|
620
419
|
if guesses := sum(r.offence.unsafe for r in results):
|
|
621
|
-
|
|
420
|
+
likely: int = sum(r.offence.likely for r in results)
|
|
421
|
+
parts.append(
|
|
422
|
+
f"{guesses} more with --unsafe-fixes" + f" ({likely} with --likely)" * bool(likely),
|
|
423
|
+
)
|
|
622
424
|
if options.filter.baseline_file is not None:
|
|
623
425
|
parts.append(f"{sum(run.baselined for run in checked)} baselined")
|
|
624
426
|
_ = sys.stdout.write("; ".join(parts) + ".\n")
|
constricter/cli/config.py
CHANGED
|
@@ -8,7 +8,7 @@ from functools import partial
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import TYPE_CHECKING, Final, TypeAlias, cast
|
|
10
10
|
|
|
11
|
-
from constricter.cli.protocol import SERVERS
|
|
11
|
+
from constricter.cli.lsp.protocol import SERVERS
|
|
12
12
|
from constricter.jsonc import is_int
|
|
13
13
|
from constricter.offences import FIX_KINDS, LEVELS, MESSAGES, WIDEN_KINDS
|
|
14
14
|
|
|
@@ -276,6 +276,7 @@ _READERS: dict[str, Callable[[_Toml], Default | None]] = {
|
|
|
276
276
|
"fix-plain-bases": _strings,
|
|
277
277
|
"type-comments": _flag,
|
|
278
278
|
"all-scopes": _flag,
|
|
279
|
+
"gitignore": _flag,
|
|
279
280
|
"per-path-levels": _levels,
|
|
280
281
|
"per-file-ignores": partial(_lists, read=_codes),
|
|
281
282
|
"narrower": partial(_lists, read=_strings), # a type hierarchy: each type, and those it's narrower than
|