clast-ai 0.1.2 → 0.2.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 (290) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +11 -9
  3. package/README.md +447 -401
  4. package/bench/ab/onair-concept.json +22 -0
  5. package/bench/ab/onair-deadcode.json +7 -0
  6. package/bench/ab/onair-hard.json +32 -0
  7. package/bench/ab/onair-impact.json +17 -0
  8. package/bench/ab/onair-refactors.json +18 -0
  9. package/bench/ab/onair-survey.json +17 -0
  10. package/bench/ab/onair-tasks.json +20 -0
  11. package/bench/ab/onair.json +52 -0
  12. package/bench/ab/vscode-concept.json +22 -0
  13. package/bench/ab/vscode-hard.json +32 -0
  14. package/bench/ab/vscode-impact.json +17 -0
  15. package/bench/ab/vscode-refactors.json +18 -0
  16. package/bench/ab/vscode-tasks.json +20 -0
  17. package/bench/ab/vscode.json +52 -0
  18. package/bench/ab.mjs +213 -0
  19. package/bench/bench.mjs +98 -0
  20. package/bench/resolution.mjs +75 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +257 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/config/defaults.d.ts +12 -2
  25. package/dist/config/defaults.js +38 -31
  26. package/dist/config/defaults.js.map +1 -1
  27. package/dist/config/index.d.ts +1 -1
  28. package/dist/config/index.js +26 -14
  29. package/dist/config/index.js.map +1 -1
  30. package/dist/db/index.d.ts +73 -13
  31. package/dist/db/index.js +481 -39
  32. package/dist/db/index.js.map +1 -1
  33. package/dist/engine.d.ts +15 -0
  34. package/dist/engine.js +28 -0
  35. package/dist/engine.js.map +1 -0
  36. package/dist/indexer/imports.d.ts +6 -0
  37. package/dist/indexer/imports.js +254 -0
  38. package/dist/indexer/imports.js.map +1 -0
  39. package/dist/indexer/index.d.ts +63 -0
  40. package/dist/indexer/index.js +360 -0
  41. package/dist/indexer/index.js.map +1 -0
  42. package/dist/indexer/process.d.ts +22 -0
  43. package/dist/indexer/process.js +42 -0
  44. package/dist/indexer/process.js.map +1 -0
  45. package/dist/indexer/scan.d.ts +43 -0
  46. package/dist/indexer/scan.js +223 -0
  47. package/dist/indexer/scan.js.map +1 -0
  48. package/dist/indexer/worker.d.ts +1 -0
  49. package/dist/indexer/worker.js +23 -0
  50. package/dist/indexer/worker.js.map +1 -0
  51. package/dist/llm/comment-generator.d.ts +17 -19
  52. package/dist/llm/comment-generator.js +46 -80
  53. package/dist/llm/comment-generator.js.map +1 -1
  54. package/dist/llm/index.d.ts +0 -1
  55. package/dist/llm/index.js.map +1 -1
  56. package/dist/llm/prompts.d.ts +0 -1
  57. package/dist/llm/prompts.js +16 -16
  58. package/dist/parser/extractor.d.ts +21 -16
  59. package/dist/parser/extractor.js +700 -228
  60. package/dist/parser/extractor.js.map +1 -1
  61. package/dist/parser/index.d.ts +17 -21
  62. package/dist/parser/index.js +115 -171
  63. package/dist/parser/index.js.map +1 -1
  64. package/dist/parser/langs/c.d.ts +3 -0
  65. package/dist/parser/langs/c.js +879 -0
  66. package/dist/parser/langs/c.js.map +1 -0
  67. package/dist/parser/langs/csharp.d.ts +2 -0
  68. package/dist/parser/langs/csharp.js +548 -0
  69. package/dist/parser/langs/csharp.js.map +1 -0
  70. package/dist/parser/langs/dart.d.ts +2 -0
  71. package/dist/parser/langs/dart.js +893 -0
  72. package/dist/parser/langs/dart.js.map +1 -0
  73. package/dist/parser/langs/go.d.ts +2 -0
  74. package/dist/parser/langs/go.js +455 -0
  75. package/dist/parser/langs/go.js.map +1 -0
  76. package/dist/parser/langs/java.d.ts +2 -0
  77. package/dist/parser/langs/java.js +390 -0
  78. package/dist/parser/langs/java.js.map +1 -0
  79. package/dist/parser/langs/php.d.ts +2 -0
  80. package/dist/parser/langs/php.js +486 -0
  81. package/dist/parser/langs/php.js.map +1 -0
  82. package/dist/parser/langs/python.d.ts +2 -0
  83. package/dist/parser/langs/python.js +216 -0
  84. package/dist/parser/langs/python.js.map +1 -0
  85. package/dist/parser/langs/ruby.d.ts +2 -0
  86. package/dist/parser/langs/ruby.js +602 -0
  87. package/dist/parser/langs/ruby.js.map +1 -0
  88. package/dist/parser/langs/rust.d.ts +2 -0
  89. package/dist/parser/langs/rust.js +931 -0
  90. package/dist/parser/langs/rust.js.map +1 -0
  91. package/dist/parser/langs/swift.d.ts +2 -0
  92. package/dist/parser/langs/swift.js +616 -0
  93. package/dist/parser/langs/swift.js.map +1 -0
  94. package/dist/parser/langs/typescript.d.ts +4 -0
  95. package/dist/parser/langs/typescript.js +535 -0
  96. package/dist/parser/langs/typescript.js.map +1 -0
  97. package/dist/parser/registry.d.ts +16 -0
  98. package/dist/parser/registry.js +87 -0
  99. package/dist/parser/registry.js.map +1 -0
  100. package/dist/parser/returns.d.ts +9 -0
  101. package/dist/parser/returns.js +152 -0
  102. package/dist/parser/returns.js.map +1 -0
  103. package/dist/parser/spec.d.ts +165 -0
  104. package/dist/parser/spec.js +188 -0
  105. package/dist/parser/spec.js.map +1 -0
  106. package/dist/query/arity.d.ts +27 -0
  107. package/dist/query/arity.js +187 -0
  108. package/dist/query/arity.js.map +1 -0
  109. package/dist/query/callgraph.d.ts +113 -0
  110. package/dist/query/callgraph.js +332 -0
  111. package/dist/query/callgraph.js.map +1 -0
  112. package/dist/query/format.d.ts +36 -0
  113. package/dist/query/format.js +136 -0
  114. package/dist/query/format.js.map +1 -0
  115. package/dist/query/hierarchy.d.ts +44 -0
  116. package/dist/query/hierarchy.js +441 -0
  117. package/dist/query/hierarchy.js.map +1 -0
  118. package/dist/query/resolve.d.ts +265 -0
  119. package/dist/query/resolve.js +1957 -0
  120. package/dist/query/resolve.js.map +1 -0
  121. package/dist/query/search.d.ts +35 -0
  122. package/dist/query/search.js +379 -0
  123. package/dist/query/search.js.map +1 -0
  124. package/dist/query/words.d.ts +30 -0
  125. package/dist/query/words.js +201 -0
  126. package/dist/query/words.js.map +1 -0
  127. package/dist/server/index.d.ts +1 -3
  128. package/dist/server/index.js +313 -73
  129. package/dist/server/index.js.map +1 -1
  130. package/dist/tools/context.d.ts +35 -0
  131. package/dist/tools/context.js +79 -0
  132. package/dist/tools/context.js.map +1 -0
  133. package/dist/tools/explore.d.ts +51 -0
  134. package/dist/tools/explore.js +779 -0
  135. package/dist/tools/explore.js.map +1 -0
  136. package/dist/tools/impact.d.ts +21 -0
  137. package/dist/tools/impact.js +743 -0
  138. package/dist/tools/impact.js.map +1 -0
  139. package/dist/tools/search.d.ts +59 -0
  140. package/dist/tools/search.js +843 -0
  141. package/dist/tools/search.js.map +1 -0
  142. package/dist/tools/stats.d.ts +26 -0
  143. package/dist/tools/stats.js +604 -0
  144. package/dist/tools/stats.js.map +1 -0
  145. package/dist/types.d.ts +95 -40
  146. package/dist/types.js +9 -1
  147. package/dist/types.js.map +1 -1
  148. package/dist/version.d.ts +2 -0
  149. package/dist/version.js +3 -0
  150. package/dist/version.js.map +1 -0
  151. package/dist/watcher/index.d.ts +18 -16
  152. package/dist/watcher/index.js +94 -77
  153. package/dist/watcher/index.js.map +1 -1
  154. package/package.json +21 -24
  155. package/plugin/server.mjs +50 -0
  156. package/skills/audit/SKILL.md +37 -0
  157. package/skills/deadcode/SKILL.md +33 -0
  158. package/skills/explore/SKILL.md +54 -0
  159. package/skills/impact/SKILL.md +24 -0
  160. package/skills/status/SKILL.md +11 -0
  161. package/src/cli.ts +256 -0
  162. package/src/config/defaults.ts +50 -32
  163. package/src/config/index.ts +23 -14
  164. package/src/db/index.ts +540 -43
  165. package/src/engine.ts +30 -0
  166. package/src/indexer/imports.ts +241 -0
  167. package/src/indexer/index.ts +369 -0
  168. package/src/indexer/process.ts +63 -0
  169. package/src/indexer/scan.ts +226 -0
  170. package/src/indexer/worker.ts +24 -0
  171. package/src/llm/comment-generator.ts +52 -103
  172. package/src/llm/index.ts +80 -80
  173. package/src/llm/prompts.ts +95 -95
  174. package/src/parser/extractor.ts +643 -256
  175. package/src/parser/index.ts +105 -205
  176. package/src/parser/langs/c.ts +860 -0
  177. package/src/parser/langs/csharp.ts +519 -0
  178. package/src/parser/langs/dart.ts +820 -0
  179. package/src/parser/langs/go.ts +437 -0
  180. package/src/parser/langs/java.ts +361 -0
  181. package/src/parser/langs/php.ts +450 -0
  182. package/src/parser/langs/python.ts +206 -0
  183. package/src/parser/langs/ruby.ts +554 -0
  184. package/src/parser/langs/rust.ts +904 -0
  185. package/src/parser/langs/swift.ts +590 -0
  186. package/src/parser/langs/typescript.ts +499 -0
  187. package/src/parser/registry.ts +88 -0
  188. package/src/parser/returns.ts +137 -0
  189. package/src/parser/spec.ts +309 -0
  190. package/src/query/arity.ts +173 -0
  191. package/src/query/callgraph.ts +473 -0
  192. package/src/query/format.ts +136 -0
  193. package/src/query/hierarchy.ts +447 -0
  194. package/src/query/resolve.ts +1972 -0
  195. package/src/query/search.ts +430 -0
  196. package/src/query/words.ts +183 -0
  197. package/src/server/index.ts +390 -79
  198. package/src/tools/context.ts +82 -0
  199. package/src/tools/explore.ts +860 -0
  200. package/src/tools/impact.ts +748 -0
  201. package/src/tools/search.ts +886 -0
  202. package/src/tools/stats.ts +633 -0
  203. package/src/types.ts +113 -45
  204. package/src/version.ts +2 -0
  205. package/src/watcher/index.ts +82 -88
  206. package/tsconfig.json +7 -4
  207. package/.mcp.json +0 -8
  208. package/commands/clast-status.md +0 -9
  209. package/dist/config/defaults.d.ts.map +0 -1
  210. package/dist/config/index.d.ts.map +0 -1
  211. package/dist/db/index.d.ts.map +0 -1
  212. package/dist/db/queries.d.ts +0 -44
  213. package/dist/db/queries.d.ts.map +0 -1
  214. package/dist/db/queries.js +0 -251
  215. package/dist/db/queries.js.map +0 -1
  216. package/dist/db/schema.d.ts +0 -3
  217. package/dist/db/schema.d.ts.map +0 -1
  218. package/dist/db/schema.js +0 -94
  219. package/dist/db/schema.js.map +0 -1
  220. package/dist/db/types.d.ts +0 -44
  221. package/dist/db/types.d.ts.map +0 -1
  222. package/dist/db/types.js +0 -2
  223. package/dist/db/types.js.map +0 -1
  224. package/dist/llm/comment-generator.d.ts.map +0 -1
  225. package/dist/llm/index.d.ts.map +0 -1
  226. package/dist/llm/prompts.d.ts.map +0 -1
  227. package/dist/parser/call-resolver.d.ts +0 -15
  228. package/dist/parser/call-resolver.d.ts.map +0 -1
  229. package/dist/parser/call-resolver.js +0 -16
  230. package/dist/parser/call-resolver.js.map +0 -1
  231. package/dist/parser/extractor.d.ts.map +0 -1
  232. package/dist/parser/index.d.ts.map +0 -1
  233. package/dist/parser/languages.d.ts +0 -11
  234. package/dist/parser/languages.d.ts.map +0 -1
  235. package/dist/parser/languages.js +0 -74
  236. package/dist/parser/languages.js.map +0 -1
  237. package/dist/server/index.d.ts.map +0 -1
  238. package/dist/server/register-tools.d.ts +0 -7
  239. package/dist/server/register-tools.d.ts.map +0 -1
  240. package/dist/server/register-tools.js +0 -17
  241. package/dist/server/register-tools.js.map +0 -1
  242. package/dist/server/tools/clast-call-graph.d.ts +0 -4
  243. package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
  244. package/dist/server/tools/clast-call-graph.js +0 -70
  245. package/dist/server/tools/clast-call-graph.js.map +0 -1
  246. package/dist/server/tools/clast-comment.d.ts +0 -5
  247. package/dist/server/tools/clast-comment.d.ts.map +0 -1
  248. package/dist/server/tools/clast-comment.js +0 -64
  249. package/dist/server/tools/clast-comment.js.map +0 -1
  250. package/dist/server/tools/clast-file-summary.d.ts +0 -4
  251. package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
  252. package/dist/server/tools/clast-file-summary.js +0 -42
  253. package/dist/server/tools/clast-file-summary.js.map +0 -1
  254. package/dist/server/tools/clast-get-context.d.ts +0 -4
  255. package/dist/server/tools/clast-get-context.d.ts.map +0 -1
  256. package/dist/server/tools/clast-get-context.js +0 -80
  257. package/dist/server/tools/clast-get-context.js.map +0 -1
  258. package/dist/server/tools/clast-reindex.d.ts +0 -5
  259. package/dist/server/tools/clast-reindex.d.ts.map +0 -1
  260. package/dist/server/tools/clast-reindex.js +0 -36
  261. package/dist/server/tools/clast-reindex.js.map +0 -1
  262. package/dist/server/tools/clast-search.d.ts +0 -4
  263. package/dist/server/tools/clast-search.d.ts.map +0 -1
  264. package/dist/server/tools/clast-search.js +0 -29
  265. package/dist/server/tools/clast-search.js.map +0 -1
  266. package/dist/server/tools/clast-status.d.ts +0 -5
  267. package/dist/server/tools/clast-status.d.ts.map +0 -1
  268. package/dist/server/tools/clast-status.js +0 -23
  269. package/dist/server/tools/clast-status.js.map +0 -1
  270. package/dist/types.d.ts.map +0 -1
  271. package/dist/watcher/gitignore.d.ts +0 -9
  272. package/dist/watcher/gitignore.d.ts.map +0 -1
  273. package/dist/watcher/gitignore.js +0 -38
  274. package/dist/watcher/gitignore.js.map +0 -1
  275. package/dist/watcher/index.d.ts.map +0 -1
  276. package/skills/clast-explore/SKILL.md +0 -41
  277. package/src/db/queries.ts +0 -332
  278. package/src/db/schema.ts +0 -106
  279. package/src/db/types.ts +0 -47
  280. package/src/parser/call-resolver.ts +0 -17
  281. package/src/parser/languages.ts +0 -86
  282. package/src/server/register-tools.ts +0 -30
  283. package/src/server/tools/clast-call-graph.ts +0 -82
  284. package/src/server/tools/clast-comment.ts +0 -76
  285. package/src/server/tools/clast-file-summary.ts +0 -59
  286. package/src/server/tools/clast-get-context.ts +0 -97
  287. package/src/server/tools/clast-reindex.ts +0 -52
  288. package/src/server/tools/clast-search.ts +0 -38
  289. package/src/server/tools/clast-status.ts +0 -37
  290. package/src/watcher/gitignore.ts +0 -42
