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.
Files changed (236) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +73 -2
  3. package/assets/fonts/NotoEmoji-OFL.txt +93 -0
  4. package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
  5. package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
  6. package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
  7. package/dist/bin.js +11 -0
  8. package/dist/card/previous.js +38 -0
  9. package/dist/card/render.js +71 -0
  10. package/dist/cli.js +138 -0
  11. package/dist/commands/card.js +69 -0
  12. package/dist/commands/common.js +1 -0
  13. package/dist/commands/diff.js +54 -0
  14. package/dist/commands/footer.js +28 -0
  15. package/dist/commands/report.js +49 -0
  16. package/dist/commands/route.js +62 -0
  17. package/dist/commands/submit.js +125 -0
  18. package/dist/commands/summary.js +77 -0
  19. package/dist/diff/compare.js +73 -0
  20. package/dist/diff/conditions.js +87 -0
  21. package/dist/diff/format.js +23 -0
  22. package/dist/exec/docker.js +158 -0
  23. package/dist/exec/findings.js +73 -0
  24. package/dist/exec/plan.js +50 -0
  25. package/dist/exec/reports.js +90 -0
  26. package/dist/exec/roots.js +49 -0
  27. package/dist/exec/run.js +182 -0
  28. package/dist/exec/steps/build.js +45 -0
  29. package/dist/exec/steps/coverage-install.js +12 -0
  30. package/dist/exec/steps/install.js +41 -0
  31. package/dist/exec/steps/knip.js +28 -0
  32. package/dist/exec/steps/lint.js +25 -0
  33. package/dist/exec/steps/lockfiles.js +25 -0
  34. package/dist/exec/steps/test.js +17 -0
  35. package/dist/exec/steps/typecheck.js +40 -0
  36. package/dist/exec/test-reports/coverage-py.js +23 -0
  37. package/dist/exec/test-reports/coverage-ranges.js +65 -0
  38. package/dist/exec/test-reports/coverage-summary.js +23 -0
  39. package/dist/exec/test-reports/jest-json.js +31 -0
  40. package/dist/exec/test-reports/junit.js +24 -0
  41. package/dist/exec/test-reports/mocha-json.js +18 -0
  42. package/dist/exec/test-reports/vitest-json.js +5 -0
  43. package/dist/exec/test-runners/detect.js +99 -0
  44. package/dist/exec/test-runners/index.js +6 -0
  45. package/dist/exec/test-runners/jest.js +27 -0
  46. package/dist/exec/test-runners/mocha.js +28 -0
  47. package/dist/exec/test-runners/node-test.js +24 -0
  48. package/dist/exec/test-runners/pytest.js +18 -0
  49. package/dist/exec/test-runners/script.js +48 -0
  50. package/dist/exec/test-runners/spec.js +1 -0
  51. package/dist/exec/test-runners/vitest.js +28 -0
  52. package/dist/exec/tests.js +120 -0
  53. package/dist/exec/types.js +1 -0
  54. package/dist/interactive/after-scan.js +121 -0
  55. package/dist/interactive/docker-setup.js +97 -0
  56. package/dist/interactive/prompt.js +17 -0
  57. package/dist/interactive/system.js +10 -0
  58. package/dist/manifest/package-json.js +36 -0
  59. package/dist/manifest/python.js +106 -0
  60. package/dist/measure/d3/index.js +45 -0
  61. package/dist/measure/d3/presets.js +100 -0
  62. package/dist/measure/d3/py/adapter.js +67 -0
  63. package/dist/measure/d3/py/config.js +80 -0
  64. package/dist/measure/d3/py/contracts.js +57 -0
  65. package/dist/measure/d3/py/domain.js +57 -0
  66. package/dist/measure/d3/py/graph.js +214 -0
  67. package/dist/measure/d3/py/interpreter.js +31 -0
  68. package/dist/measure/d3/py/layers.js +120 -0
  69. package/dist/measure/d3/py/roots.js +58 -0
  70. package/dist/measure/d3/py/types.js +1 -0
  71. package/dist/measure/d3/scc.js +64 -0
  72. package/dist/measure/d3/ts/adapter.js +371 -0
  73. package/dist/measure/d3/ts/classify.js +107 -0
  74. package/dist/measure/d3/ts/config.js +318 -0
  75. package/dist/measure/d3/ts/domain.js +23 -0
  76. package/dist/measure/d3/ts/rules.js +65 -0
  77. package/dist/measure/d3/types.js +5 -0
  78. package/dist/measure/deps/cvss.js +66 -0
  79. package/dist/measure/deps/declared.js +195 -0
  80. package/dist/measure/deps/files.js +40 -0
  81. package/dist/measure/deps/lockfiles.js +308 -0
  82. package/dist/measure/deps/osv.js +122 -0
  83. package/dist/measure/deps/registry.js +142 -0
  84. package/dist/measure/deps/resolved.js +78 -0
  85. package/dist/measure/deps/units.js +5 -0
  86. package/dist/measure/eslint/health-plugin.js +19 -0
  87. package/dist/measure/ops/docs-commands.js +73 -0
  88. package/dist/measure/ops/docs-resolve.js +217 -0
  89. package/dist/measure/ops/entry-points.js +49 -0
  90. package/dist/measure/ops/env-example.js +20 -0
  91. package/dist/measure/ops/env-reads.js +46 -0
  92. package/dist/measure/ops/runtime.js +73 -0
  93. package/dist/measure/p1-1.js +8 -0
  94. package/dist/measure/p1-2.js +8 -0
  95. package/dist/measure/p1-3.js +8 -0
  96. package/dist/measure/p1-4.js +41 -0
  97. package/dist/measure/p1-5.js +59 -0
  98. package/dist/measure/p2-1.js +8 -0
  99. package/dist/measure/p2-2.js +44 -0
  100. package/dist/measure/p2-3.js +81 -0
  101. package/dist/measure/p2-4.js +288 -0
  102. package/dist/measure/p2-5.js +8 -0
  103. package/dist/measure/p3-1.js +16 -0
  104. package/dist/measure/p3-2.js +26 -0
  105. package/dist/measure/p3-3.js +37 -0
  106. package/dist/measure/p3-4.js +26 -0
  107. package/dist/measure/p3-5.js +26 -0
  108. package/dist/measure/p4-1.js +184 -0
  109. package/dist/measure/p4-2.js +27 -0
  110. package/dist/measure/p4-3.js +106 -0
  111. package/dist/measure/p4-4.js +309 -0
  112. package/dist/measure/p5-1.js +117 -0
  113. package/dist/measure/p5-2.js +80 -0
  114. package/dist/measure/p5-3.js +21 -0
  115. package/dist/measure/p5-4.js +161 -0
  116. package/dist/measure/p5-5.js +158 -0
  117. package/dist/measure/p6-1.js +20 -0
  118. package/dist/measure/p6-2.js +20 -0
  119. package/dist/measure/p6-3.js +40 -0
  120. package/dist/measure/p6-4.js +31 -0
  121. package/dist/measure/p6-5.js +60 -0
  122. package/dist/measure/p6-6.js +54 -0
  123. package/dist/measure/py/typecheck-config.js +95 -0
  124. package/dist/measure/registry.js +31 -0
  125. package/dist/measure/safety/py-tests.js +124 -0
  126. package/dist/measure/safety/ts-barrels.js +68 -0
  127. package/dist/measure/safety/ts-cases.js +111 -0
  128. package/dist/measure/safety/ts-credits.js +101 -0
  129. package/dist/measure/safety/ts-imports.js +63 -0
  130. package/dist/measure/safety/ts-resolve.js +43 -0
  131. package/dist/measure/ts/directives.js +37 -0
  132. package/dist/measure/ts/exports.js +60 -0
  133. package/dist/measure/ts/tsconfig.js +179 -0
  134. package/dist/measure/types.js +1 -0
  135. package/dist/measure/units.js +41 -0
  136. package/dist/methodology/load.js +159 -0
  137. package/dist/methodology/schema.js +105 -0
  138. package/dist/python/deadcode.js +27 -0
  139. package/dist/python/env.js +13 -0
  140. package/dist/python/helper.js +24 -0
  141. package/dist/python/imports.js +40 -0
  142. package/dist/python/nox.js +16 -0
  143. package/dist/python/scope.js +35 -0
  144. package/dist/python/tests.js +19 -0
  145. package/dist/python/typing.js +27 -0
  146. package/dist/report/escape.js +5 -0
  147. package/dist/report/format.js +75 -0
  148. package/dist/report/render.js +213 -0
  149. package/dist/report/roadmap.js +58 -0
  150. package/dist/report/scares.js +83 -0
  151. package/dist/report/style.js +27 -0
  152. package/dist/report-only/hotspots.js +48 -0
  153. package/dist/route/check.js +37 -0
  154. package/dist/route/files.js +74 -0
  155. package/dist/route/order.js +22 -0
  156. package/dist/route/select.js +114 -0
  157. package/dist/route/write.js +13 -0
  158. package/dist/run/read.js +89 -0
  159. package/dist/run/recompute.js +19 -0
  160. package/dist/run/scan.js +196 -0
  161. package/dist/run/schema.js +1 -0
  162. package/dist/run/score-run.js +42 -0
  163. package/dist/run/write.js +4 -0
  164. package/dist/scope/build.js +173 -0
  165. package/dist/scope/packages.js +87 -0
  166. package/dist/scope/rules.js +21 -0
  167. package/dist/scope/tracked.js +34 -0
  168. package/dist/scope/ts-lines.js +63 -0
  169. package/dist/scope/types.js +7 -0
  170. package/dist/scoring/aggregate.js +38 -0
  171. package/dist/scoring/pool.js +65 -0
  172. package/dist/scoring/score.js +81 -0
  173. package/dist/scoring/what-if.js +38 -0
  174. package/dist/scripts/checks.js +53 -0
  175. package/dist/scripts/ci.js +139 -0
  176. package/dist/scripts/entry-points/find.js +31 -0
  177. package/dist/scripts/entry-points/index.js +18 -0
  178. package/dist/scripts/entry-points/justfile.js +62 -0
  179. package/dist/scripts/entry-points/makefile.js +66 -0
  180. package/dist/scripts/entry-points/nox.js +29 -0
  181. package/dist/scripts/entry-points/package-json.js +21 -0
  182. package/dist/scripts/entry-points/pyproject.js +73 -0
  183. package/dist/scripts/entry-points/tox.js +130 -0
  184. package/dist/scripts/expand.js +300 -0
  185. package/dist/scripts/triggers.js +151 -0
  186. package/dist/scripts/types.js +1 -0
  187. package/dist/submit/constants.js +10 -0
  188. package/dist/submit/payload.js +42 -0
  189. package/dist/submit/repo-identity.js +46 -0
  190. package/dist/submit/send.js +29 -0
  191. package/dist/tools/catalog.js +46 -0
  192. package/dist/tools/image.js +180 -0
  193. package/dist/tools/kit.js +71 -0
  194. package/dist/tools/knip.js +169 -0
  195. package/dist/tools/outcome.js +21 -0
  196. package/dist/tools/resolve.js +66 -0
  197. package/dist/tools/workcopy.js +27 -0
  198. package/dist/util/ini.js +37 -0
  199. package/dist/util/pep440.js +163 -0
  200. package/dist/util/toml.js +27 -0
  201. package/docker/Dockerfile +142 -0
  202. package/docker/bin/health-capture +5 -0
  203. package/docker/bin/health-coverage-install +5 -0
  204. package/docker/bin/health-eslint +66 -0
  205. package/docker/bin/health-pytest +10 -0
  206. package/docker/bin/health-tsc +10 -0
  207. package/docker/bin/health-venv +11 -0
  208. package/docker/config/eslint.config.mjs +4 -0
  209. package/docker/config/eslint.rules.mjs +65 -0
  210. package/docker/config/eslint.untyped.config.mjs +4 -0
  211. package/docker/config/knip.json +1 -0
  212. package/docker/config/ruff.toml +7 -0
  213. package/methodology/LICENSE +396 -0
  214. package/methodology/v1.0/METHODOLOGY.md +1176 -0
  215. package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
  216. package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
  217. package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
  218. package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
  219. package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
  220. package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
  221. package/methodology/v1.0/methodology.yaml +242 -0
  222. package/methodology/v1.0/presets/django.yaml +43 -0
  223. package/methodology/v1.0/presets/express.yaml +47 -0
  224. package/methodology/v1.0/presets/fastapi.yaml +52 -0
  225. package/methodology/v1.0/presets/generic.yaml +41 -0
  226. package/methodology/v1.0/presets/nextjs.yaml +50 -0
  227. package/methodology/v1.0/scope.yaml +115 -0
  228. package/package.json +61 -4
  229. package/python/dead_code_helper.py +265 -0
  230. package/python/env_helper.py +71 -0
  231. package/python/graph_helper.py +277 -0
  232. package/python/imports_helper.py +132 -0
  233. package/python/nox_helper.py +79 -0
  234. package/python/scope_helper.py +96 -0
  235. package/python/tests_helper.py +171 -0
  236. 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()