vibecodedshit 0.0.0-stage → 0.3.0
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.
- package/LICENSE +95 -0
- package/README.md +73 -2
- package/assets/fonts/NotoEmoji-OFL.txt +93 -0
- package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
- package/dist/bin.js +11 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +138 -0
- package/dist/commands/card.js +69 -0
- package/dist/commands/common.js +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/footer.js +28 -0
- package/dist/commands/report.js +49 -0
- package/dist/commands/route.js +62 -0
- package/dist/commands/submit.js +125 -0
- package/dist/commands/summary.js +77 -0
- package/dist/diff/compare.js +73 -0
- package/dist/diff/conditions.js +87 -0
- package/dist/diff/format.js +23 -0
- package/dist/exec/docker.js +158 -0
- package/dist/exec/findings.js +73 -0
- package/dist/exec/plan.js +50 -0
- package/dist/exec/reports.js +90 -0
- package/dist/exec/roots.js +49 -0
- package/dist/exec/run.js +182 -0
- package/dist/exec/steps/build.js +45 -0
- package/dist/exec/steps/coverage-install.js +12 -0
- package/dist/exec/steps/install.js +41 -0
- package/dist/exec/steps/knip.js +28 -0
- package/dist/exec/steps/lint.js +25 -0
- package/dist/exec/steps/lockfiles.js +25 -0
- package/dist/exec/steps/test.js +17 -0
- package/dist/exec/steps/typecheck.js +40 -0
- package/dist/exec/test-reports/coverage-py.js +23 -0
- package/dist/exec/test-reports/coverage-ranges.js +65 -0
- package/dist/exec/test-reports/coverage-summary.js +23 -0
- package/dist/exec/test-reports/jest-json.js +31 -0
- package/dist/exec/test-reports/junit.js +24 -0
- package/dist/exec/test-reports/mocha-json.js +18 -0
- package/dist/exec/test-reports/vitest-json.js +5 -0
- package/dist/exec/test-runners/detect.js +99 -0
- package/dist/exec/test-runners/index.js +6 -0
- package/dist/exec/test-runners/jest.js +27 -0
- package/dist/exec/test-runners/mocha.js +28 -0
- package/dist/exec/test-runners/node-test.js +24 -0
- package/dist/exec/test-runners/pytest.js +18 -0
- package/dist/exec/test-runners/script.js +48 -0
- package/dist/exec/test-runners/spec.js +1 -0
- package/dist/exec/test-runners/vitest.js +28 -0
- package/dist/exec/tests.js +120 -0
- package/dist/exec/types.js +1 -0
- package/dist/interactive/after-scan.js +121 -0
- package/dist/interactive/docker-setup.js +97 -0
- package/dist/interactive/prompt.js +17 -0
- package/dist/interactive/system.js +10 -0
- package/dist/manifest/package-json.js +36 -0
- package/dist/manifest/python.js +106 -0
- package/dist/measure/d3/index.js +45 -0
- package/dist/measure/d3/presets.js +100 -0
- package/dist/measure/d3/py/adapter.js +67 -0
- package/dist/measure/d3/py/config.js +80 -0
- package/dist/measure/d3/py/contracts.js +57 -0
- package/dist/measure/d3/py/domain.js +57 -0
- package/dist/measure/d3/py/graph.js +214 -0
- package/dist/measure/d3/py/interpreter.js +31 -0
- package/dist/measure/d3/py/layers.js +120 -0
- package/dist/measure/d3/py/roots.js +58 -0
- package/dist/measure/d3/py/types.js +1 -0
- package/dist/measure/d3/scc.js +64 -0
- package/dist/measure/d3/ts/adapter.js +371 -0
- package/dist/measure/d3/ts/classify.js +107 -0
- package/dist/measure/d3/ts/config.js +318 -0
- package/dist/measure/d3/ts/domain.js +23 -0
- package/dist/measure/d3/ts/rules.js +65 -0
- package/dist/measure/d3/types.js +5 -0
- package/dist/measure/deps/cvss.js +66 -0
- package/dist/measure/deps/declared.js +195 -0
- package/dist/measure/deps/files.js +40 -0
- package/dist/measure/deps/lockfiles.js +308 -0
- package/dist/measure/deps/osv.js +122 -0
- package/dist/measure/deps/registry.js +142 -0
- package/dist/measure/deps/resolved.js +78 -0
- package/dist/measure/deps/units.js +5 -0
- package/dist/measure/eslint/health-plugin.js +19 -0
- package/dist/measure/ops/docs-commands.js +73 -0
- package/dist/measure/ops/docs-resolve.js +217 -0
- package/dist/measure/ops/entry-points.js +49 -0
- package/dist/measure/ops/env-example.js +20 -0
- package/dist/measure/ops/env-reads.js +46 -0
- package/dist/measure/ops/runtime.js +73 -0
- package/dist/measure/p1-1.js +8 -0
- package/dist/measure/p1-2.js +8 -0
- package/dist/measure/p1-3.js +8 -0
- package/dist/measure/p1-4.js +41 -0
- package/dist/measure/p1-5.js +59 -0
- package/dist/measure/p2-1.js +8 -0
- package/dist/measure/p2-2.js +44 -0
- package/dist/measure/p2-3.js +81 -0
- package/dist/measure/p2-4.js +288 -0
- package/dist/measure/p2-5.js +8 -0
- package/dist/measure/p3-1.js +16 -0
- package/dist/measure/p3-2.js +26 -0
- package/dist/measure/p3-3.js +37 -0
- package/dist/measure/p3-4.js +26 -0
- package/dist/measure/p3-5.js +26 -0
- package/dist/measure/p4-1.js +184 -0
- package/dist/measure/p4-2.js +27 -0
- package/dist/measure/p4-3.js +106 -0
- package/dist/measure/p4-4.js +309 -0
- package/dist/measure/p5-1.js +117 -0
- package/dist/measure/p5-2.js +80 -0
- package/dist/measure/p5-3.js +21 -0
- package/dist/measure/p5-4.js +161 -0
- package/dist/measure/p5-5.js +158 -0
- package/dist/measure/p6-1.js +20 -0
- package/dist/measure/p6-2.js +20 -0
- package/dist/measure/p6-3.js +40 -0
- package/dist/measure/p6-4.js +31 -0
- package/dist/measure/p6-5.js +60 -0
- package/dist/measure/p6-6.js +54 -0
- package/dist/measure/py/typecheck-config.js +95 -0
- package/dist/measure/registry.js +31 -0
- package/dist/measure/safety/py-tests.js +124 -0
- package/dist/measure/safety/ts-barrels.js +68 -0
- package/dist/measure/safety/ts-cases.js +111 -0
- package/dist/measure/safety/ts-credits.js +101 -0
- package/dist/measure/safety/ts-imports.js +63 -0
- package/dist/measure/safety/ts-resolve.js +43 -0
- package/dist/measure/ts/directives.js +37 -0
- package/dist/measure/ts/exports.js +60 -0
- package/dist/measure/ts/tsconfig.js +179 -0
- package/dist/measure/types.js +1 -0
- package/dist/measure/units.js +41 -0
- package/dist/methodology/load.js +159 -0
- package/dist/methodology/schema.js +105 -0
- package/dist/python/deadcode.js +27 -0
- package/dist/python/env.js +13 -0
- package/dist/python/helper.js +24 -0
- package/dist/python/imports.js +40 -0
- package/dist/python/nox.js +16 -0
- package/dist/python/scope.js +35 -0
- package/dist/python/tests.js +19 -0
- package/dist/python/typing.js +27 -0
- package/dist/report/escape.js +5 -0
- package/dist/report/format.js +75 -0
- package/dist/report/render.js +213 -0
- package/dist/report/roadmap.js +58 -0
- package/dist/report/scares.js +83 -0
- package/dist/report/style.js +27 -0
- package/dist/report-only/hotspots.js +48 -0
- package/dist/route/check.js +37 -0
- package/dist/route/files.js +74 -0
- package/dist/route/order.js +22 -0
- package/dist/route/select.js +114 -0
- package/dist/route/write.js +13 -0
- package/dist/run/read.js +89 -0
- package/dist/run/recompute.js +19 -0
- package/dist/run/scan.js +196 -0
- package/dist/run/schema.js +1 -0
- package/dist/run/score-run.js +42 -0
- package/dist/run/write.js +4 -0
- package/dist/scope/build.js +173 -0
- package/dist/scope/packages.js +87 -0
- package/dist/scope/rules.js +21 -0
- package/dist/scope/tracked.js +34 -0
- package/dist/scope/ts-lines.js +63 -0
- package/dist/scope/types.js +7 -0
- package/dist/scoring/aggregate.js +38 -0
- package/dist/scoring/pool.js +65 -0
- package/dist/scoring/score.js +81 -0
- package/dist/scoring/what-if.js +38 -0
- package/dist/scripts/checks.js +53 -0
- package/dist/scripts/ci.js +139 -0
- package/dist/scripts/entry-points/find.js +31 -0
- package/dist/scripts/entry-points/index.js +18 -0
- package/dist/scripts/entry-points/justfile.js +62 -0
- package/dist/scripts/entry-points/makefile.js +66 -0
- package/dist/scripts/entry-points/nox.js +29 -0
- package/dist/scripts/entry-points/package-json.js +21 -0
- package/dist/scripts/entry-points/pyproject.js +73 -0
- package/dist/scripts/entry-points/tox.js +130 -0
- package/dist/scripts/expand.js +300 -0
- package/dist/scripts/triggers.js +151 -0
- package/dist/scripts/types.js +1 -0
- package/dist/submit/constants.js +10 -0
- package/dist/submit/payload.js +42 -0
- package/dist/submit/repo-identity.js +46 -0
- package/dist/submit/send.js +29 -0
- package/dist/tools/catalog.js +46 -0
- package/dist/tools/image.js +180 -0
- package/dist/tools/kit.js +71 -0
- package/dist/tools/knip.js +169 -0
- package/dist/tools/outcome.js +21 -0
- package/dist/tools/resolve.js +66 -0
- package/dist/tools/workcopy.js +27 -0
- package/dist/util/ini.js +37 -0
- package/dist/util/pep440.js +163 -0
- package/dist/util/toml.js +27 -0
- package/docker/Dockerfile +142 -0
- package/docker/bin/health-capture +5 -0
- package/docker/bin/health-coverage-install +5 -0
- package/docker/bin/health-eslint +66 -0
- package/docker/bin/health-pytest +10 -0
- package/docker/bin/health-tsc +10 -0
- package/docker/bin/health-venv +11 -0
- package/docker/config/eslint.config.mjs +4 -0
- package/docker/config/eslint.rules.mjs +65 -0
- package/docker/config/eslint.untyped.config.mjs +4 -0
- package/docker/config/knip.json +1 -0
- package/docker/config/ruff.toml +7 -0
- package/methodology/LICENSE +396 -0
- package/methodology/v1.0/METHODOLOGY.md +1176 -0
- package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
- package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
- package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
- package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
- package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
- package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
- package/methodology/v1.0/methodology.yaml +242 -0
- package/methodology/v1.0/presets/django.yaml +43 -0
- package/methodology/v1.0/presets/express.yaml +47 -0
- package/methodology/v1.0/presets/fastapi.yaml +52 -0
- package/methodology/v1.0/presets/generic.yaml +41 -0
- package/methodology/v1.0/presets/nextjs.yaml +50 -0
- package/methodology/v1.0/scope.yaml +115 -0
- package/package.json +61 -4
- package/python/dead_code_helper.py +265 -0
- package/python/env_helper.py +71 -0
- package/python/graph_helper.py +277 -0
- package/python/imports_helper.py +132 -0
- package/python/nox_helper.py +79 -0
- package/python/scope_helper.py +96 -0
- package/python/tests_helper.py +171 -0
- package/python/typing_helper.py +235 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Import and test-case facts for Python files (1.4 and 1.5). Standard library only.
|
|
2
|
+
|
|
3
|
+
Reads {"files": [relative paths]} as JSON on stdin and prints
|
|
4
|
+
{path: {"ok": false}
|
|
5
|
+
| {"ok": true, "imports": [...], "defined": [...], "reexports": [...], "cases": [...]}}.
|
|
6
|
+
|
|
7
|
+
imports: every import statement at any nesting depth:
|
|
8
|
+
{"module": str | null, "level": int, "names": [{"name", "asname"}], "star": bool}
|
|
9
|
+
("import a.b" has module "a.b", level 0 and no names; "from . import x" has module null and level 1).
|
|
10
|
+
defined: names defined at module level (functions, classes, assignments).
|
|
11
|
+
reexports: module-level "from m import a as b" bindings (including inside top-level if/try/with):
|
|
12
|
+
{"name": bound name, "module", "level", "orig"}; used for __init__.py re-exports.
|
|
13
|
+
cases: test_* functions at module level or directly in a class body: {"name", "line", "asserts": bool}.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
NESTED = (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda, ast.ClassDef)
|
|
21
|
+
DEFS = (ast.FunctionDef, ast.AsyncFunctionDef)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def read_tree(path):
|
|
25
|
+
with open(path, "rb") as handle:
|
|
26
|
+
return ast.parse(handle.read().decode("utf-8", errors="replace"), filename=path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def target_names(target):
|
|
30
|
+
if isinstance(target, ast.Name):
|
|
31
|
+
return [target.id]
|
|
32
|
+
if isinstance(target, (ast.Tuple, ast.List)):
|
|
33
|
+
return [n for t in target.elts for n in target_names(t)]
|
|
34
|
+
if isinstance(target, ast.Starred):
|
|
35
|
+
return target_names(target.value)
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def top_level(nodes):
|
|
40
|
+
"""Statements at module level, descending into if/try/with but not into definitions."""
|
|
41
|
+
for node in nodes:
|
|
42
|
+
yield node
|
|
43
|
+
if isinstance(node, ast.If):
|
|
44
|
+
yield from top_level(node.body + node.orelse)
|
|
45
|
+
elif isinstance(node, ast.Try):
|
|
46
|
+
yield from top_level(node.body + node.orelse + node.finalbody)
|
|
47
|
+
for handler in node.handlers:
|
|
48
|
+
yield from top_level(handler.body)
|
|
49
|
+
elif isinstance(node, (ast.With, ast.AsyncWith)):
|
|
50
|
+
yield from top_level(node.body)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def pytest_names(tree):
|
|
54
|
+
modules, members = set(), set()
|
|
55
|
+
for node in ast.walk(tree):
|
|
56
|
+
if isinstance(node, ast.Import):
|
|
57
|
+
for alias in node.names:
|
|
58
|
+
if alias.name == "pytest":
|
|
59
|
+
modules.add(alias.asname or "pytest")
|
|
60
|
+
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module == "pytest":
|
|
61
|
+
for alias in node.names:
|
|
62
|
+
if alias.name in ("raises", "warns"):
|
|
63
|
+
members.add(alias.asname or alias.name)
|
|
64
|
+
return modules, members
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def walk_body(nodes):
|
|
68
|
+
"""Every node of a body, not descending into nested def, lambda or class bodies."""
|
|
69
|
+
stack = list(reversed(nodes))
|
|
70
|
+
while stack:
|
|
71
|
+
node = stack.pop()
|
|
72
|
+
yield node
|
|
73
|
+
if isinstance(node, NESTED):
|
|
74
|
+
continue
|
|
75
|
+
stack.extend(reversed(list(ast.iter_child_nodes(node))))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_assertion(node, pytest_modules, pytest_members):
|
|
79
|
+
if isinstance(node, ast.Assert):
|
|
80
|
+
return True
|
|
81
|
+
if not isinstance(node, ast.Call):
|
|
82
|
+
return False
|
|
83
|
+
func = node.func
|
|
84
|
+
if isinstance(func, ast.Attribute):
|
|
85
|
+
if isinstance(func.value, ast.Name) and func.value.id == "self" and func.attr.startswith("assert"):
|
|
86
|
+
return True
|
|
87
|
+
if func.attr in ("raises", "warns") and isinstance(func.value, ast.Name) and func.value.id in pytest_modules:
|
|
88
|
+
return True
|
|
89
|
+
if func.attr.startswith("assert_"):
|
|
90
|
+
return True
|
|
91
|
+
return isinstance(func, ast.Name) and func.id in pytest_members
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def direct_assertion(body, pm, pf):
|
|
95
|
+
return any(is_assertion(n, pm, pf) for n in walk_body(body))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def case_asserts(func, class_methods, module_funcs, pm, pf):
|
|
99
|
+
nodes = list(walk_body(func.body))
|
|
100
|
+
if any(is_assertion(n, pm, pf) for n in nodes):
|
|
101
|
+
return True
|
|
102
|
+
inner = {n.name: n for n in nodes if isinstance(n, DEFS)}
|
|
103
|
+
for node in nodes:
|
|
104
|
+
if not isinstance(node, ast.Call):
|
|
105
|
+
continue
|
|
106
|
+
helper = None
|
|
107
|
+
if isinstance(node.func, ast.Name):
|
|
108
|
+
helper = inner.get(node.func.id) or module_funcs.get(node.func.id)
|
|
109
|
+
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) and node.func.value.id == "self":
|
|
110
|
+
helper = class_methods.get(node.func.attr)
|
|
111
|
+
if helper is not None and helper is not func and direct_assertion(helper.body, pm, pf):
|
|
112
|
+
return True
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def analyze(path):
|
|
117
|
+
tree = read_tree(path)
|
|
118
|
+
pm, pf = pytest_names(tree)
|
|
119
|
+
imports = []
|
|
120
|
+
for node in ast.walk(tree):
|
|
121
|
+
if isinstance(node, ast.Import):
|
|
122
|
+
for alias in node.names:
|
|
123
|
+
imports.append({"module": alias.name, "level": 0, "names": [], "star": False, "asname": alias.asname})
|
|
124
|
+
elif isinstance(node, ast.ImportFrom):
|
|
125
|
+
imports.append({
|
|
126
|
+
"module": node.module,
|
|
127
|
+
"level": node.level,
|
|
128
|
+
"names": [{"name": a.name, "asname": a.asname} for a in node.names if a.name != "*"],
|
|
129
|
+
"star": any(a.name == "*" for a in node.names),
|
|
130
|
+
})
|
|
131
|
+
defined, reexports = [], []
|
|
132
|
+
module_funcs = {}
|
|
133
|
+
for node in top_level(tree.body):
|
|
134
|
+
if isinstance(node, DEFS):
|
|
135
|
+
defined.append(node.name)
|
|
136
|
+
module_funcs[node.name] = node
|
|
137
|
+
elif isinstance(node, ast.ClassDef):
|
|
138
|
+
defined.append(node.name)
|
|
139
|
+
elif isinstance(node, ast.Assign):
|
|
140
|
+
for target in node.targets:
|
|
141
|
+
defined.extend(target_names(target))
|
|
142
|
+
elif isinstance(node, (ast.AnnAssign, ast.AugAssign)):
|
|
143
|
+
defined.extend(target_names(node.target))
|
|
144
|
+
elif isinstance(node, ast.ImportFrom):
|
|
145
|
+
for alias in node.names:
|
|
146
|
+
if alias.name != "*":
|
|
147
|
+
reexports.append({"name": alias.asname or alias.name, "module": node.module, "level": node.level, "orig": alias.name})
|
|
148
|
+
cases = []
|
|
149
|
+
for node in tree.body:
|
|
150
|
+
if isinstance(node, DEFS) and node.name.startswith("test_"):
|
|
151
|
+
cases.append({"name": node.name, "line": node.lineno, "asserts": case_asserts(node, {}, module_funcs, pm, pf)})
|
|
152
|
+
elif isinstance(node, ast.ClassDef):
|
|
153
|
+
methods = {n.name: n for n in node.body if isinstance(n, DEFS)}
|
|
154
|
+
for method in methods.values():
|
|
155
|
+
if method.name.startswith("test_"):
|
|
156
|
+
cases.append({"name": f"{node.name}.{method.name}", "line": method.lineno, "asserts": case_asserts(method, methods, module_funcs, pm, pf)})
|
|
157
|
+
return {"ok": True, "imports": imports, "defined": sorted(set(defined)), "reexports": reexports, "cases": cases}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def main():
|
|
161
|
+
request = json.load(sys.stdin)
|
|
162
|
+
out = {}
|
|
163
|
+
for path in request["files"]:
|
|
164
|
+
try:
|
|
165
|
+
out[path] = analyze(path)
|
|
166
|
+
except (SyntaxError, ValueError, OSError, RecursionError):
|
|
167
|
+
out[path] = {"ok": False}
|
|
168
|
+
json.dump(out, sys.stdout)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
main()
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Typing facts for Python files. Standard library only.
|
|
2
|
+
|
|
3
|
+
Reads {"files": [relative paths]} as JSON on stdin and prints
|
|
4
|
+
{path: {"ok": false}
|
|
5
|
+
| {"ok": true, "points": {...}, "allNames": [...] | null, "defs": [...]}}
|
|
6
|
+
as JSON on stdout. The helper only extracts facts; counting and publicness rules are applied by the caller.
|
|
7
|
+
|
|
8
|
+
points: anyCount, castCount, typeIgnoreLine, typeIgnoreHeader, pyrightIgnore, mypyIgnoreErrors (bool).
|
|
9
|
+
defs: public-surface candidates in source order, module level only (including inside top-level if,
|
|
10
|
+
try and with blocks):
|
|
11
|
+
{"kind": "function", "name", "info": fn} or {"kind": "class", "name", "methods": [fn]}
|
|
12
|
+
fn: {"name", "params": [{"name", "typed", "kind"}], "returns": bool, "decorators": [str]}
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import tokenize
|
|
21
|
+
|
|
22
|
+
TYPE_IGNORE = re.compile(r"#\s*type:\s*ignore\b")
|
|
23
|
+
PYRIGHT_IGNORE = re.compile(r"#\s*pyright:\s*ignore\b")
|
|
24
|
+
MYPY_IGNORE_ERRORS = re.compile(r"^#\s*mypy:\s*ignore-errors\b")
|
|
25
|
+
TYPING_MODULES = ("typing", "typing_extensions")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def read_text(path):
|
|
29
|
+
with open(path, "rb") as handle:
|
|
30
|
+
raw = handle.read()
|
|
31
|
+
try:
|
|
32
|
+
encoding, _ = tokenize.detect_encoding(io.BytesIO(raw).readline)
|
|
33
|
+
text = raw.decode(encoding)
|
|
34
|
+
except (SyntaxError, UnicodeDecodeError, LookupError):
|
|
35
|
+
text = raw.decode("utf-8", errors="replace")
|
|
36
|
+
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Imports:
|
|
40
|
+
"""Names that resolve to typing.Any, typing.cast and the typing modules themselves."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, tree):
|
|
43
|
+
self.any_names = set()
|
|
44
|
+
self.cast_names = set()
|
|
45
|
+
self.modules = set()
|
|
46
|
+
for node in ast.walk(tree):
|
|
47
|
+
if isinstance(node, ast.ImportFrom) and node.level == 0 and node.module in TYPING_MODULES:
|
|
48
|
+
for alias in node.names:
|
|
49
|
+
bound = alias.asname or alias.name
|
|
50
|
+
if alias.name == "Any":
|
|
51
|
+
self.any_names.add(bound)
|
|
52
|
+
elif alias.name == "cast":
|
|
53
|
+
self.cast_names.add(bound)
|
|
54
|
+
elif isinstance(node, ast.Import):
|
|
55
|
+
for alias in node.names:
|
|
56
|
+
if alias.name in TYPING_MODULES:
|
|
57
|
+
self.modules.add(alias.asname or alias.name)
|
|
58
|
+
|
|
59
|
+
def is_member(self, node, member, names):
|
|
60
|
+
if isinstance(node, ast.Name):
|
|
61
|
+
return node.id in names
|
|
62
|
+
return (
|
|
63
|
+
isinstance(node, ast.Attribute)
|
|
64
|
+
and node.attr == member
|
|
65
|
+
and isinstance(node.value, ast.Name)
|
|
66
|
+
and node.value.id in self.modules
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def count_any(annotation, imports):
|
|
71
|
+
if annotation is None:
|
|
72
|
+
return 0
|
|
73
|
+
total = 0
|
|
74
|
+
for node in ast.walk(annotation):
|
|
75
|
+
if isinstance(node, (ast.Name, ast.Attribute)) and imports.is_member(node, "Any", imports.any_names):
|
|
76
|
+
total += 1
|
|
77
|
+
elif isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
78
|
+
try:
|
|
79
|
+
inner = ast.parse(node.value.strip(), mode="eval")
|
|
80
|
+
except SyntaxError:
|
|
81
|
+
continue
|
|
82
|
+
total += count_any(inner.body, imports)
|
|
83
|
+
return total
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def all_arguments(args):
|
|
87
|
+
return list(args.posonlyargs) + list(args.args) + ([args.vararg] if args.vararg else []) + list(args.kwonlyargs) + ([args.kwarg] if args.kwarg else [])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def describe_function(node):
|
|
91
|
+
args = node.args
|
|
92
|
+
params = []
|
|
93
|
+
for kind, group in (
|
|
94
|
+
("positional", list(args.posonlyargs) + list(args.args)),
|
|
95
|
+
("vararg", [args.vararg] if args.vararg else []),
|
|
96
|
+
("keyword", list(args.kwonlyargs)),
|
|
97
|
+
("kwarg", [args.kwarg] if args.kwarg else []),
|
|
98
|
+
):
|
|
99
|
+
for arg in group:
|
|
100
|
+
params.append({"name": arg.arg, "typed": arg.annotation is not None, "kind": kind})
|
|
101
|
+
decorators = []
|
|
102
|
+
for decorator in node.decorator_list:
|
|
103
|
+
try:
|
|
104
|
+
decorators.append(ast.unparse(decorator))
|
|
105
|
+
except Exception:
|
|
106
|
+
decorators.append("")
|
|
107
|
+
return {"name": node.name, "params": params, "returns": node.returns is not None, "decorators": decorators}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
FUNCTIONS = (ast.FunctionDef, ast.AsyncFunctionDef)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def module_level(statements):
|
|
114
|
+
"""Statements at module level, descending into if, try and with blocks but not into functions or classes."""
|
|
115
|
+
for statement in statements:
|
|
116
|
+
yield statement
|
|
117
|
+
if isinstance(statement, ast.If):
|
|
118
|
+
yield from module_level(statement.body)
|
|
119
|
+
yield from module_level(statement.orelse)
|
|
120
|
+
elif isinstance(statement, (ast.With, ast.AsyncWith)):
|
|
121
|
+
yield from module_level(statement.body)
|
|
122
|
+
elif isinstance(statement, (ast.Try, getattr(ast, "TryStar", ast.Try))):
|
|
123
|
+
yield from module_level(statement.body)
|
|
124
|
+
for handler in statement.handlers:
|
|
125
|
+
yield from module_level(handler.body)
|
|
126
|
+
yield from module_level(statement.orelse)
|
|
127
|
+
yield from module_level(statement.finalbody)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def string_list(node):
|
|
131
|
+
if isinstance(node, (ast.List, ast.Tuple)):
|
|
132
|
+
return [e.value for e in node.elts if isinstance(e, ast.Constant) and isinstance(e.value, str)]
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_all_names(tree):
|
|
137
|
+
names = None
|
|
138
|
+
for statement in tree.body:
|
|
139
|
+
if isinstance(statement, ast.Assign) and any(isinstance(t, ast.Name) and t.id == "__all__" for t in statement.targets):
|
|
140
|
+
listed = string_list(statement.value)
|
|
141
|
+
if listed is not None:
|
|
142
|
+
names = list(listed)
|
|
143
|
+
elif isinstance(statement, ast.AnnAssign) and isinstance(statement.target, ast.Name) and statement.target.id == "__all__" and statement.value is not None:
|
|
144
|
+
listed = string_list(statement.value)
|
|
145
|
+
if listed is not None:
|
|
146
|
+
names = list(listed)
|
|
147
|
+
elif isinstance(statement, ast.AugAssign) and isinstance(statement.target, ast.Name) and statement.target.id == "__all__" and isinstance(statement.op, ast.Add):
|
|
148
|
+
listed = string_list(statement.value)
|
|
149
|
+
if listed is not None:
|
|
150
|
+
names = (names or []) + listed
|
|
151
|
+
elif (
|
|
152
|
+
isinstance(statement, ast.Expr)
|
|
153
|
+
and isinstance(statement.value, ast.Call)
|
|
154
|
+
and isinstance(statement.value.func, ast.Attribute)
|
|
155
|
+
and isinstance(statement.value.func.value, ast.Name)
|
|
156
|
+
and statement.value.func.value.id == "__all__"
|
|
157
|
+
and statement.value.args
|
|
158
|
+
):
|
|
159
|
+
method = statement.value.func.attr
|
|
160
|
+
argument = statement.value.args[0]
|
|
161
|
+
if method == "extend":
|
|
162
|
+
listed = string_list(argument)
|
|
163
|
+
if listed is not None:
|
|
164
|
+
names = (names or []) + listed
|
|
165
|
+
elif method == "append" and isinstance(argument, ast.Constant) and isinstance(argument.value, str):
|
|
166
|
+
names = (names or []) + [argument.value]
|
|
167
|
+
return names
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def comment_points(text, first_statement_line):
|
|
171
|
+
points = {"typeIgnoreLine": 0, "typeIgnoreHeader": 0, "pyrightIgnore": 0, "mypyIgnoreErrors": False}
|
|
172
|
+
for token in tokenize.generate_tokens(io.StringIO(text).readline):
|
|
173
|
+
if token.type != tokenize.COMMENT:
|
|
174
|
+
continue
|
|
175
|
+
comment = token.string
|
|
176
|
+
if TYPE_IGNORE.search(comment):
|
|
177
|
+
if first_statement_line is None or token.start[0] < first_statement_line:
|
|
178
|
+
points["typeIgnoreHeader"] += 1
|
|
179
|
+
else:
|
|
180
|
+
points["typeIgnoreLine"] += 1
|
|
181
|
+
if PYRIGHT_IGNORE.search(comment):
|
|
182
|
+
points["pyrightIgnore"] += 1
|
|
183
|
+
if MYPY_IGNORE_ERRORS.match(comment):
|
|
184
|
+
points["mypyIgnoreErrors"] = True
|
|
185
|
+
return points
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def analyze(path):
|
|
189
|
+
text = read_text(path)
|
|
190
|
+
try:
|
|
191
|
+
tree = ast.parse(text)
|
|
192
|
+
first = tree.body[0].lineno if tree.body else None
|
|
193
|
+
points = comment_points(text, first)
|
|
194
|
+
except (SyntaxError, ValueError, tokenize.TokenError, RecursionError, MemoryError):
|
|
195
|
+
return {"ok": False}
|
|
196
|
+
|
|
197
|
+
imports = Imports(tree)
|
|
198
|
+
any_count = 0
|
|
199
|
+
cast_count = 0
|
|
200
|
+
for node in ast.walk(tree):
|
|
201
|
+
if isinstance(node, FUNCTIONS) or isinstance(node, ast.Lambda):
|
|
202
|
+
if isinstance(node, FUNCTIONS):
|
|
203
|
+
for arg in all_arguments(node.args):
|
|
204
|
+
any_count += count_any(arg.annotation, imports)
|
|
205
|
+
any_count += count_any(node.returns, imports)
|
|
206
|
+
elif isinstance(node, ast.AnnAssign):
|
|
207
|
+
any_count += count_any(node.annotation, imports)
|
|
208
|
+
elif isinstance(node, ast.Call) and imports.is_member(node.func, "cast", imports.cast_names):
|
|
209
|
+
cast_count += 1
|
|
210
|
+
points["anyCount"] = any_count
|
|
211
|
+
points["castCount"] = cast_count
|
|
212
|
+
|
|
213
|
+
defs = []
|
|
214
|
+
for statement in module_level(tree.body):
|
|
215
|
+
if isinstance(statement, FUNCTIONS):
|
|
216
|
+
defs.append({"kind": "function", "name": statement.name, "info": describe_function(statement)})
|
|
217
|
+
elif isinstance(statement, ast.ClassDef):
|
|
218
|
+
methods = [describe_function(m) for m in statement.body if isinstance(m, FUNCTIONS)]
|
|
219
|
+
defs.append({"kind": "class", "name": statement.name, "methods": methods})
|
|
220
|
+
return {"ok": True, "points": points, "allNames": find_all_names(tree), "defs": defs}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def main():
|
|
224
|
+
request = json.load(sys.stdin)
|
|
225
|
+
result = {}
|
|
226
|
+
for path in request["files"]:
|
|
227
|
+
try:
|
|
228
|
+
result[path] = analyze(path)
|
|
229
|
+
except OSError:
|
|
230
|
+
result[path] = {"ok": False}
|
|
231
|
+
json.dump(result, sys.stdout)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
main()
|