@@ -0,0 +1,499 @@
1
+ import type { Node } from 'web-tree-sitter';
2
+ import type { Ctx, LangSpec, Visitor } from '../spec.js';
3
+ import { childOfType, field, isConstName, lastSegment, simpleReceiver, stripQuotes } from '../spec.js';
4
+
5
+ const FUNCTION_VALUES = new Set(['arrow_function', 'function_expression', 'function', 'generator_function']);
6
+
7
+ function isExported(node: Node): Node | null {
8
+ const p = node.parent;
9
+ return p && p.type === 'export_statement' ? p : null;
10
+ }
11
+
12
+ /** True if a declaration sits directly in a module or namespace body. */
13
+ function isTopLevel(decl: Node | null): boolean {
14
+ let p = decl?.parent;
15
+ if (p && p.type === 'export_statement') p = p.parent;
16
+ if (!p) return false;
17
+ if (p.type === 'program') return true;
18
+ if (p.type === 'statement_block') {
19
+ const owner = p.parent?.type;
20
+ return owner === 'internal_module' || owner === 'module';
21
+ }
22
+ return false;
23
+ }
24
+
25
+ function classHeritage(node: Node, sym: number, ctx: Ctx): void {
26
+ for (const c of node.namedChildren) {
27
+ if (!c) continue;
28
+ if (c.type === 'class_heritage') {
29
+ // TS: class_heritage > extends_clause / implements_clause. JS: class_heritage > expression.
30
+ let found = false;
31
+ for (const h of c.namedChildren) {
32
+ if (!h) continue;
33
+ if (h.type === 'extends_clause') {
34
+ found = true;
35
+ for (const v of h.childrenForFieldName('value')) if (v) ctx.inherit(sym, lastSegment(v.text), 'extends');
36
+ } else if (h.type === 'implements_clause') {
37
+ found = true;
38
+ for (const t of h.namedChildren) if (t) ctx.inherit(sym, lastSegment(t.text.replace(/<.*$/s, '')), 'implements');
39
+ }
40
+ }
41
+ if (!found && c.namedChildren[0]) ctx.inherit(sym, lastSegment(c.namedChildren[0].text), 'extends');
42
+ } else if (c.type === 'extends_type_clause' || c.type === 'extends_clause') {
43
+ for (const t of c.namedChildren) if (t) ctx.inherit(sym, lastSegment(t.text.replace(/<.*$/s, '')), 'extends');
44
+ }
45
+ }
46
+ }
47
+
48
+ const classLike = (kind: 'class' | 'interface'): Visitor => (node, ctx) => {
49
+ const name = field(node, 'name');
50
+ if (!name) return;
51
+ const exp = isExported(node);
52
+ const sym = ctx.def(node, { name: name.text, kind, range: exp ?? node, exported: !!exp });
53
+ classHeritage(node, sym, ctx);
54
+ };
55
+
56
+ const functionDecl: Visitor = (node, ctx) => {
57
+ const name = field(node, 'name');
58
+ if (!name) return;
59
+ const exp = isExported(node);
60
+ ctx.def(node, {
61
+ name: name.text,
62
+ kind: ctx.parentKind() === 'class' ? 'method' : 'function',
63
+ range: exp ?? node,
64
+ exported: !!exp,
65
+ });
66
+ };
67
+
68
+ const method: Visitor = (node, ctx) => {
69
+ const name = field(node, 'name');
70
+ if (!name) return;
71
+ // Class members and object-literal members alike: called through an object.
72
+ ctx.def(node, { name: name.text.replace(/^['"]|['"]$/g, ''), kind: 'method' });
73
+ };
74
+
75
+ const fieldDef: Visitor = (node, ctx) => {
76
+ const name = field(node, 'name') ?? field(node, 'property');
77
+ if (!name) return;
78
+ const value = field(node, 'value');
79
+ const isFn = value && FUNCTION_VALUES.has(value.type);
80
+ ctx.def(node, {
81
+ name: name.text,
82
+ kind: isFn ? 'method' : 'property',
83
+ body: isFn ? field(value, 'body') : null,
84
+ });
85
+ ctx.typeHint(name.text, declaredType(node, value));
86
+ };
87
+
88
+ /** Type text of a declaration: its annotation, or the class of a `new X()` initializer. */
89
+ function declaredType(node: Node, value: Node | null): string | null {
90
+ const annotation = field(node, 'type');
91
+ if (annotation) return annotation.text;
92
+ if (value && value.type === 'new_expression') return field(value, 'constructor')?.text ?? null;
93
+ return null;
94
+ }
95
+
96
+ /** Parameters with type annotations; constructor parameter properties also declare a field. */
97
+ const parameter: Visitor = (node, ctx) => {
98
+ const pattern = field(node, 'pattern');
99
+ const type = field(node, 'type');
100
+ if (!pattern || pattern.type !== 'identifier' || !type) return;
101
+ ctx.typeHint(pattern.text, type.text);
102
+ // `constructor(@IFoo private readonly foo: IFoo)`: a parameter property is also a field.
103
+ for (let i = 0; i < node.childCount; i++) {
104
+ const c = node.child(i);
105
+ if (!c || c === pattern) break;
106
+ if (c.type === 'accessibility_modifier' || c.type === 'readonly' || c.type === 'override_modifier') {
107
+ ctx.typeHint(pattern.text, type.text, true);
108
+ break;
109
+ }
110
+ }
111
+ };
112
+
113
+ /** `@memoize` / `@ns.decorator`: applying a decorator calls it. (`@foo()` is a call_expression already.) */
114
+ const decorator: Visitor = (node, ctx) => {
115
+ const expr = node.namedChildren[0];
116
+ if (!expr) return;
117
+ if (expr.type === 'identifier') ctx.call(expr.text, node);
118
+ else if (expr.type === 'member_expression') {
119
+ ctx.call(field(expr, 'property')?.text, node, simpleReceiver(field(expr, 'object')));
120
+ }
121
+ };
122
+
123
+ const HANDLED_FUNCTION_PARENTS = new Set([
124
+ 'variable_declarator', 'assignment_expression', 'pair', 'public_field_definition', 'field_definition',
125
+ ]);
126
+
127
+ /** Named function expressions used as values: `defineGetter(req, 'ip', function ip() {})`. */
128
+ const functionExpression: Visitor = (node, ctx) => {
129
+ const name = field(node, 'name');
130
+ if (!name || (node.parent && HANDLED_FUNCTION_PARENTS.has(node.parent.type))) return;
131
+ ctx.def(node, { name: name.text, kind: 'function' });
132
+ };
133
+
134
+ /** Module path if `value` is `require('x')` or `require('x').member`, else null. */
135
+ function requireSource(value: Node | null): string | null {
136
+ let call = value;
137
+ if (call && call.type === 'member_expression') call = field(call, 'object');
138
+ if (!call || call.type !== 'call_expression' || field(call, 'function')?.text !== 'require') return null;
139
+ const arg = field(call, 'arguments')?.namedChildren[0];
140
+ return arg && arg.type === 'string' ? stripQuotes(arg.text) : null;
141
+ }
142
+
143
+ const variableDeclarator: Visitor = (node, ctx) => {
144
+ const nameNode = field(node, 'name');
145
+ const value = field(node, 'value');
146
+ if (nameNode && nameNode.type === 'object_pattern') {
147
+ // const { a, b: c } = require('./x')
148
+ const source = requireSource(value);
149
+ if (source) {
150
+ const names: string[] = [];
151
+ for (const p of nameNode.namedChildren) {
152
+ if (!p) continue;
153
+ if (p.type === 'shorthand_property_identifier_pattern') names.push(p.text);
154
+ else if (p.type === 'pair_pattern') {
155
+ const key = field(p, 'key')?.text;
156
+ const bound = field(p, 'value');
157
+ if (key && bound && bound.type === 'identifier') names.push(bound.text === key ? key : `${bound.text}=${key}`);
158
+ }
159
+ }
160
+ ctx.imp(source, node, names);
161
+ }
162
+ return;
163
+ }
164
+ if (!nameNode || nameNode.type !== 'identifier') return;
165
+ const decl = node.parent; // lexical_declaration | variable_declaration
166
+ const exp = decl ? isExported(decl) : null;
167
+ const range = exp ?? (decl && decl.namedChildCount === 1 ? decl : node);
168
+ const name = nameNode.text;
169
+ ctx.typeHint(name, declaredType(node, value));
170
+
171
+ if (value && FUNCTION_VALUES.has(value.type)) {
172
+ ctx.def(node, { name, kind: 'function', range, body: field(value, 'body'), exported: !!exp });
173
+ return;
174
+ }
175
+ if (value && value.type === 'class') {
176
+ const sym = ctx.def(node, { name, kind: 'class', range, body: field(value, 'body'), exported: !!exp });
177
+ classHeritage(value, sym, ctx);
178
+ return;
179
+ }
180
+ // CommonJS: `const x = require('y')` and `const f = require('y').f` bind imports, they define nothing.
181
+ const required = requireSource(value);
182
+ if (required) {
183
+ const member = value && value.type === 'member_expression' ? field(value, 'property')?.text : undefined;
184
+ ctx.imp(required, node, [member && member !== name ? `${name}=${member}` : name]);
185
+ return;
186
+ }
187
+ // Only module-level (or namespace-level) variables become symbols; locals
188
+ // inside callbacks (tests, event handlers) are not worth indexing.
189
+ if (ctx.inCallable() || !isTopLevel(decl)) return;
190
+ const wrapped = value && value.type === 'call_expression' ? field(value, 'arguments') : null;
191
+ const fnArg = wrapped?.namedChildren.find(a => a && FUNCTION_VALUES.has(a.type));
192
+ if (fnArg) {
193
+ // const useThing = memo(() => {...}) / forwardRef(function ...) style wrappers
194
+ ctx.def(node, { name, kind: 'function', range, body: field(fnArg, 'body'), exported: !!exp });
195
+ return;
196
+ }
197
+ const isConst = decl?.type === 'lexical_declaration' && decl.child(0)?.type === 'const';
198
+ ctx.def(node, {
199
+ name,
200
+ kind: isConst || isConstName(name) ? 'constant' : 'variable',
201
+ range,
202
+ body: null,
203
+ exported: !!exp,
204
+ });
205
+ };
206
+
207
+ /** `foo.bar = function() {}`, `exports.x = () => {}`, `Foo.prototype.bar = function() {}` */
208
+ const assignment: Visitor = (node, ctx) => {
209
+ // `this.repo = repo` / `this.cache = new Cache()`: the field takes the type of what is assigned.
210
+ const target = field(node, 'left');
211
+ if (target && target.type === 'member_expression' && field(target, 'object')?.type === 'this') {
212
+ const prop = field(target, 'property');
213
+ const value = field(node, 'right');
214
+ if (prop && value) {
215
+ if (value.type === 'new_expression') ctx.typeHint(prop.text, field(value, 'constructor')?.text, true);
216
+ else if (value.type === 'identifier') ctx.typeHint(prop.text, ctx.typeOf(value.text), true);
217
+ }
218
+ }
219
+ // Inner links of a chain (a = b = function) are handled from the head.
220
+ if (node.parent && node.parent.type === 'assignment_expression') return;
221
+ const targets: Node[] = [];
222
+ let right: Node | null = node;
223
+ while (right && right.type === 'assignment_expression') {
224
+ const left = field(right, 'left');
225
+ if (left) targets.push(left);
226
+ right = field(right, 'right');
227
+ }
228
+ if (!right) return;
229
+ // module.exports = createApplication / exports = module.exports = createApplication
230
+ if (targets.some(t => /^(module\.)?exports$/.test(t.text))) {
231
+ if (right.type === 'identifier') ctx.exportDefault(right.text);
232
+ else if (requireSource(right)) {
233
+ // module.exports = require('./lib/express'): pass that module's export through.
234
+ ctx.imp(requireSource(right), node, ['*']);
235
+ ctx.exportDefault('*');
236
+ }
237
+ else if ((FUNCTION_VALUES.has(right.type) || right.type === 'class') && field(right, 'name')) {
238
+ ctx.exportDefault(field(right, 'name')!.text);
239
+ }
240
+ }
241
+ const isFn = FUNCTION_VALUES.has(right.type);
242
+ const isClass = right.type === 'class';
243
+ if (!isFn && !isClass) return;
244
+ const stmt = node.parent && node.parent.type === 'expression_statement' ? node.parent : node;
245
+
246
+ // `res.set = res.header = function header() {}`: every target names the same function.
247
+ // Aliases are leaf symbols; the first target owns the body scope.
248
+ for (let i = targets.length - 1; i >= 0; i--) {
249
+ const left = targets[i]!;
250
+ if (left.type !== 'member_expression') continue;
251
+ const prop = field(left, 'property');
252
+ const obj = field(left, 'object');
253
+ if (!prop || !obj) continue;
254
+ let qualifier = obj.text.replace(/\.prototype$/, '');
255
+ const exported = /^(module\.)?exports$/.test(qualifier);
256
+ if (exported || qualifier === 'this' || qualifier.length > 60 || /[^\w.$]/.test(qualifier)) qualifier = '';
257
+ ctx.def(node, {
258
+ name: prop.text,
259
+ kind: isClass ? 'class' : qualifier ? 'method' : 'function',
260
+ range: stmt,
261
+ body: field(right, 'body'),
262
+ qualifier,
263
+ exported,
264
+ scope: i === 0,
265
+ });
266
+ }
267
+ };
268
+
269
+ /** Object literal members with function values: `{ foo: () => {} }` */
270
+ const pair: Visitor = (node, ctx) => {
271
+ const value = field(node, 'value');
272
+ const key = field(node, 'key');
273
+ if (!value || !key || !FUNCTION_VALUES.has(value.type)) return;
274
+ if (key.type !== 'property_identifier' && key.type !== 'string') return;
275
+ // A member of an object literal: reachable only through the object, never by its bare name.
276
+ ctx.def(node, { name: stripQuotes(key.text), kind: 'method', body: field(value, 'body') });
277
+ };
278
+
279
+ const callExpression: Visitor = (node, ctx) => {
280
+ const fn = field(node, 'function');
281
+ if (!fn) return;
282
+ if (fn.type === 'identifier') {
283
+ ctx.call(fn.text, node);
284
+ } else if (fn.type === 'member_expression') {
285
+ ctx.call(field(fn, 'property')?.text, node, simpleReceiver(field(fn, 'object')));
286
+ } else if (fn.type === 'import') {
287
+ const arg = field(node, 'arguments')?.namedChildren[0];
288
+ if (arg && arg.type === 'string') ctx.imp(stripQuotes(arg.text), node);
289
+ }
290
+ };
291
+
292
+ const newExpression: Visitor = (node, ctx) => {
293
+ const c = field(node, 'constructor');
294
+ if (c) ctx.call(lastSegment(c.text), node);
295
+ };
296
+
297
+ const jsxElement: Visitor = (node, ctx) => {
298
+ const name = field(node, 'name');
299
+ if (!name) return;
300
+ const t = lastSegment(name.text);
301
+ // Components are capitalised; lowercase tags are DOM elements.
302
+ if (/^[A-Z]/.test(t)) ctx.call(t, node);
303
+ };
304
+
305
+ const importStatement: Visitor = (node, ctx) => {
306
+ const source = field(node, 'source');
307
+ if (!source) return;
308
+ const names: string[] = [];
309
+ const clause = childOfType(node, 'import_clause');
310
+ if (clause) {
311
+ for (const c of clause.namedChildren) {
312
+ if (!c) continue;
313
+ if (c.type === 'identifier') names.push(c.text);
314
+ else if (c.type === 'namespace_import') names.push(c.namedChildren[0]?.text ?? '*');
315
+ else if (c.type === 'named_imports') {
316
+ for (const s of c.namedChildren) {
317
+ // The local binding is the alias when there is one (import { a as b }).
318
+ if (s && s.type === 'import_specifier') {
319
+ const original = field(s, 'name')?.text ?? s.text;
320
+ const alias = field(s, 'alias')?.text;
321
+ names.push(alias && alias !== original ? `${alias}=${original}` : original);
322
+ }
323
+ }
324
+ }
325
+ }
326
+ }
327
+ const src = stripQuotes(source.text);
328
+ // Remember where each local name comes from, for `export { X as Y }` further down.
329
+ let map = importSources.get(ctx);
330
+ if (!map) importSources.set(ctx, (map = new Map()));
331
+ for (const n of names) map.set(n.split('=')[0]!, src + '\0' + (n.split('=')[1] ?? n));
332
+ ctx.imp(src, node, names);
333
+ };
334
+
335
+ /** Per file: local name -> "source\0original name" of what it was imported as. */
336
+ const importSources = new WeakMap<Ctx, Map<string, string>>();
337
+
338
+ const exportStatement: Visitor = (node, ctx) => {
339
+ // Re-exports: export { a } from './x'; export * from './x';
340
+ const source = field(node, 'source');
341
+ const clause = childOfType(node, 'export_clause');
342
+ // Renamed on the way out: `export { parseId as getId } from './impl'`. Importers of this file
343
+ // call it by the new name, so the alias is recorded like an import alias (`getId=parseId`).
344
+ const renamed: Array<[string, string]> = [];
345
+ if (clause) {
346
+ for (const s of clause.namedChildren) {
347
+ if (!s || s.type !== 'export_specifier') continue;
348
+ const original = field(s, 'name')?.text;
349
+ const alias = field(s, 'alias')?.text;
350
+ if (original && alias && alias !== original && alias !== 'default' && original !== 'default') renamed.push([alias, original]);
351
+ }
352
+ }
353
+ if (source) {
354
+ ctx.imp(stripQuotes(source.text), node, renamed.map(([alias, original]) => `${alias}=${original}`));
355
+ } else if (renamed.length) {
356
+ // `import { Picker } from './picker'; export { Picker as TerminalPicker };`
357
+ const map = importSources.get(ctx);
358
+ for (const [alias, original] of renamed) {
359
+ const from = map?.get(original);
360
+ if (!from) continue;
361
+ const [src, real] = from.split('\0') as [string, string];
362
+ ctx.imp(src, node, [`${alias}=${real}`]);
363
+ }
364
+ }
365
+ // export default X / export default function name() {} / export default class Name {}
366
+ if (node.children.some(c => c?.type === 'default')) {
367
+ const value = field(node, 'value');
368
+ const decl = field(node, 'declaration');
369
+ if (value && value.type === 'identifier') ctx.exportDefault(value.text);
370
+ else if (decl) ctx.exportDefault(field(decl, 'name')?.text);
371
+ }
372
+ };
373
+
374
+ /**
375
+ * Anonymous class expressions (`registerAction2(class extends Action2 { run() {} })`):
376
+ * give them a synthetic name so their methods have a container and the class
377
+ * shows up as a subtype of what it extends.
378
+ */
379
+ const classExpression: Visitor = (node, ctx) => {
380
+ if (field(node, 'name')) return; // named class expressions are handled where they are bound
381
+ const p = node.parent;
382
+ if (p && (p.type === 'variable_declarator' || p.type === 'assignment_expression')) return;
383
+ let base = '';
384
+ for (const c of node.namedChildren) {
385
+ if (!c || c.type !== 'class_heritage') continue;
386
+ const ext = c.namedChildren.find(h => h?.type === 'extends_clause');
387
+ const value = ext ? ext.childrenForFieldName('value')[0] : c.namedChildren[0];
388
+ if (value) base = lastSegment(value.text);
389
+ }
390
+ const sym = ctx.def(node, { name: base ? `(anonymous ${base})` : '(anonymous class)', kind: 'class' });
391
+ classHeritage(node, sym, ctx);
392
+ };
393
+
394
+ const typeAlias: Visitor = (node, ctx) => {
395
+ const name = field(node, 'name');
396
+ if (!name) return;
397
+ const exp = isExported(node);
398
+ ctx.def(node, { name: name.text, kind: 'type', range: exp ?? node, body: field(node, 'value'), exported: !!exp });
399
+ };
400
+
401
+ const enumDecl: Visitor = (node, ctx) => {
402
+ const name = field(node, 'name');
403
+ if (!name) return;
404
+ const exp = isExported(node);
405
+ ctx.def(node, { name: name.text, kind: 'enum', range: exp ?? node, exported: !!exp });
406
+ for (const m of field(node, 'body')?.namedChildren ?? []) {
407
+ if (!m) continue;
408
+ const mName = m.type === 'enum_assignment' ? field(m, 'name') : m.type === 'property_identifier' ? m : null;
409
+ if (mName) ctx.def(m, { name: stripQuotes(mName.text), kind: 'constant', body: null, scope: false });
410
+ }
411
+ };
412
+
413
+ const namespaceDecl: Visitor = (node, ctx) => {
414
+ const name = field(node, 'name');
415
+ if (!name) return;
416
+ const outer = node.parent?.type === 'expression_statement' ? node.parent : node;
417
+ const exp = isExported(outer);
418
+ ctx.def(node, { name: stripQuotes(name.text), kind: 'namespace', range: exp ?? outer, exported: !!exp });
419
+ };
420
+
421
+ const propertySignature: Visitor = (node, ctx) => {
422
+ const name = field(node, 'name');
423
+ if (!name || ctx.parentKind() !== 'interface') return;
424
+ ctx.def(node, { name: stripQuotes(name.text), kind: 'property', body: null, scope: false });
425
+ ctx.typeHint(name.text, field(node, 'type')?.text);
426
+ };
427
+
428
+ const jsVisitors: Record<string, Visitor> = {
429
+ function_declaration: functionDecl,
430
+ generator_function_declaration: functionDecl,
431
+ class_declaration: classLike('class'),
432
+ method_definition: method,
433
+ field_definition: fieldDef,
434
+ variable_declarator: variableDeclarator,
435
+ assignment_expression: assignment,
436
+ pair,
437
+ call_expression: callExpression,
438
+ new_expression: newExpression,
439
+ jsx_opening_element: jsxElement,
440
+ jsx_self_closing_element: jsxElement,
441
+ import_statement: importStatement,
442
+ export_statement: exportStatement,
443
+ decorator,
444
+ function_expression: functionExpression,
445
+ class: classExpression,
446
+ };
447
+
448
+ const tsVisitors: Record<string, Visitor> = {
449
+ ...jsVisitors,
450
+ abstract_class_declaration: classLike('class'),
451
+ interface_declaration: classLike('interface'),
452
+ function_signature: functionDecl,
453
+ method_signature: method,
454
+ abstract_method_signature: method,
455
+ public_field_definition: fieldDef,
456
+ property_signature: propertySignature,
457
+ type_alias_declaration: typeAlias,
458
+ enum_declaration: enumDecl,
459
+ internal_module: namespaceDecl,
460
+ module: namespaceDecl,
461
+ required_parameter: parameter,
462
+ optional_parameter: parameter,
463
+ };
464
+ delete tsVisitors.field_definition;
465
+ delete tsVisitors.jsx_opening_element;
466
+ delete tsVisitors.jsx_self_closing_element;
467
+
468
+ const JS_IDENTIFIERS = [
469
+ 'identifier', 'property_identifier', 'shorthand_property_identifier',
470
+ 'shorthand_property_identifier_pattern', 'private_property_identifier',
471
+ ];
472
+
473
+ export const javascript: LangSpec = {
474
+ id: 'javascript',
475
+ grammar: 'javascript',
476
+ extensions: ['.js', '.jsx', '.mjs', '.cjs'],
477
+ identifiers: JS_IDENTIFIERS,
478
+ visit: jsVisitors,
479
+ };
480
+
481
+ export const typescript: LangSpec = {
482
+ id: 'typescript',
483
+ grammar: 'typescript',
484
+ extensions: ['.ts', '.mts', '.cts'],
485
+ identifiers: [...JS_IDENTIFIERS, 'type_identifier'],
486
+ visit: tsVisitors,
487
+ };
488
+
489
+ export const tsx: LangSpec = {
490
+ id: 'tsx',
491
+ grammar: 'tsx',
492
+ extensions: ['.tsx'],
493
+ identifiers: [...JS_IDENTIFIERS, 'type_identifier'],
494
+ visit: {
495
+ ...tsVisitors,
496
+ jsx_opening_element: jsxElement,
497
+ jsx_self_closing_element: jsxElement,
498
+ },
499
+ };
@@ -0,0 +1,88 @@
1
+ import type { LangSpec } from './spec.js';
2
+ import { javascript, typescript, tsx } from './langs/typescript.js';
3
+ import { python } from './langs/python.js';
4
+ import { go } from './langs/go.js';
5
+ import { rust } from './langs/rust.js';
6
+ import { java } from './langs/java.js';
7
+ import { csharp } from './langs/csharp.js';
8
+ import { c, cpp } from './langs/c.js';
9
+ import { ruby } from './langs/ruby.js';
10
+ import { php } from './langs/php.js';
11
+ import { swift } from './langs/swift.js';
12
+ import { dart } from './langs/dart.js';
13
+
14
+ /**
15
+ * All language specs. To add a language: create `langs/<name>.ts` exporting a
16
+ * LangSpec and add it here. The grammar must exist in @repomix/tree-sitter-wasms.
17
+ */
18
+ export const SPECS: LangSpec[] = [
19
+ typescript,
20
+ tsx,
21
+ javascript,
22
+ python,
23
+ go,
24
+ rust,
25
+ java,
26
+ csharp,
27
+ c,
28
+ cpp,
29
+ ruby,
30
+ php,
31
+ swift,
32
+ dart,
33
+ ];
34
+
35
+ const BY_ID = new Map<string, LangSpec>();
36
+ const BY_EXT = new Map<string, LangSpec>();
37
+
38
+ function rebuild(): void {
39
+ BY_ID.clear();
40
+ BY_EXT.clear();
41
+ for (const spec of SPECS) {
42
+ BY_ID.set(spec.id, spec);
43
+ for (const ext of spec.extensions) {
44
+ if (!BY_EXT.has(ext)) BY_EXT.set(ext, spec);
45
+ }
46
+ }
47
+ }
48
+ rebuild();
49
+
50
+ export function registerSpec(spec: LangSpec): void {
51
+ const i = SPECS.findIndex(s => s.id === spec.id);
52
+ if (i >= 0) SPECS[i] = spec;
53
+ else SPECS.push(spec);
54
+ rebuild();
55
+ }
56
+
57
+ export function specById(id: string): LangSpec | undefined {
58
+ return BY_ID.get(id);
59
+ }
60
+
61
+ export function specForExtension(ext: string): LangSpec | undefined {
62
+ return BY_EXT.get(ext.toLowerCase());
63
+ }
64
+
65
+ /**
66
+ * Config language names map to spec ids; a few names enable several specs
67
+ * ("typescript" also enables tsx, "c" and "cpp" share headers).
68
+ */
69
+ export function enabledSpecs(languages: string[] | undefined): LangSpec[] {
70
+ if (!languages || languages.length === 0) return SPECS;
71
+ const want = new Set(languages.map(l => l.toLowerCase()));
72
+ if (want.has('typescript')) want.add('tsx');
73
+ if (want.has('c++')) want.add('cpp');
74
+ if (want.has('c#')) want.add('csharp');
75
+ return SPECS.filter(s => want.has(s.id));
76
+ }
77
+
78
+ export function supportedExtensions(languages?: string[]): Set<string> {
79
+ const out = new Set<string>();
80
+ for (const spec of enabledSpecs(languages)) {
81
+ for (const ext of spec.extensions) out.add(ext);
82
+ }
83
+ return out;
84
+ }
85
+
86
+ export function allLanguageIds(): string[] {
87
+ return SPECS.map(s => s.id);
88
+ }