python-constricter 0.4.2__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_constricter-0.4.2 → python_constricter-0.4.4}/.gitignore +0 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/PKG-INFO +39 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/README.md +38 -10
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/__main__.py +3 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/_version.py +1 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/baseline.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/collecting.py +6 -4
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/command.py +38 -15
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/config.py +7 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/explain.py +21 -1
- python_constricter-0.4.4/constricter/cli/lsp/__init__.py +1 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/connection.py +19 -22
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/edits.py +1 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/guard.py +10 -8
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/hints.py +165 -53
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/protocol.py +39 -16
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/remembered.py +9 -10
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/lsp/stored.py +6 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/options.py +24 -7
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/paths.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/reading.py +5 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/report.py +15 -8
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/rounds.py +72 -27
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/runs.py +34 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/schedule.py +48 -100
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/cli/workers.py +10 -24
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/empty.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/asked.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/fixes.py +16 -4
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/imports.py +83 -58
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/inherited.py +4 -5
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/known.py +40 -27
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/core/signatures.py +2 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/__init__.py +0 -1
- python_constricter-0.4.4/constricter/fix/index/across.py +145 -0
- python_constricter-0.4.4/constricter/fix/index/annotated.py +83 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/awaits.py +11 -12
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/beyond.py +5 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/callers.py +37 -8
- python_constricter-0.4.4/constricter/fix/index/consistent.py +133 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/crossed.py +84 -35
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/decorated.py +21 -9
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/fixtures.py +21 -12
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/linked.py +29 -30
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/loose.py +9 -10
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/managed.py +9 -10
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/modules.py +90 -41
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/offers.py +10 -17
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/order.py +6 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/origins.py +7 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/own_methods.py +3 -4
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/own_overloads.py +9 -5
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/own_types.py +11 -15
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/params.py +6 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/plain.py +3 -4
- python_constricter-0.4.4/constricter/fix/index/project.py +236 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/sides.py +10 -75
- python_constricter-0.4.2/constricter/fix/index/project.py → python_constricter-0.4.4/constricter/fix/index/spelling.py +152 -243
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/strings.py +1 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/atoms.py +36 -25
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/chained.py +2 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/classnames.py +3 -4
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/declared.py +2 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/installed.py +134 -43
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/names.py +8 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/read.py +6 -7
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/signatures.py +11 -14
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/stubs/stubbed.py +10 -12
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/taken.py +10 -7
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/index/tuples.py +7 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/__init__.py +0 -1
- python_constricter-0.4.4/constricter/fix/libraries/generics.py +50 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/library.py +60 -22
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/opened.py +0 -1
- python_constricter-0.4.4/constricter/fix/libraries/operators.py +185 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/overloads.py +90 -224
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/receivers.py +2 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/stdlib.py +55 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/libraries/walks.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/source.json +1 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/aliased.py +8 -19
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/bodies.py +41 -5
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/callables.py +14 -22
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/called.py +20 -24
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/classvars.py +34 -32
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/decided.py +2 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/displays.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/doubts.py +50 -31
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/entered.py +12 -19
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/fills.py +176 -33
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/guesses.py +34 -34
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/hinted.py +181 -41
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/inference.py +87 -55
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/loops.py +5 -8
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/members.py +142 -20
- python_constricter-0.4.4/constricter/fix/values/narrowed.py +483 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/operated.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/ordered.py +1 -2
- python_constricter-0.4.4/constricter/fix/values/reads.py +388 -0
- python_constricter-0.4.4/constricter/fix/values/refused.py +87 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/returned.py +46 -48
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/returns.py +2 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/shapes.py +30 -20
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/stored.py +64 -35
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/fix/values}/tables.py +4 -5
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/targets.py +18 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/values/unchecked.py +27 -14
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/jsonc.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/noqa.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/notebook.py +2 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/offences.py +102 -41
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/overrides.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/plugins/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/plugins/flake8.py +50 -50
- python_constricter-0.4.4/constricter/plugins/options.py +41 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/plugins/pylint.py +36 -65
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/recording.py +63 -23
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/__init__.py +0 -1
- python_constricter-0.4.4/constricter/rules/attributes.py +258 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/binding.py +149 -57
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/calls.py +105 -22
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/checker.py +115 -32
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/late.py +46 -312
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/narrowing.py +3 -4
- python_constricter-0.4.4/constricter/rules/parameters.py +94 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/rebinding.py +1 -2
- python_constricter-0.4.4/constricter/rules/rebound.py +296 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/recorded.py +8 -4
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/redundant.py +2 -3
- python_constricter-0.4.4/constricter/rules/returning.py +251 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/scope.py +186 -38
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/scoped.py +11 -6
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/scoping.py +9 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/settings.py +69 -40
- python_constricter-0.4.4/constricter/rules/signatures.py +77 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/rules/widened.py +10 -8
- python_constricter-0.4.4/constricter/source/__init__.py +1 -0
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/annotations.py +2 -3
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/declarations.py +170 -14
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/decorators.py +11 -30
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/flow.py +1 -2
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/keys.py +1 -2
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/parsed.py +2 -3
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/quoted.py +0 -1
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/syntax.py +99 -46
- {python_constricter-0.4.2/constricter/rules → python_constricter-0.4.4/constricter/source}/walked.py +17 -11
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/trace.py +104 -35
- {python_constricter-0.4.2/constricter/cli → python_constricter-0.4.4/constricter}/traced.py +54 -9
- {python_constricter-0.4.2 → python_constricter-0.4.4}/hatch_build.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/mypyc.txt +3 -3
- {python_constricter-0.4.2 → python_constricter-0.4.4}/pyproject.toml +32 -20
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/__init__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/__main__.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/accepting.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/configured.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/elements.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/forms.py +1 -2
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/generate.py +8 -9
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/overloads.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/reading.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/shed.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/stubs.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/templates.py +0 -1
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/written.py +0 -1
- python_constricter-0.4.2/constricter/cli/lsp/__init__.py +0 -2
- python_constricter-0.4.2/constricter/fix/values/narrowed.py +0 -220
- {python_constricter-0.4.2 → python_constricter-0.4.4}/LICENSE.md +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/aliases.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/attributes.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/awaited.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/awaited_overloads.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/bases.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/classes.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/elements.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/functions.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/generic_attributes.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/method_overloads.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/method_signatures.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/methods.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/overloads.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/platform.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/returns.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/scalar_members.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/scalars.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/subscriptable.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/type_parameters.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/fix/tables/variables.json +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/constricter/py.typed +0 -0
- {python_constricter-0.4.2 → python_constricter-0.4.4}/stdlib_tables/partial.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-constricter
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Import-Name: constricter
|
|
5
5
|
Summary: Lint rules: every local variable is typed where it's first bound. A flake8 plugin, a pylint plugin and a standalone CLI.
|
|
6
6
|
Author: Ivy Duggan
|
|
@@ -105,6 +105,9 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
105
105
|
| `LVA010` | with every value known: a union member no value is | drop the member |
|
|
106
106
|
| `LVA011` | an annotation listing a fixed-length tuple of more than `max-length` types (4 by default) | name the fields (a `NamedTuple`, a dataclass) |
|
|
107
107
|
| `LVA012` | opt-in: a local bound once, outside any loop, and never rebound | `name: Final = ...` |
|
|
108
|
+
| `LVA013` | opt-in: an instance attribute a plain class's method first stores, declared nowhere in it | `self.x: T = ...` |
|
|
109
|
+
| `LVA014` | opt-in: a plain function that declares no return type | `def f(...) -> T:` |
|
|
110
|
+
| `LVA015` | opt-in: a parameter with no annotation | `def f(x: T):` |
|
|
108
111
|
|
|
109
112
|
Exempt: comprehensions, `except ... as`, imports, `def`/`class`, `type` aliases, parameters,
|
|
110
113
|
`global`/`nonlocal`, and `_`; in module and class bodies, dunder names (`__all__`, `__slots__`) and
|
|
@@ -117,7 +120,8 @@ type checker: `T = TypeVar("T")`, a `ParamSpec`, a `TypeVarTuple`, a `NewType`,
|
|
|
117
120
|
A `# type:` comment (`x = 1 # type: int`, `with f() as x: # type: T`) counts as an annotation with
|
|
118
121
|
`type-comments`, or automatically in a module written to run on Python 2: one that imports
|
|
119
122
|
`print_function`, `unicode_literals`, `absolute_import`, `division`, `with_statement`, `generators`
|
|
120
|
-
or `nested_scopes` from `__future__`.
|
|
123
|
+
or `nested_scopes` from `__future__`. Otherwise it's `LVA001`, and `--fix` writes the comment's type
|
|
124
|
+
as the annotation and drops the comment.
|
|
121
125
|
|
|
122
126
|
`LVA007` compares a block on its own: an `if`'s body and its `orelse`, a `try`'s body and its
|
|
123
127
|
`except`s, and the like, are different blocks, since they don't both run in the same pass.
|
|
@@ -154,29 +158,51 @@ or SARIF `warning` in those formats) but exits 0; flake8 and pylint report error
|
|
|
154
158
|
at every level, and an error only at `suffocate`: on the corpus, 45–70% of every codebase's first
|
|
155
159
|
bindings qualify, so it suits a codebase that wants `Final` everywhere it can go, not a default.
|
|
156
160
|
|
|
161
|
+
`LVA013` is opt-in the same way (pylint's `enable = unannotated-instance-attribute`), and an error
|
|
162
|
+
from `strict`, as `LVA001` is. It reports an instance attribute a plain class's method stores
|
|
163
|
+
(`self.count = 0`) that no annotation in the class declares, at its first plain `self.x = value`:
|
|
164
|
+
a class the module defines once, with no keyword (a metaclass), on no base but `object` or another
|
|
165
|
+
such class, since another file's base may declare the attribute itself. Dunders, stores in a
|
|
166
|
+
nested function, and names the class's body or a base of the module's binds are left out.
|
|
167
|
+
|
|
168
|
+
`LVA014` is opt-in too (pylint's `enable = missing-return-type`), an error from `strict`. It
|
|
169
|
+
reports a plain function or method (not decorated) with no return annotation, as ruff's `ANN201`
|
|
170
|
+
does; its fix is what the function's own `return`s give (`-> int`, a method's a guess, as is a
|
|
171
|
+
function's whose `return`s' values are guessed), or `-> None` for one that returns nothing, or what
|
|
172
|
+
a traced run saw it return (`--infer-from`, a guess), where the type names only what the module
|
|
173
|
+
already binds. `LVA015` is opt-in the same way (pylint's
|
|
174
|
+
`enable = missing-parameter-type`): a parameter with no annotation, as ruff's `ANN001` reports it,
|
|
175
|
+
but a method's receiver; its fix, a guess, is what typed the parameter for the function's own
|
|
176
|
+
check: what every call passes it, the one class with every member the function reads of it, its
|
|
177
|
+
pytest fixture's value, or the one type the checked files annotate its name with. A signature is
|
|
178
|
+
written whole or not at all: a return on bare parameters, or one parameter's type among bare ones,
|
|
179
|
+
is an error to a checker held to complete signatures where none was. A signature `--fix` writes
|
|
180
|
+
reaches the other files in the same run.
|
|
181
|
+
|
|
157
182
|
Python 3.11+, no runtime dependencies.
|
|
158
183
|
|
|
159
184
|
## Use
|
|
160
185
|
|
|
161
186
|
| Tool | Setup | Reports | Suppress |
|
|
162
187
|
| ------ | ----------------------------------------------------- | ------------------------------- | ------------------------------------------------ |
|
|
163
|
-
| CLI | `constricter [PATH...] [--level L] [--format F] [-q]` | `LVA001`–`
|
|
164
|
-
| flake8 | install it (on by default) | `LVA001`–`
|
|
165
|
-
| pylint | `load-plugins = ["constricter.plugins.pylint"]` | `C9101`–`
|
|
166
|
-
| ruff | run the CLI after ruff; set `lint.external = ["LVA"]` | `LVA001`–`
|
|
188
|
+
| CLI | `constricter [PATH...] [--level L] [--format F] [-q]` | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
189
|
+
| flake8 | install it (on by default) | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
190
|
+
| pylint | `load-plugins = ["constricter.plugins.pylint"]` | `C9101`–`C9115` (symbols below) | `# noqa: LVA001` or `# pylint: disable=<symbol>` |
|
|
191
|
+
| ruff | run the CLI after ruff; set `lint.external = ["LVA"]` | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
167
192
|
|
|
168
193
|
pylint symbols: `unannotated-local-variable`, `untyped-for-or-match-variable`,
|
|
169
194
|
`comment-typed-for-variable`, `unannotated-module-or-class-variable`, `vague-annotation`,
|
|
170
195
|
`deeply-nested-annotation`, `redundant-annotation`, `narrowable-annotation`,
|
|
171
|
-
`mismatched-value-type`, `unused-union-member`, `long-tuple-annotation`,
|
|
172
|
-
|
|
196
|
+
`mismatched-value-type`, `unused-union-member`, `long-tuple-annotation`, and, off until enabled,
|
|
197
|
+
`could-be-final`, `unannotated-instance-attribute`, `missing-return-type` and
|
|
198
|
+
`missing-parameter-type`.
|
|
173
199
|
|
|
174
200
|
Options:
|
|
175
201
|
|
|
176
202
|
| Option | CLI | `[tool.constricter]` | flake8 (CLI or config) | pylint |
|
|
177
203
|
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
|
|
178
204
|
| level | `--level` | `level` | `--constricter-level` | `constricter-level` |
|
|
179
|
-
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012`
|
|
205
|
+
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012` and `LVA013` | — | — | — |
|
|
180
206
|
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright, ty and pyrefly that's installed and runs | — | — | — |
|
|
181
207
|
| type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
|
|
182
208
|
| all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
|
|
@@ -369,7 +395,7 @@ it to code scanning (the job needs `security-events: write`; the
|
|
|
369
395
|
```yaml
|
|
370
396
|
- run: constricter --format=sarif src > constricter.sarif
|
|
371
397
|
- if: ${{ !cancelled() }} # upload the findings even when the step above failed on them
|
|
372
|
-
uses: github/codeql-action/upload-sarif@
|
|
398
|
+
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
|
373
399
|
with:
|
|
374
400
|
sarif_file: constricter.sarif
|
|
375
401
|
category: constricter
|
|
@@ -386,8 +412,10 @@ are in
|
|
|
386
412
|
|
|
387
413
|
## Roadmap
|
|
388
414
|
|
|
389
|
-
What's
|
|
415
|
+
What's next, by size:
|
|
390
416
|
[docs/ROADMAP.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/ROADMAP.md).
|
|
417
|
+
What's done, by scope:
|
|
418
|
+
[docs/COMPLETED.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/COMPLETED.md).
|
|
391
419
|
|
|
392
420
|
## AI usage
|
|
393
421
|
|
|
@@ -78,6 +78,9 @@ class bodies too. Statements are read in source order, and only a name's first b
|
|
|
78
78
|
| `LVA010` | with every value known: a union member no value is | drop the member |
|
|
79
79
|
| `LVA011` | an annotation listing a fixed-length tuple of more than `max-length` types (4 by default) | name the fields (a `NamedTuple`, a dataclass) |
|
|
80
80
|
| `LVA012` | opt-in: a local bound once, outside any loop, and never rebound | `name: Final = ...` |
|
|
81
|
+
| `LVA013` | opt-in: an instance attribute a plain class's method first stores, declared nowhere in it | `self.x: T = ...` |
|
|
82
|
+
| `LVA014` | opt-in: a plain function that declares no return type | `def f(...) -> T:` |
|
|
83
|
+
| `LVA015` | opt-in: a parameter with no annotation | `def f(x: T):` |
|
|
81
84
|
|
|
82
85
|
Exempt: comprehensions, `except ... as`, imports, `def`/`class`, `type` aliases, parameters,
|
|
83
86
|
`global`/`nonlocal`, and `_`; in module and class bodies, dunder names (`__all__`, `__slots__`) and
|
|
@@ -90,7 +93,8 @@ type checker: `T = TypeVar("T")`, a `ParamSpec`, a `TypeVarTuple`, a `NewType`,
|
|
|
90
93
|
A `# type:` comment (`x = 1 # type: int`, `with f() as x: # type: T`) counts as an annotation with
|
|
91
94
|
`type-comments`, or automatically in a module written to run on Python 2: one that imports
|
|
92
95
|
`print_function`, `unicode_literals`, `absolute_import`, `division`, `with_statement`, `generators`
|
|
93
|
-
or `nested_scopes` from `__future__`.
|
|
96
|
+
or `nested_scopes` from `__future__`. Otherwise it's `LVA001`, and `--fix` writes the comment's type
|
|
97
|
+
as the annotation and drops the comment.
|
|
94
98
|
|
|
95
99
|
`LVA007` compares a block on its own: an `if`'s body and its `orelse`, a `try`'s body and its
|
|
96
100
|
`except`s, and the like, are different blocks, since they don't both run in the same pass.
|
|
@@ -127,29 +131,51 @@ or SARIF `warning` in those formats) but exits 0; flake8 and pylint report error
|
|
|
127
131
|
at every level, and an error only at `suffocate`: on the corpus, 45–70% of every codebase's first
|
|
128
132
|
bindings qualify, so it suits a codebase that wants `Final` everywhere it can go, not a default.
|
|
129
133
|
|
|
134
|
+
`LVA013` is opt-in the same way (pylint's `enable = unannotated-instance-attribute`), and an error
|
|
135
|
+
from `strict`, as `LVA001` is. It reports an instance attribute a plain class's method stores
|
|
136
|
+
(`self.count = 0`) that no annotation in the class declares, at its first plain `self.x = value`:
|
|
137
|
+
a class the module defines once, with no keyword (a metaclass), on no base but `object` or another
|
|
138
|
+
such class, since another file's base may declare the attribute itself. Dunders, stores in a
|
|
139
|
+
nested function, and names the class's body or a base of the module's binds are left out.
|
|
140
|
+
|
|
141
|
+
`LVA014` is opt-in too (pylint's `enable = missing-return-type`), an error from `strict`. It
|
|
142
|
+
reports a plain function or method (not decorated) with no return annotation, as ruff's `ANN201`
|
|
143
|
+
does; its fix is what the function's own `return`s give (`-> int`, a method's a guess, as is a
|
|
144
|
+
function's whose `return`s' values are guessed), or `-> None` for one that returns nothing, or what
|
|
145
|
+
a traced run saw it return (`--infer-from`, a guess), where the type names only what the module
|
|
146
|
+
already binds. `LVA015` is opt-in the same way (pylint's
|
|
147
|
+
`enable = missing-parameter-type`): a parameter with no annotation, as ruff's `ANN001` reports it,
|
|
148
|
+
but a method's receiver; its fix, a guess, is what typed the parameter for the function's own
|
|
149
|
+
check: what every call passes it, the one class with every member the function reads of it, its
|
|
150
|
+
pytest fixture's value, or the one type the checked files annotate its name with. A signature is
|
|
151
|
+
written whole or not at all: a return on bare parameters, or one parameter's type among bare ones,
|
|
152
|
+
is an error to a checker held to complete signatures where none was. A signature `--fix` writes
|
|
153
|
+
reaches the other files in the same run.
|
|
154
|
+
|
|
130
155
|
Python 3.11+, no runtime dependencies.
|
|
131
156
|
|
|
132
157
|
## Use
|
|
133
158
|
|
|
134
159
|
| Tool | Setup | Reports | Suppress |
|
|
135
160
|
| ------ | ----------------------------------------------------- | ------------------------------- | ------------------------------------------------ |
|
|
136
|
-
| CLI | `constricter [PATH...] [--level L] [--format F] [-q]` | `LVA001`–`
|
|
137
|
-
| flake8 | install it (on by default) | `LVA001`–`
|
|
138
|
-
| pylint | `load-plugins = ["constricter.plugins.pylint"]` | `C9101`–`
|
|
139
|
-
| ruff | run the CLI after ruff; set `lint.external = ["LVA"]` | `LVA001`–`
|
|
161
|
+
| CLI | `constricter [PATH...] [--level L] [--format F] [-q]` | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
162
|
+
| flake8 | install it (on by default) | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
163
|
+
| pylint | `load-plugins = ["constricter.plugins.pylint"]` | `C9101`–`C9115` (symbols below) | `# noqa: LVA001` or `# pylint: disable=<symbol>` |
|
|
164
|
+
| ruff | run the CLI after ruff; set `lint.external = ["LVA"]` | `LVA001`–`LVA015` | `# noqa: LVA001` |
|
|
140
165
|
|
|
141
166
|
pylint symbols: `unannotated-local-variable`, `untyped-for-or-match-variable`,
|
|
142
167
|
`comment-typed-for-variable`, `unannotated-module-or-class-variable`, `vague-annotation`,
|
|
143
168
|
`deeply-nested-annotation`, `redundant-annotation`, `narrowable-annotation`,
|
|
144
|
-
`mismatched-value-type`, `unused-union-member`, `long-tuple-annotation`,
|
|
145
|
-
|
|
169
|
+
`mismatched-value-type`, `unused-union-member`, `long-tuple-annotation`, and, off until enabled,
|
|
170
|
+
`could-be-final`, `unannotated-instance-attribute`, `missing-return-type` and
|
|
171
|
+
`missing-parameter-type`.
|
|
146
172
|
|
|
147
173
|
Options:
|
|
148
174
|
|
|
149
175
|
| Option | CLI | `[tool.constricter]` | flake8 (CLI or config) | pylint |
|
|
150
176
|
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------- | --------------------------------- |
|
|
151
177
|
| level | `--level` | `level` | `--constricter-level` | `constricter-level` |
|
|
152
|
-
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012`
|
|
178
|
+
| strictest | `--max`: `--level=suffocate` for every path, `--all-scopes`, and the opt-in `LVA012` and `LVA013` | — | — | — |
|
|
153
179
|
| every fix | `--max-fix`: `--max`, `--fix --unsafe-fixes`, and `--infer-with` each of basedpyright, ty and pyrefly that's installed and runs | — | — | — |
|
|
154
180
|
| type comments | `--type-comments` | `type-comments` | `--constricter-type-comments` | `constricter-type-comments = yes` |
|
|
155
181
|
| all scopes | `--all-scopes` | `all-scopes` | `--constricter-all-scopes` | `constricter-all-scopes = yes` |
|
|
@@ -342,7 +368,7 @@ it to code scanning (the job needs `security-events: write`; the
|
|
|
342
368
|
```yaml
|
|
343
369
|
- run: constricter --format=sarif src > constricter.sarif
|
|
344
370
|
- if: ${{ !cancelled() }} # upload the findings even when the step above failed on them
|
|
345
|
-
uses: github/codeql-action/upload-sarif@
|
|
371
|
+
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
|
346
372
|
with:
|
|
347
373
|
sarif_file: constricter.sarif
|
|
348
374
|
category: constricter
|
|
@@ -359,8 +385,10 @@ are in
|
|
|
359
385
|
|
|
360
386
|
## Roadmap
|
|
361
387
|
|
|
362
|
-
What's
|
|
388
|
+
What's next, by size:
|
|
363
389
|
[docs/ROADMAP.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/ROADMAP.md).
|
|
390
|
+
What's done, by scope:
|
|
391
|
+
[docs/COMPLETED.md](https://github.com/ivylikethevine/python-constricter/blob/main/docs/COMPLETED.md).
|
|
364
392
|
|
|
365
393
|
## AI usage
|
|
366
394
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
1
|
"""`python -m constricter`, and the `constricter` script."""
|
|
3
2
|
|
|
4
3
|
import contextlib
|
|
@@ -13,9 +12,10 @@ def run() -> None:
|
|
|
13
12
|
|
|
14
13
|
Not by the interpreter's own exit, which frees every parsed file and the index an object at a
|
|
15
14
|
time: a fifth of a check of a thousand files. What the command wrote is flushed first; nothing
|
|
16
|
-
else waits on the exit (its workers have ended, its output file is closed).
|
|
15
|
+
else waits on the exit (its workers have ended, its output file is closed). Nor is the collector
|
|
16
|
+
turned on again for it (`leaving`).
|
|
17
17
|
"""
|
|
18
|
-
status: int = main()
|
|
18
|
+
status: int = main(leaving=True)
|
|
19
19
|
with contextlib.suppress(OSError, ValueError): # a reader that left, a stream closed already
|
|
20
20
|
_ = sys.stdout.flush()
|
|
21
21
|
_ = sys.stderr.flush()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
1
|
"""The garbage collector, as the command runs it: by hand, once per file checked.
|
|
3
2
|
|
|
4
3
|
A check keeps every file's tree (see `parsed`), and allocates a great deal for each file, little of it
|
|
@@ -24,9 +23,12 @@ _YOUNGER: Final = not sysconfig.get_config_var("Py_GIL_DISABLED")
|
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
@contextlib.contextmanager
|
|
27
|
-
def by_hand() -> Generator[None]:
|
|
26
|
+
def by_hand(*, leaving: bool = False) -> Generator[None]:
|
|
28
27
|
"""Turn automatic collection off for the command's run, and back as it was after.
|
|
29
28
|
|
|
29
|
+
Not for a process that leaves once the run's done (`leaving`): turned on again, the collector
|
|
30
|
+
looked through all the run left alive, at once (a check of pandas: 3.4s, for nothing).
|
|
31
|
+
|
|
30
32
|
Yields:
|
|
31
33
|
Nothing: the run.
|
|
32
34
|
|
|
@@ -36,9 +38,9 @@ def by_hand() -> Generator[None]:
|
|
|
36
38
|
try:
|
|
37
39
|
yield
|
|
38
40
|
finally:
|
|
39
|
-
if _FREEZES:
|
|
41
|
+
if _FREEZES and not leaving:
|
|
40
42
|
gc.unfreeze()
|
|
41
|
-
if was:
|
|
43
|
+
if was and not leaving:
|
|
42
44
|
gc.enable()
|
|
43
45
|
|
|
44
46
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
1
|
"""The `constricter` command (see README)."""
|
|
3
2
|
|
|
4
3
|
import codecs
|
|
@@ -18,7 +17,7 @@ from constricter.cli import baseline, collecting, schedule
|
|
|
18
17
|
from constricter.cli.lsp.protocol import HintError
|
|
19
18
|
from constricter.cli.options import Mode, Options, Output
|
|
20
19
|
from constricter.cli.paths import STDIN, python_files
|
|
21
|
-
from constricter.cli.reading import encoding_of, read_text
|
|
20
|
+
from constricter.cli.reading import READ_ERRORS, encoding_of, read_text
|
|
22
21
|
from constricter.cli.report import Format, Result, fix_reasons, render, statistics
|
|
23
22
|
from constricter.cli.rounds import checked_all, settled
|
|
24
23
|
from constricter.cli.runs import Added, BaselineRun, CheckRun, CoverageRun, FileRun
|
|
@@ -32,16 +31,17 @@ from constricter.offences import (
|
|
|
32
31
|
Edit,
|
|
33
32
|
Offence,
|
|
34
33
|
)
|
|
35
|
-
from constricter.rules import parsed
|
|
36
34
|
from constricter.rules.checker import Checked, Coverage, annotation_coverage, checked_source
|
|
35
|
+
from constricter.source import parsed
|
|
37
36
|
|
|
38
37
|
if TYPE_CHECKING: # slow to import, and only needed for `--infer-with`
|
|
38
|
+
from types import ModuleType
|
|
39
|
+
|
|
39
40
|
from constricter.cli.lsp import hints
|
|
40
41
|
|
|
41
42
|
EXIT_CLEAN: Final = 0
|
|
42
43
|
EXIT_FOUND: Final = 1
|
|
43
44
|
EXIT_ERROR: Final = 2
|
|
44
|
-
_ALL: Final = 100 # percent
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def _source(raw: str, name: Path) -> tuple[str, list[notebook.Line]]:
|
|
@@ -198,11 +198,21 @@ def _read_checked(
|
|
|
198
198
|
checked: Checked
|
|
199
199
|
try:
|
|
200
200
|
raw, checked = _checked(path, name, checks, outside)
|
|
201
|
-
except
|
|
202
|
-
return "", Checked([], Returns()),
|
|
201
|
+
except READ_ERRORS as error:
|
|
202
|
+
return "", Checked([], Returns()), _error(name, error)
|
|
203
203
|
return raw, checked, ""
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
def _error(name: Path, error: Exception) -> str:
|
|
207
|
+
"""Say why a file couldn't be read or parsed.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The message.
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
return f"{name}: error: {error}"
|
|
214
|
+
|
|
215
|
+
|
|
206
216
|
def _results(raw: str, name: Path, offences: Sequence[Offence], options: Options) -> list[Result]:
|
|
207
217
|
"""Turn the offences in `raw`, the text of `name`, into the results to report.
|
|
208
218
|
|
|
@@ -325,8 +335,8 @@ def _cover_path(path: Path, _outside: Outside, options: Options) -> CoverageRun:
|
|
|
325
335
|
name: Path = options.input.name(path)
|
|
326
336
|
try:
|
|
327
337
|
return CoverageRun(annotation_coverage(_source(read_text(path), name)[0], options.checks))
|
|
328
|
-
except
|
|
329
|
-
return CoverageRun(error=
|
|
338
|
+
except READ_ERRORS as error:
|
|
339
|
+
return CoverageRun(error=_error(name, error))
|
|
330
340
|
|
|
331
341
|
|
|
332
342
|
def _check_all(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
@@ -370,12 +380,17 @@ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
370
380
|
return names, settled(paths, check, options, None)
|
|
371
381
|
session: hints.Session
|
|
372
382
|
# Imported only for `--infer-with`: it's slow to import, for every run's start.
|
|
373
|
-
|
|
383
|
+
hinting: ModuleType = importlib.import_module("constricter.cli.lsp.hints")
|
|
384
|
+
asking: hints.Asking = cast("type[hints.Asking]", hinting.Asking)(
|
|
385
|
+
kept=not options.infer_fresh,
|
|
386
|
+
in_turn=options.infer_in_turn,
|
|
387
|
+
)
|
|
388
|
+
with cast("type[hints.Session]", hinting.Session)(
|
|
374
389
|
options.infer_with,
|
|
375
390
|
Path.cwd(),
|
|
376
391
|
options.jobs,
|
|
377
392
|
options.infer_memory,
|
|
378
|
-
|
|
393
|
+
asking,
|
|
379
394
|
) as session:
|
|
380
395
|
runs: list[FileRun] = settled(paths, check, options, session)
|
|
381
396
|
checker: str
|
|
@@ -383,6 +398,11 @@ def _check_rounds(options: Options) -> tuple[list[Path], list[FileRun]]:
|
|
|
383
398
|
for checker, path in session.abandoned:
|
|
384
399
|
name: Path = options.input.name(path)
|
|
385
400
|
_ = sys.stderr.write(f"constricter: warning: {checker} hung on {name} twice: no hints for it\n")
|
|
401
|
+
read: int
|
|
402
|
+
rounds: int
|
|
403
|
+
for checker, read, rounds in session.reused:
|
|
404
|
+
told: str = f"{checker}'s hints for {read} of {rounds} rounds were read from the store"
|
|
405
|
+
_ = sys.stderr.write(f"constricter: note: {told} (`--infer-fresh` asks again)\n")
|
|
386
406
|
return names, runs
|
|
387
407
|
|
|
388
408
|
|
|
@@ -485,8 +505,8 @@ def _typed(found: Coverage) -> str:
|
|
|
485
505
|
return f"{found.typed}/{found.total} typed ({found.percent:.1f}%){wider}"
|
|
486
506
|
|
|
487
507
|
|
|
488
|
-
def _run(options: Options) -> int:
|
|
489
|
-
"""Check, fix, diff, count or baseline, printing what that finds.
|
|
508
|
+
def _run(options: Options, *, leaving: bool) -> int:
|
|
509
|
+
"""Check, fix, diff, count or baseline, printing what that finds (`leaving`: see `main`).
|
|
490
510
|
|
|
491
511
|
Returns:
|
|
492
512
|
The exit status.
|
|
@@ -494,7 +514,7 @@ def _run(options: Options) -> int:
|
|
|
494
514
|
"""
|
|
495
515
|
names: list[Path]
|
|
496
516
|
runs: list[FileRun]
|
|
497
|
-
with collecting.by_hand():
|
|
517
|
+
with collecting.by_hand(leaving=leaving):
|
|
498
518
|
try:
|
|
499
519
|
names, runs = _check_all(options)
|
|
500
520
|
except HintError as error:
|
|
@@ -527,9 +547,12 @@ def _run(options: Options) -> int:
|
|
|
527
547
|
return EXIT_ERROR if failed else status
|
|
528
548
|
|
|
529
549
|
|
|
530
|
-
def main(argv: Sequence[str] | None = None) -> int:
|
|
550
|
+
def main(argv: Sequence[str] | None = None, *, leaving: bool = False) -> int:
|
|
531
551
|
"""Run the command.
|
|
532
552
|
|
|
553
|
+
`leaving`: the process leaves once it's done (`constricter.__main__.run`), its collector as the
|
|
554
|
+
run left it (see `collecting.by_hand`).
|
|
555
|
+
|
|
533
556
|
Returns:
|
|
534
557
|
Its exit status.
|
|
535
558
|
|
|
@@ -543,5 +566,5 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
543
566
|
else contextlib.nullcontext(sys.stdout) as stream,
|
|
544
567
|
contextlib.redirect_stdout(stream),
|
|
545
568
|
):
|
|
546
|
-
status: int = _run(options)
|
|
569
|
+
status: int = _run(options, leaving=leaving)
|
|
547
570
|
return EXIT_CLEAN if options.output.exit_zero and status == EXIT_FOUND else status
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
1
|
"""The command's defaults, from the nearest `pyproject.toml`'s `[tool.constricter]` table."""
|
|
3
2
|
|
|
4
3
|
import re
|
|
@@ -221,11 +220,13 @@ def _checkers(value: _Toml) -> list[str] | None:
|
|
|
221
220
|
"""Read `infer-with`: a checker, or a list of them, each one it knows.
|
|
222
221
|
|
|
223
222
|
Returns:
|
|
224
|
-
Them,
|
|
223
|
+
Them, each once in the order they're preferred, as `--infer-with` gives them; `None` for
|
|
224
|
+
anything else.
|
|
225
225
|
|
|
226
226
|
"""
|
|
227
227
|
checkers: list[str] | None = [value] if isinstance(value, str) else _strings(value)
|
|
228
|
-
|
|
228
|
+
known: bool = bool(checkers) and all(checker in SERVERS for checker in checkers or ())
|
|
229
|
+
return list(dict.fromkeys(checkers)) if checkers and known else None
|
|
229
230
|
|
|
230
231
|
|
|
231
232
|
def _levels(value: _Toml) -> dict[str, str] | None:
|
|
@@ -248,7 +249,7 @@ def _lists(value: _Toml, read: Callable[[_Toml], list[str] | None]) -> dict[str,
|
|
|
248
249
|
return None if None in lists.values() else {key: list(item or []) for key, item in lists.items()}
|
|
249
250
|
|
|
250
251
|
|
|
251
|
-
def
|
|
252
|
+
def _path(value: _Toml, root: Path) -> str | None:
|
|
252
253
|
"""Read a baseline's or a trace's path, relative to `root` (the pyproject.toml that names it).
|
|
253
254
|
|
|
254
255
|
Returns:
|
|
@@ -303,8 +304,8 @@ def config_defaults(start: Path) -> dict[str, Default]:
|
|
|
303
304
|
return {}
|
|
304
305
|
readers: dict[str, Callable[[_Toml], Default | None]] = {
|
|
305
306
|
**_READERS,
|
|
306
|
-
"baseline": partial(
|
|
307
|
-
"infer-from": partial(
|
|
307
|
+
"baseline": partial(_path, root=path.parent),
|
|
308
|
+
"infer-from": partial(_path, root=path.parent),
|
|
308
309
|
}
|
|
309
310
|
defaults: dict[str, Default] = {}
|
|
310
311
|
key: str
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
1
|
"""Each code's rationale and fix, for `constricter --explain`."""
|
|
3
2
|
|
|
4
3
|
from typing import Final
|
|
@@ -14,7 +13,10 @@ from constricter.offences import (
|
|
|
14
13
|
OPT_IN,
|
|
15
14
|
REDUNDANT_TYPE,
|
|
16
15
|
UNANNOTATED,
|
|
16
|
+
UNANNOTATED_ATTRIBUTE,
|
|
17
17
|
UNANNOTATED_MEMBER,
|
|
18
|
+
UNANNOTATED_PARAMETER,
|
|
19
|
+
UNANNOTATED_RETURN,
|
|
18
20
|
UNTYPED_TARGET,
|
|
19
21
|
UNUSED_UNION_MEMBER,
|
|
20
22
|
VAGUE_TYPE,
|
|
@@ -83,6 +85,24 @@ _WHY: Final = {
|
|
|
83
85
|
"(not from a nested function either) could say so: `limit: Final = 10`. Off unless selected\n"
|
|
84
86
|
"by its full code (`--extend-select LVA012`): most locals are bound once."
|
|
85
87
|
),
|
|
88
|
+
UNANNOTATED_ATTRIBUTE: (
|
|
89
|
+
"An instance attribute a plain class's method first stores (`self.count = 0`), declared\n"
|
|
90
|
+
"nowhere in the class, is typed by whatever a type checker infers from that store. Annotate\n"
|
|
91
|
+
"it there: `self.count: int = 0`. Off unless selected by its full code (`--extend-select LVA013`)."
|
|
92
|
+
),
|
|
93
|
+
UNANNOTATED_RETURN: (
|
|
94
|
+
"A plain function with no return annotation leaves its callers to whatever a type checker\n"
|
|
95
|
+
"infers. `--fix` writes what its own `return`s give (`-> int`), or `-> None` for one that\n"
|
|
96
|
+
"returns nothing. Off unless selected by its full code (`--extend-select LVA014`): ruff's ANN201\n"
|
|
97
|
+
"reports it too; this is for the fix."
|
|
98
|
+
),
|
|
99
|
+
UNANNOTATED_PARAMETER: (
|
|
100
|
+
"A parameter with no annotation leaves the function's body to whatever a type checker\n"
|
|
101
|
+
"infers of it. `--fix` writes what typed it from outside, a guess: what every call passes it,\n"
|
|
102
|
+
"the one class with every member the function reads of it, its pytest fixture's value, or\n"
|
|
103
|
+
"the one type the checked files annotate its name with. Off unless selected by its full code\n"
|
|
104
|
+
"(`--extend-select LVA015`): ruff's ANN001 reports it too; this is for the fix."
|
|
105
|
+
),
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""constricter.cli.lsp."""
|