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,137 @@
1
+ import { typeName } from './spec.js';
2
+
3
+ /**
4
+ * Best-effort return type of a function from its signature text. Used to
5
+ * type call chains (`a.b().c()`): the receiver of `c` is whatever `b`
6
+ * returns. Purely syntactic; returns null when unsure.
7
+ *
8
+ * `selfType` is the enclosing class, substituted for Self / self / static /
9
+ * this (fluent builders).
10
+ */
11
+ export function returnTypeOf(
12
+ signature: string,
13
+ name: string,
14
+ lang: string,
15
+ selfType: string | null,
16
+ allowLowercase: boolean
17
+ ): string | null {
18
+ const raw = rawReturnType(signature, name, lang);
19
+ if (!raw) return null;
20
+ const text = raw.trim();
21
+ if (/^(&\s*(mut\s+)?)?(Self|self|static|\$this|this)\b/.test(text)) return selfType;
22
+ // Result<T, E> / (T, error): the interesting part is T.
23
+ const result = /^(?:\w+::)*Result\s*<\s*(.+)$/s.exec(text);
24
+ const inner = result ? firstTypeArg(result[1]!) : text.startsWith('(') ? firstTypeArg(text.slice(1)) : text;
25
+ if (/^(&\s*(mut\s+)?)?(Self|self|static)\b/.test(inner.trim())) return selfType;
26
+ return typeName(inner, allowLowercase);
27
+ }
28
+
29
+ function firstTypeArg(text: string): string {
30
+ let depth = 0;
31
+ for (let i = 0; i < text.length; i++) {
32
+ const ch = text[i];
33
+ if (ch === '<' || ch === '(' || ch === '[') depth++;
34
+ else if (ch === '>' || ch === ')' || ch === ']') {
35
+ if (depth === 0) return text.slice(0, i);
36
+ depth--;
37
+ } else if (ch === ',' && depth === 0) return text.slice(0, i);
38
+ }
39
+ return text;
40
+ }
41
+
42
+ /** Index just past the parameter list that follows `name` in `sig`, or -1. */
43
+ function afterParams(sig: string, name: string): number {
44
+ let from = 0;
45
+ for (;;) {
46
+ const at = sig.indexOf(name, from);
47
+ if (at === -1) return -1;
48
+ let i = at + name.length;
49
+ // optional generic parameters between the name and '('
50
+ while (i < sig.length && /\s/.test(sig[i]!)) i++;
51
+ if (sig[i] === '<' || sig[i] === '[') {
52
+ const open = sig[i]!;
53
+ const close = open === '<' ? '>' : ']';
54
+ let depth = 0;
55
+ for (; i < sig.length; i++) {
56
+ if (sig[i] === open) depth++;
57
+ else if (sig[i] === close && --depth === 0) { i++; break; }
58
+ }
59
+ while (i < sig.length && /\s/.test(sig[i]!)) i++;
60
+ }
61
+ if (sig[i] === '(') {
62
+ let depth = 0;
63
+ for (; i < sig.length; i++) {
64
+ if (sig[i] === '(') depth++;
65
+ else if (sig[i] === ')' && --depth === 0) return i + 1;
66
+ }
67
+ return -1;
68
+ }
69
+ from = at + name.length;
70
+ }
71
+ }
72
+
73
+ function rawReturnType(sig: string, name: string, lang: string): string | null {
74
+ switch (lang) {
75
+ case 'python': {
76
+ const m = /->\s*([^:]+?)\s*:?\s*$/.exec(sig);
77
+ return m ? m[1]!.replace(/^["']|["']$/g, '') : null;
78
+ }
79
+ case 'rust':
80
+ case 'swift': {
81
+ const end = afterParams(sig, name);
82
+ if (end === -1) return null;
83
+ // Swift: `func f() async throws -> T`, `throws(MyError) -> T`.
84
+ const m = /^\s*(?:(?:async|rethrows|throws(?:\([^)]*\))?)\s+)*->\s*(.+?)(?:\s+where\b.*)?$/s.exec(sig.slice(end));
85
+ if (!m) return null;
86
+ // `(any Foo)?` / `any Foo` / `some Foo` -> Foo
87
+ return m[1]!.replace(/^\((?:any|some)\s+([^()]+)\)/, '$1').replace(/^(?:any|some)\s+/, '');
88
+ }
89
+ case 'typescript':
90
+ case 'tsx':
91
+ case 'php':
92
+ case 'kotlin': {
93
+ const end = afterParams(sig, name);
94
+ const tail = end !== -1 ? sig.slice(end) : (/\)\s*(:.*)$/s.exec(sig)?.[1] ?? '');
95
+ const m = /^\s*:\s*([^={]+?)\s*(=>)?\s*$/s.exec(tail);
96
+ return m ? m[1]! : null;
97
+ }
98
+ case 'go': {
99
+ const end = afterParams(sig, name);
100
+ if (end === -1) return null;
101
+ const rest = sig.slice(end).trim();
102
+ return rest ? rest : null;
103
+ }
104
+ case 'java':
105
+ case 'csharp':
106
+ case 'dart':
107
+ case 'c':
108
+ case 'cpp': {
109
+ // <modifiers> <ReturnType> [Class::]name(...)
110
+ const at = sig.search(new RegExp('(?:[\\w~]+\\s*::\\s*)*\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*(?:<[^()]*>)?\\s*\\('));
111
+ if (at <= 0) return null;
112
+ let head = sig
113
+ .slice(0, at)
114
+ .replace(/@\w+(\([^)]*\))?/g, ' ')
115
+ .replace(/\[[^\]]*\]/g, ' ')
116
+ .replace(/\btemplate\s*<[^>]*>/g, ' ')
117
+ .trim();
118
+ if (!head) return null;
119
+ // The trailing type expression, balancing <>.
120
+ let depth = 0;
121
+ let i = head.length - 1;
122
+ for (; i >= 0; i--) {
123
+ const ch = head[i]!;
124
+ if (ch === '>') depth++;
125
+ else if (ch === '<') depth--;
126
+ else if (/\s/.test(ch) && depth === 0) break;
127
+ }
128
+ head = head.slice(i + 1);
129
+ if (/^(public|private|protected|static|final|virtual|inline|override|async|abstract|explicit|constexpr|new|return|void)$/.test(head)) {
130
+ return null;
131
+ }
132
+ return head;
133
+ }
134
+ default:
135
+ return null;
136
+ }
137
+ }
@@ -0,0 +1,309 @@
1
+ import type { Node } from 'web-tree-sitter';
2
+ import type { SymbolKind } from '../types.js';
3
+
4
+ /**
5
+ * Options for registering a definition found in the syntax tree.
6
+ */
7
+ export interface DefOpts {
8
+ name: string;
9
+ kind: SymbolKind;
10
+ /**
11
+ * Outer node used for the symbol's line range and doc-comment lookup, when it
12
+ * differs from the definition node (e.g. `export_statement`, a decorated
13
+ * definition, or a `lexical_declaration` wrapping a declarator).
14
+ */
15
+ range?: Node | null;
16
+ /**
17
+ * Body node. The signature is the source text from the start of the range up
18
+ * to the start of the body. Defaults to the node's `body` field. Pass `null`
19
+ * to use the whole (first line of the) node as the signature.
20
+ */
21
+ body?: Node | null;
22
+ /** Explicit signature text (overrides body-based slicing). */
23
+ sig?: string;
24
+ /** Explicit doc text (e.g. Python docstrings). */
25
+ doc?: string | null;
26
+ /**
27
+ * Extra qualifier prepended to the qualified name for definitions that are
28
+ * lexically outside their container: Go receivers, C++ `Foo::bar`, etc.
29
+ */
30
+ qualifier?: string | null;
31
+ exported?: boolean;
32
+ /**
33
+ * When false the symbol does not open a scope: nested definitions are not
34
+ * qualified by it and calls inside are attributed to the enclosing symbol.
35
+ * Required when defining a symbol for a node other than the one being
36
+ * visited (e.g. enum members registered from the enum visitor). Default true.
37
+ */
38
+ scope?: boolean;
39
+ /**
40
+ * Define the symbol as a member of the nearest enclosing container (class,
41
+ * struct, ...) instead of the innermost scope. Used for instance attributes
42
+ * assigned inside methods (`self.x = ...` in Python `__init__`).
43
+ */
44
+ inContainer?: boolean;
45
+ }
46
+
47
+ /**
48
+ * Context handed to language visitors. All methods refer to the node currently
49
+ * being visited; scopes opened by `def`/`scope` close automatically when the
50
+ * walker leaves that node.
51
+ */
52
+ export interface Ctx {
53
+ readonly src: string;
54
+ /** Register a symbol. Returns its index. */
55
+ def(node: Node, opts: DefOpts): number;
56
+ /** Open a naming-only scope (Rust `impl`, PHP/Java packages, ...). */
57
+ scope(name: string): void;
58
+ /**
59
+ * Record a call made at `node`. `receiverType` may be given when the
60
+ * receiver's class is evident from the expression itself
61
+ * (`new Foo().bar()`, casts); otherwise it is derived from type hints.
62
+ */
63
+ call(name: string | null | undefined, node: Node, receiver?: string | null, receiverType?: string | null): void;
64
+ /**
65
+ * Record an import of `source`. `names` are the names bound in this file;
66
+ * write `alias=original` when the binding is renamed (`import { a as b }`
67
+ * -> `b=a`) and `*` for wildcard imports.
68
+ */
69
+ imp(source: string | null | undefined, node: Node, names?: string[]): void;
70
+ /** Record that symbol `sym` extends/implements `name`. */
71
+ inherit(sym: number, name: string | null | undefined, kind?: 'extends' | 'implements', arity?: number): void;
72
+ /**
73
+ * Record an identifier occurrence under a different name than the source
74
+ * text (e.g. C# `[Authorize]` also references `AuthorizeAttribute`).
75
+ */
76
+ ref(name: string | null | undefined, node: Node): void;
77
+ /**
78
+ * Record the declared type of a parameter, local, or field so that member
79
+ * calls on it (`record.topic()`, `this.repo.find()`) can be resolved to
80
+ * the right class. `type` is raw type text; it is normalised with
81
+ * typeName(). The hint is attached to the nearest enclosing function (or
82
+ * class, for field declarations). Pass `onContainer` = true to attach it
83
+ * to the enclosing class even from inside a method: constructor parameter
84
+ * properties, `self.x = Foo()` assignments.
85
+ */
86
+ typeHint(name: string | null | undefined, type: string | null | undefined, onContainer?: boolean): void;
87
+ /**
88
+ * Declare the symbol this file exports as its default
89
+ * (`export default X`, `module.exports = X`). Importers that bind it to
90
+ * another local name are then linked to X.
91
+ */
92
+ exportDefault(name: string | null | undefined): void;
93
+ /**
94
+ * Declared type of a variable / parameter visible at the current node
95
+ * (as recorded by typeHint), or null. Lets a spec propagate types through
96
+ * simple assignments such as `this.repo = repo`.
97
+ */
98
+ typeOf(name: string | null | undefined): string | null;
99
+ /** Kind of the innermost enclosing symbol, or null at file level. */
100
+ parentKind(): SymbolKind | null;
101
+ /** True if any enclosing symbol is a function or method. */
102
+ inCallable(): boolean;
103
+ /** Number of enclosing real symbols (0 = file level). */
104
+ depth(): number;
105
+ }
106
+
107
+ /** Return 'skip' to prevent the walker from descending into the node. */
108
+ export type Visitor = (node: Node, ctx: Ctx) => void | 'skip';
109
+
110
+ export interface LangSpec {
111
+ /** Language id used in config and in the database. */
112
+ id: string;
113
+ /** Grammar file name without the `tree-sitter-` prefix and `.wasm` suffix. */
114
+ grammar: string;
115
+ extensions: string[];
116
+ /** Leaf node types recorded as identifier occurrences (reference index). */
117
+ identifiers: string[];
118
+ /** Type names may be lowercase (Go unexported types, C structs); see typeName(). */
119
+ lowercaseTypes?: boolean;
120
+ /** Type hints keep a package qualifier (`pkg.Type`, Go); resolved against the file's imports at query time. */
121
+ packageTypes?: boolean;
122
+ /** Node types whose text is a comment (for doc extraction). */
123
+ comments?: string[];
124
+ /**
125
+ * Node types that may sit between a doc comment and the item it documents
126
+ * (Rust attributes, PHP attributes, ...). They are skipped when looking for
127
+ * the leading comment.
128
+ */
129
+ docSkip?: string[];
130
+ /** Visitors keyed by tree-sitter node type. */
131
+ visit: Record<string, Visitor>;
132
+ /**
133
+ * Optional: pick a different spec id based on file content (used to parse
134
+ * `.h` files as C++ when they look like C++).
135
+ */
136
+ sniff?: (source: string, path: string) => string | undefined;
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Helpers shared by language specs
141
+ // ---------------------------------------------------------------------------
142
+
143
+ export function field(node: Node | null | undefined, name: string): Node | null {
144
+ return node ? node.childForFieldName(name) : null;
145
+ }
146
+
147
+ export function text(node: Node | null | undefined): string {
148
+ return node ? node.text : '';
149
+ }
150
+
151
+ /** First named child with one of the given types. */
152
+ export function childOfType(node: Node | null | undefined, ...types: string[]): Node | null {
153
+ if (!node) return null;
154
+ for (const c of node.namedChildren) {
155
+ if (c && types.includes(c.type)) return c;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ export function childrenOfType(node: Node | null | undefined, ...types: string[]): Node[] {
161
+ if (!node) return [];
162
+ const out: Node[] = [];
163
+ for (const c of node.namedChildren) {
164
+ if (c && types.includes(c.type)) out.push(c);
165
+ }
166
+ return out;
167
+ }
168
+
169
+ /** Nearest ancestor with one of the given types (bounded). */
170
+ export function ancestor(node: Node, types: string[], maxUp = 8): Node | null {
171
+ let cur = node.parent;
172
+ for (let i = 0; cur && i < maxUp; i++) {
173
+ if (types.includes(cur.type)) return cur;
174
+ cur = cur.parent;
175
+ }
176
+ return null;
177
+ }
178
+
179
+ export function stripQuotes(s: string): string {
180
+ return s.replace(/^[bru]*(['"`<])/i, '').replace(/['"`>]$/, '');
181
+ }
182
+
183
+ /** Last segment of a dotted / scoped name: `a.b.C` -> `C`, `a::b` -> `b`. */
184
+ export function lastSegment(s: string): string {
185
+ const m = /([A-Za-z_$][\w$]*)\s*(?:<[^<>]*>)?\s*$/.exec(s);
186
+ return m ? m[1]! : s;
187
+ }
188
+
189
+ /** Receiver marker for member calls whose receiver is a complex expression. */
190
+ export const COMPLEX_RECEIVER = '?';
191
+
192
+ /**
193
+ * Receiver text of a member call. Short simple receivers (`this`, `db`,
194
+ * `Foo`, `this.repo`) are returned as text; anything else (call chains,
195
+ * literals, indexing) becomes COMPLEX_RECEIVER so the resolver still knows
196
+ * the call HAD a receiver and is not a bare `foo()` call. Returns null only
197
+ * when there is no receiver node at all.
198
+ */
199
+ export function simpleReceiver(node: Node | null | undefined): string | null {
200
+ if (!node) return null;
201
+ if (node.endIndex - node.startIndex > 48) return COMPLEX_RECEIVER;
202
+ // `this.grid!.layout()`, `a?.b.run()`, Rust `x?.go()`: the assertion / optional-chain / try marks
203
+ // do not change which object it is.
204
+ const t = node.text.replace(/[!?]+$/, '').replace(/[!?]+(?=\.)/g, '');
205
+ if (!t || /[\s(\[{"'`]/.test(t) || /^\d/.test(t)) return COMPLEX_RECEIVER;
206
+ return t;
207
+ }
208
+
209
+ const TYPE_WRAPPERS = new Set([
210
+ 'Option', 'Optional', 'Arc', 'Rc', 'Box', 'RefCell', 'Mutex', 'RwLock', 'Cell', 'Pin', 'Weak',
211
+ 'Promise', 'Readonly', 'Partial', 'Required', 'NonNullable', 'Awaited', 'Lazy', 'Task', 'ValueTask',
212
+ 'Nullable', 'Future', 'CompletableFuture', 'Supplier', 'Provider', 'Ref', 'Ptr', 'unique_ptr',
213
+ 'shared_ptr', 'weak_ptr', 'Type', 'ClassVar', 'Final', 'Annotated',
214
+ ]);
215
+
216
+ /**
217
+ * Reduce raw type text to the bare name of the class it denotes, or null:
218
+ * `*Site` -> Site, `ProducerRecord<K, V>` -> ProducerRecord,
219
+ * `Option<Arc<Handle>>` -> Handle, `pkg.Foo` / `ns::Foo` -> Foo,
220
+ * `Foo | null` -> Foo, `const Foo&` -> Foo. Collections and primitives
221
+ * are not class types and yield null.
222
+ */
223
+ /** Number of type arguments in `Name<A, B<C>>` (0 when not generic). */
224
+ export function genericArity(text: string): number {
225
+ const lt = text.indexOf('<');
226
+ if (lt === -1) return 0;
227
+ let depth = 0;
228
+ let n = 1;
229
+ for (let i = lt; i < text.length; i++) {
230
+ const ch = text[i];
231
+ if (ch === '<' || ch === '(' || ch === '[') depth++;
232
+ else if (ch === '>' || ch === ')' || ch === ']') {
233
+ if (--depth === 0) return n;
234
+ } else if (ch === ',' && depth === 1) n++;
235
+ }
236
+ return n;
237
+ }
238
+
239
+ export function typeName(raw: string | null | undefined, allowLowercase = false, keepPackage = false): string | null {
240
+ if (!raw) return null;
241
+ let t = raw.trim();
242
+ for (let i = 0; i < 4; i++) {
243
+ // Strip qualifiers, pointer / reference sigils and lifetimes until stable (`&'a mut Foo`).
244
+ for (let guard = 0; guard < 6; guard++) {
245
+ const before = t;
246
+ t = t
247
+ .replace(/^(const|readonly|mut|final|volatile|static|ref|out|in|params|dyn|impl|struct|class|enum|typename|unsigned|signed)\s+/, '')
248
+ .replace(/^[&*?\s:]+/, '')
249
+ .replace(/^'[a-z_]+\s+/, '');
250
+ if (t === before) break;
251
+ }
252
+ t = t
253
+ .replace(/[&*?!\s]+$/, '')
254
+ .replace(/\s*\|\s*(null|undefined|None)\b/g, '')
255
+ .replace(/^(null|undefined|None)\s*\|\s*/, '')
256
+ .trim();
257
+ const m = /^([\w.:\\]+)\s*[<\[](.*)[>\]]$/s.exec(t);
258
+ if (!m) break;
259
+ const outer = m[1]!.split(/\.|::|\\/).pop()!;
260
+ if (!TYPE_WRAPPERS.has(outer)) { t = m[1]!; break; }
261
+ // Unwrap: first type argument at nesting depth 0.
262
+ let depth = 0;
263
+ let end = m[2]!.length;
264
+ for (let k = 0; k < m[2]!.length; k++) {
265
+ const ch = m[2]![k];
266
+ if (ch === '<' || ch === '[' || ch === '(') depth++;
267
+ else if (ch === '>' || ch === ']' || ch === ')') depth--;
268
+ else if (ch === ',' && depth === 0) { end = k; break; }
269
+ }
270
+ t = m[2]!.slice(0, end).trim();
271
+ }
272
+ if (/[|&(){}\[\],<>=\s]/.test(t)) return null;
273
+ const segs = t.split(/\.|::|\\/);
274
+ const name = segs.pop() ?? '';
275
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) return null;
276
+ if (name.length < 2) return null;
277
+ // Go: `pkg.Type` keeps its package so that it is not confused with a same-named type elsewhere.
278
+ if (keepPackage && segs.length >= 1 && /^[A-Z]/.test(name)) {
279
+ // Rust `std::io::Error` / `io::Error`: anything under std / core / alloc is marked "std", otherwise
280
+ // the module the type is taken from (`io`, `crate`, `super`) is kept. Go: `pkg.Type`.
281
+ const rustPath = t.includes('::');
282
+ if (rustPath && segs.some(s => s === 'std' || s === 'core' || s === 'alloc')) return 'std.' + name;
283
+ const q = segs[segs.length - 1]!;
284
+ if ((rustPath || segs.length === 1) && /^[a-z_][a-z0-9_]*$/.test(q)) return q + '.' + name;
285
+ }
286
+ if (!/[A-Z]/.test(name[0]!)) {
287
+ // Lowercase names are primitives / builtins in most languages (int, string, str, bool, ...).
288
+ // Go and C name real types in lowercase too, so those specs opt in, minus the builtins.
289
+ if (!allowLowercase || PRIMITIVE_TYPES.has(name) || /^(u?int|float|complex)\d*(_t)?$/.test(name) || name.endsWith('_t')) return null;
290
+ }
291
+ return name;
292
+ }
293
+
294
+ const PRIMITIVE_TYPES = new Set([
295
+ 'int', 'uint', 'char', 'short', 'long', 'float', 'double', 'void', 'bool', 'boolean', 'byte', 'rune', 'string',
296
+ 'str', 'error', 'any', 'auto', 'var', 'let', 'unsigned', 'signed', 'size', 'uintptr', 'interface', 'struct',
297
+ 'func', 'map', 'chan', 'object', 'number', 'bigint', 'symbol', 'unknown', 'never', 'null', 'undefined', 'dict',
298
+ 'list', 'tuple', 'set', 'bytes', 'usize', 'isize', 'self', 'this', 'array', 'mixed', 'callable', 'iterable',
299
+ 'static', 'const', 'wchar', 'ssize', 'off', 'time', 'file', 'va_list', 'sds',
300
+ // C++ standard library value types: not classes of the project.
301
+ 'vector', 'deque', 'pair', 'queue', 'stack', 'mutex', 'thread', 'atomic', 'function', 'optional', 'variant',
302
+ 'ostream', 'istream', 'ofstream', 'ifstream', 'stringstream', 'unordered_map', 'unordered_set', 'basic_string',
303
+ 'string_view', 'span', 'bitset', 'complex', 'regex', 'iterator', 'allocator',
304
+ ]);
305
+
306
+ /** UPPER_SNAKE names are treated as constants. */
307
+ export function isConstName(name: string): boolean {
308
+ return /^[A-Z][A-Z0-9_]*$/.test(name) && name.length > 1;
309
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Parameter counts read from a signature, used to tell overloads apart and to
3
+ * reject call sites that cannot be calls of a definition (`info.partition()`
4
+ * is not `Partitioner.partition(topic, key, keyBytes, value, valueBytes, cluster)`).
5
+ *
6
+ * Everything here is conservative: when a signature cannot be read with
7
+ * confidence the result is null, which callers treat as "anything fits".
8
+ */
9
+ export interface ParamRange {
10
+ /** Required parameters. */
11
+ min: number;
12
+ /** All parameters; Infinity for variadic functions. */
13
+ max: number;
14
+ /** C# extension method: the first (`this`) parameter is the receiver when called as a member. */
15
+ ext: boolean;
16
+ /** TypeScript: the last required parameter has a type-parameter type (`fire(event: T)`), which may be `void`. */
17
+ voidable?: boolean;
18
+ }
19
+
20
+ /** Languages whose signatures are checked. Others (PHP, Ruby, JavaScript, Swift, Dart) accept any count. */
21
+ export const ARITY_LANGS = new Set(['java', 'csharp', 'go', 'c', 'cpp', 'typescript', 'tsx', 'python', 'rust']);
22
+
23
+ function escapeRe(s: string): string {
24
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
25
+ }
26
+
27
+ /** Index of the `)` matching the `(` at `open`, or -1. Skips string literals. */
28
+ function closing(sig: string, open: number): number {
29
+ let depth = 0;
30
+ for (let i = open; i < sig.length; i++) {
31
+ const ch = sig[i];
32
+ if (ch === '"' || ch === "'" || ch === '`') {
33
+ const end = sig.indexOf(ch, i + 1);
34
+ if (end === -1) return -1;
35
+ i = end;
36
+ } else if (ch === '(') depth++;
37
+ else if (ch === ')' && --depth === 0) return i;
38
+ }
39
+ return -1;
40
+ }
41
+
42
+ /** Split a parameter list on top-level commas. */
43
+ function splitParams(list: string): string[] {
44
+ const out: string[] = [];
45
+ let depth = 0;
46
+ let start = 0;
47
+ for (let i = 0; i < list.length; i++) {
48
+ const ch = list[i];
49
+ if (ch === '"' || ch === "'" || ch === '`') {
50
+ const end = list.indexOf(ch, i + 1);
51
+ if (end === -1) break;
52
+ i = end;
53
+ } else if (ch === '(' || ch === '[' || ch === '{' || ch === '<') depth++;
54
+ else if (ch === ')' || ch === ']' || ch === '}') depth--;
55
+ else if (ch === '>') {
56
+ // Not the arrow of `=>` / `->`.
57
+ const prev = list[i - 1];
58
+ if (prev !== '=' && prev !== '-') depth--;
59
+ } else if (ch === ',' && depth <= 0) {
60
+ out.push(list.slice(start, i));
61
+ start = i + 1;
62
+ depth = 0;
63
+ }
64
+ }
65
+ out.push(list.slice(start));
66
+ return out.map(p => p.trim()).filter(Boolean);
67
+ }
68
+
69
+ /** True when the parameter has a default value (`x = 1`, `x: int = 1`) at its top level. */
70
+ function hasDefault(param: string): boolean {
71
+ let depth = 0;
72
+ for (let i = 0; i < param.length; i++) {
73
+ const ch = param[i];
74
+ if (ch === '(' || ch === '[' || ch === '{' || ch === '<') depth++;
75
+ else if (ch === ')' || ch === ']' || ch === '}' || (ch === '>' && param[i - 1] !== '=' && param[i - 1] !== '-')) depth--;
76
+ else if (ch === '=' && depth <= 0) {
77
+ const next = param[i + 1];
78
+ const prev = param[i - 1];
79
+ if (next !== '=' && next !== '>' && prev !== '=' && prev !== '!' && prev !== '<' && prev !== '>') return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+
85
+ export function paramRange(signature: string, name: string, lang: string): ParamRange | null {
86
+ if (!ARITY_LANGS.has(lang) || !signature || !name) return null;
87
+ let sig = signature;
88
+ if (lang === 'go') {
89
+ // func (r *T) Name(...): skip the receiver.
90
+ const recv = /^func\s*\(/.exec(sig);
91
+ if (recv) {
92
+ const end = closing(sig, recv[0].length - 1);
93
+ if (end === -1) return null;
94
+ sig = sig.slice(end + 1);
95
+ }
96
+ }
97
+ const m = new RegExp('(?:^|[^\\w$])' + escapeRe(name) + '\\s*(?=[<\\[(])').exec(sig);
98
+ if (!m) return null;
99
+ let i = m.index + m[0].length;
100
+ // Type parameters: <T, U> or Go [T any].
101
+ if (sig[i] === '<' || sig[i] === '[') {
102
+ const open = sig[i];
103
+ const close = open === '<' ? '>' : ']';
104
+ let depth = 0;
105
+ for (; i < sig.length; i++) {
106
+ if (sig[i] === open) depth++;
107
+ else if (sig[i] === close && sig[i - 1] !== '=' && sig[i - 1] !== '-' && --depth === 0) { i++; break; }
108
+ }
109
+ while (sig[i] === ' ') i++;
110
+ }
111
+ if (sig[i] !== '(') return null;
112
+ const end = closing(sig, i);
113
+ if (end === -1) return null; // truncated signature
114
+ let params = splitParams(sig.slice(i + 1, end));
115
+
116
+ if (lang === 'c' || lang === 'cpp') {
117
+ if (params.length === 1 && params[0] === 'void') params = [];
118
+ // `int f()` in C says nothing about the parameters.
119
+ else if (params.length === 0 && lang === 'c') return null;
120
+ }
121
+ if (lang === 'python') {
122
+ if (params.length && /^(self|cls)\b/.test(params[0]!)) params = params.slice(1);
123
+ } else if (lang === 'rust') {
124
+ if (params.length && /^(&\s*('\w+\s+)?)?(mut\s+)?self\b/.test(params[0]!)) params = params.slice(1);
125
+ } else if (lang === 'typescript' || lang === 'tsx') {
126
+ if (params.length && /^this\s*:/.test(params[0]!)) params = params.slice(1);
127
+ }
128
+
129
+ let min = 0;
130
+ let max = 0;
131
+ let variadic = false;
132
+ let ext = false;
133
+ let voidable = false;
134
+ for (let k = 0; k < params.length; k++) {
135
+ const p = params[k]!;
136
+ if (lang === 'python') {
137
+ if (p === '*' || p === '/') continue; // keyword-only / positional-only markers
138
+ if (p.startsWith('*')) { variadic = true; continue; }
139
+ }
140
+ if (p.includes('...') || (lang === 'csharp' && /^(\[[^\]]*\]\s*)*params\s/.test(p))) {
141
+ variadic = true;
142
+ continue;
143
+ }
144
+ if (lang === 'csharp' && k === 0 && /^(\[[^\]]*\]\s*)*this\s/.test(p)) ext = true;
145
+ max++;
146
+ const optional =
147
+ hasDefault(p) ||
148
+ ((lang === 'typescript' || lang === 'tsx') && /^[\w$]+\?\s*(:|$)/.test(p));
149
+ if (!optional) {
150
+ min++;
151
+ voidable = (lang === 'typescript' || lang === 'tsx') && /:\s*(?:[A-Z]|T[A-Z]\w*)\s*$/.test(p);
152
+ }
153
+ }
154
+ return { min, max: variadic ? Infinity : max, ext, voidable };
155
+ }
156
+
157
+ /**
158
+ * Can a call with `argc` arguments be a call of a function with this range?
159
+ * `unbound` marks calls written through a type name (`Base.save(self, x)`,
160
+ * `Type::method(value, x)`), which pass the receiver as an argument.
161
+ */
162
+ export function arityFits(range: ParamRange | null, argc: number, lang: string, unbound: boolean): boolean {
163
+ if (!range || argc < 0) return true;
164
+ if (argc >= range.min && argc <= range.max) return true;
165
+ // `emitter.fire()` on an Emitter<void>: a parameter of type T can be left out when T is void.
166
+ if (range.voidable && argc === range.min - 1) return true;
167
+ // Extension method called as a member: the receiver fills the `this` parameter.
168
+ if (range.ext && argc + 1 >= range.min && argc + 1 <= range.max) return true;
169
+ if (unbound && (lang === 'python' || lang === 'rust') && argc - 1 >= range.min && argc - 1 <= range.max) return true;
170
+ // Go: f(g()) forwards all results of g.
171
+ if (lang === 'go' && argc === 1 && range.min > 1) return true;
172
+ return false;
173
+ }