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,590 @@
1
+ import type { Node } from 'web-tree-sitter';
2
+ import type { SymbolKind } from '../../types.js';
3
+ import type { Ctx, LangSpec, Visitor } from '../spec.js';
4
+ import { field, simpleReceiver } from '../spec.js';
5
+
6
+ const MAX_SIG_SLICE = 400;
7
+ const MAX_TYPE_TEXT = 120;
8
+ const MAX_INLINE_VALUE = 60;
9
+
10
+ /** Bodies whose direct children are members of a type. */
11
+ const MEMBER_BODIES = new Set(['class_body', 'enum_class_body', 'protocol_body']);
12
+
13
+ /**
14
+ * Types declared so far in the file being extracted (simple name -> symbol),
15
+ * so `extension Foo: Bar` can attach the conformance to Foo. The Ctx object
16
+ * is created once per file.
17
+ */
18
+ const declaredTypes = new WeakMap<Ctx, Map<string, number>>();
19
+
20
+ function modifiersOf(node: Node): Node | null {
21
+ const first = node.firstChild;
22
+ return first && first.type === 'modifiers' ? first : null;
23
+ }
24
+
25
+ /** `private` / `fileprivate` (but not `private(set)`, which only restricts the setter). */
26
+ function isPrivate(mods: Node | null): boolean {
27
+ if (!mods) return false;
28
+ for (const c of mods.children) {
29
+ if (!c || c.type !== 'visibility_modifier') continue;
30
+ const first = c.firstChild;
31
+ if (c.childCount === 1 && first && (first.type === 'private' || first.type === 'fileprivate')) return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ /** Offset where the signature starts: after leading attributes (`@MainActor`, `@available(...)`). */
37
+ function sigStart(node: Node, mods: Node | null): number {
38
+ if (!mods) return node.startIndex;
39
+ for (const c of mods.children) {
40
+ if (!c) continue;
41
+ if (c.type === 'attribute' || c.type === 'comment' || c.type === 'multiline_comment') continue;
42
+ return c.startIndex;
43
+ }
44
+ const next = mods.nextSibling;
45
+ return next ? next.startIndex : node.startIndex;
46
+ }
47
+
48
+ function sliceSig(ctx: Ctx, start: number, end: number): string {
49
+ if (end - start > MAX_SIG_SLICE) end = start + MAX_SIG_SLICE;
50
+ return ctx.src.slice(start, end).replace(/\s*[{=]\s*$/, '');
51
+ }
52
+
53
+ /** Signature from after the attributes up to `stop` (a body node) or the node end. */
54
+ function signature(node: Node, mods: Node | null, ctx: Ctx, stop: Node | null): string {
55
+ const start = sigStart(node, mods);
56
+ return sliceSig(ctx, start, stop && stop.startIndex > start ? stop.startIndex : node.endIndex);
57
+ }
58
+
59
+ const MAX_SIG = 236;
60
+
61
+ /**
62
+ * Signature of a callable. Long parameter lists are elided in the middle
63
+ * (`func f(a: A, ...) -> R`) so the return type survives truncation.
64
+ */
65
+ function callableSignature(node: Node, mods: Node | null, ctx: Ctx, stop: Node | null): string {
66
+ const start = sigStart(node, mods);
67
+ let end = stop && stop.startIndex > start ? stop.startIndex : node.endIndex;
68
+ if (end - start <= MAX_SIG) return sliceSig(ctx, start, end);
69
+ if (end - start > 4000) end = start + 4000;
70
+ const full = ctx.src.slice(start, end).replace(/\s+/g, ' ').replace(/\s*[{=]\s*$/, '').trim();
71
+ if (full.length <= MAX_SIG) return full;
72
+ const open = full.indexOf('(');
73
+ if (open === -1) return full;
74
+ // Matching close of the parameter list and the top-level commas inside it.
75
+ let depth = 0;
76
+ let close = -1;
77
+ const commas: number[] = [];
78
+ for (let i = open; i < full.length; i++) {
79
+ const ch = full[i];
80
+ if (ch === '(' || ch === '[' || ch === '{') depth++;
81
+ else if (ch === ')' || ch === ']' || ch === '}') {
82
+ if (--depth === 0) { close = i; break; }
83
+ } else if (ch === ',' && depth === 1) commas.push(i);
84
+ }
85
+ if (close === -1) return full;
86
+ const tail = full.slice(close);
87
+ const budget = MAX_SIG - tail.length - 4;
88
+ let cut = -1;
89
+ for (const c of commas) {
90
+ if (c > budget) break;
91
+ cut = c;
92
+ }
93
+ if (cut === -1) return full;
94
+ return full.slice(0, cut + 1) + ' ...' + tail;
95
+ }
96
+
97
+ /** Identifier text without the backticks of escaped names (`default`). */
98
+ function identText(node: Node): string {
99
+ const t = node.text;
100
+ return t.charCodeAt(0) === 96 ? t.replace(/`/g, '') : t;
101
+ }
102
+
103
+ /** Standard-library protocols that often come first in a class's inheritance clause. */
104
+ const KNOWN_PROTOCOLS = new Set([
105
+ 'Sendable', 'Codable', 'Decodable', 'Encodable', 'Hashable', 'Equatable', 'Comparable', 'Identifiable',
106
+ 'CustomStringConvertible', 'CustomDebugStringConvertible', 'Error', 'LocalizedError', 'ObservableObject',
107
+ 'AnyObject', 'Sequence', 'Collection', 'CaseIterable', 'RawRepresentable', 'View',
108
+ ]);
109
+
110
+ /** Dotted name of a `user_type` without generic arguments: `Foo.Bar<T>` -> `Foo.Bar`. */
111
+ function userTypeName(node: Node | null | undefined): string | null {
112
+ if (!node) return null;
113
+ if (node.type === 'type_identifier' || node.type === 'simple_identifier') return node.text;
114
+ if (node.type !== 'user_type') return null;
115
+ let out = '';
116
+ for (const c of node.namedChildren) {
117
+ if (c && c.type === 'type_identifier') out = out ? out + '.' + c.text : c.text;
118
+ }
119
+ return out || null;
120
+ }
121
+
122
+ /** Raw type text for ctx.typeHint, or null for types that never name a class (arrays, tuples, closures). */
123
+ function hintType(type: Node | null | undefined): string | null {
124
+ let n = type ?? null;
125
+ for (let i = 0; n && i < 4; i++) {
126
+ switch (n.type) {
127
+ case 'user_type':
128
+ return n.endIndex - n.startIndex > MAX_TYPE_TEXT ? userTypeName(n) : n.text;
129
+ case 'type_identifier':
130
+ return n.text;
131
+ case 'optional_type':
132
+ n = field(n, 'wrapped') ?? n.namedChild(0);
133
+ break;
134
+ case 'existential_type': // any Foo
135
+ case 'opaque_type': // some Foo
136
+ n = n.namedChild(n.namedChildCount - 1);
137
+ break;
138
+ default:
139
+ return null;
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+
145
+ /** Class constructed by an initialiser expression: `Foo()`, `Foo<T>(x)`, `y as! Foo`. */
146
+ function constructedType(value: Node | null | undefined): string | null {
147
+ if (!value) return null;
148
+ switch (value.type) {
149
+ case 'call_expression': {
150
+ const callee = value.firstChild;
151
+ if (callee && callee.type === 'simple_identifier') {
152
+ const name = callee.text;
153
+ return /^[A-Z]/.test(name) ? name : null;
154
+ }
155
+ return null;
156
+ }
157
+ case 'constructor_expression':
158
+ return hintType(field(value, 'constructed_type'));
159
+ case 'as_expression':
160
+ return hintType(field(value, 'name'));
161
+ default:
162
+ return null;
163
+ }
164
+ }
165
+
166
+ const TYPE_KINDS: Record<string, SymbolKind> = {
167
+ class: 'class',
168
+ struct: 'struct',
169
+ enum: 'enum',
170
+ actor: 'class',
171
+ };
172
+
173
+ /** class / struct / enum / actor / extension: the grammar uses one node type for all of them. */
174
+ const classDeclaration: Visitor = (node, ctx) => {
175
+ const nameNode = field(node, 'name');
176
+ if (!nameNode) return;
177
+ const kindNode = field(node, 'declaration_kind');
178
+ const keyword = kindNode ? kindNode.type : 'class';
179
+ const mods = modifiersOf(node);
180
+
181
+ if (keyword === 'extension') {
182
+ // Not a symbol: members are named after the extended type.
183
+ const name = userTypeName(nameNode);
184
+ if (!name) return;
185
+ const known = declaredTypes.get(ctx);
186
+ if (known) {
187
+ const sym = known.get(name.slice(name.lastIndexOf('.') + 1));
188
+ if (sym !== undefined) {
189
+ for (const c of node.namedChildren) {
190
+ if (c && c.type === 'inheritance_specifier') ctx.inherit(sym, userTypeName(field(c, 'inherits_from')), 'implements');
191
+ }
192
+ }
193
+ }
194
+ ctx.scope(name);
195
+ return;
196
+ }
197
+
198
+ const name = nameNode.text;
199
+ const kind = TYPE_KINDS[keyword] ?? 'class';
200
+ const sym = ctx.def(node, {
201
+ name,
202
+ kind,
203
+ sig: signature(node, mods, ctx, field(node, 'body')),
204
+ exported: !isPrivate(mods),
205
+ });
206
+ let known = declaredTypes.get(ctx);
207
+ if (!known) declaredTypes.set(ctx, (known = new Map()));
208
+ if (!known.has(name)) known.set(name, sym);
209
+
210
+ let first = keyword === 'class';
211
+ for (const c of node.namedChildren) {
212
+ if (!c || c.type !== 'inheritance_specifier') continue;
213
+ const base = userTypeName(field(c, 'inherits_from'));
214
+ if (!base) continue;
215
+ ctx.inherit(sym, base, first && !KNOWN_PROTOCOLS.has(base) ? 'extends' : 'implements');
216
+ first = false;
217
+ }
218
+ };
219
+
220
+ const protocolDeclaration: Visitor = (node, ctx) => {
221
+ const nameNode = field(node, 'name');
222
+ if (!nameNode) return;
223
+ const mods = modifiersOf(node);
224
+ const name = nameNode.text;
225
+ const sym = ctx.def(node, {
226
+ name,
227
+ kind: 'interface',
228
+ sig: signature(node, mods, ctx, field(node, 'body')),
229
+ exported: !isPrivate(mods),
230
+ });
231
+ let known = declaredTypes.get(ctx);
232
+ if (!known) declaredTypes.set(ctx, (known = new Map()));
233
+ if (!known.has(name)) known.set(name, sym);
234
+ for (const c of node.namedChildren) {
235
+ if (c && c.type === 'inheritance_specifier') ctx.inherit(sym, userTypeName(field(c, 'inherits_from')), 'extends');
236
+ }
237
+ };
238
+
239
+ function isMember(node: Node): boolean {
240
+ const p = node.parent;
241
+ return !!p && MEMBER_BODIES.has(p.type);
242
+ }
243
+
244
+ /** Members of a protocol take the protocol's visibility. */
245
+ function memberExported(node: Node, mods: Node | null): boolean {
246
+ if (isPrivate(mods)) return false;
247
+ const body = node.parent;
248
+ if (body && (body.type === 'protocol_body' || node.type === 'enum_entry')) {
249
+ const decl = body.parent;
250
+ if (decl && isPrivate(modifiersOf(decl))) return false;
251
+ }
252
+ return true;
253
+ }
254
+
255
+ /** `func`, protocol method requirements and operator functions (`static func == (...)`). */
256
+ const functionDeclaration: Visitor = (node, ctx) => {
257
+ const nameNode = field(node, 'name');
258
+ if (!nameNode || nameNode.endIndex - nameNode.startIndex > 100) return;
259
+ const mods = modifiersOf(node);
260
+ ctx.def(node, {
261
+ name: identText(nameNode),
262
+ kind: isMember(node) ? 'method' : 'function',
263
+ sig: callableSignature(node, mods, ctx, field(node, 'body')),
264
+ exported: memberExported(node, mods),
265
+ });
266
+ };
267
+
268
+ const fixedName = (name: string): Visitor => (node, ctx) => {
269
+ const mods = modifiersOf(node);
270
+ let stop = field(node, 'body');
271
+ if (!stop) {
272
+ // subscript: the accessor block has no field name.
273
+ const last = node.lastChild;
274
+ if (last && last.type === 'computed_property') stop = last;
275
+ }
276
+ ctx.def(node, {
277
+ name,
278
+ kind: 'method',
279
+ sig: callableSignature(node, mods, ctx, stop),
280
+ exported: memberExported(node, mods),
281
+ });
282
+ };
283
+
284
+ const typealiasDeclaration: Visitor = (node, ctx) => {
285
+ const nameNode = field(node, 'name');
286
+ if (!nameNode) return;
287
+ const mods = modifiersOf(node);
288
+ ctx.def(node, {
289
+ name: nameNode.text,
290
+ kind: 'type',
291
+ sig: signature(node, mods, ctx, null),
292
+ exported: memberExported(node, mods),
293
+ });
294
+ };
295
+
296
+ /** `case a = 1, b(Int)`: one constant per name. */
297
+ const enumEntry: Visitor = (node, ctx) => {
298
+ const exported = memberExported(node, modifiersOf(node));
299
+ let name: Node | null = null;
300
+ let end = 0;
301
+ const flush = (): void => {
302
+ if (!name) return;
303
+ ctx.def(node, {
304
+ name: identText(name),
305
+ kind: 'constant',
306
+ sig: 'case ' + sliceSig(ctx, name.startIndex, end),
307
+ scope: false,
308
+ exported,
309
+ });
310
+ };
311
+ const count = node.childCount;
312
+ for (let i = 0; i < count; i++) {
313
+ const c = node.child(i);
314
+ if (!c) continue;
315
+ const f = node.fieldNameForChild(i);
316
+ if (f === 'name' && c.type === 'simple_identifier') {
317
+ flush();
318
+ name = c;
319
+ end = c.endIndex;
320
+ } else if ((f === 'data_contents' || f === 'raw_value') && name) {
321
+ end = c.endIndex;
322
+ }
323
+ }
324
+ flush();
325
+ };
326
+
327
+ interface Binding {
328
+ name: Node;
329
+ type: Node | null;
330
+ value: Node | null;
331
+ /** Accessor / observer block (`{ get set }`, `{ didSet {} }`). */
332
+ block: Node | null;
333
+ end: number;
334
+ }
335
+
336
+ function bindingsOf(node: Node): Binding[] {
337
+ const out: Binding[] = [];
338
+ let cur: Binding | null = null;
339
+ const count = node.childCount;
340
+ for (let i = 0; i < count; i++) {
341
+ const c = node.child(i);
342
+ if (!c || !c.isNamed) continue;
343
+ switch (c.type) {
344
+ case 'pattern': {
345
+ const id = field(c, 'bound_identifier');
346
+ cur = id ? { name: id, type: null, value: null, block: null, end: c.endIndex } : null;
347
+ if (cur) out.push(cur);
348
+ break;
349
+ }
350
+ case 'type_annotation':
351
+ if (cur) {
352
+ cur.type = c.namedChild(c.namedChildCount - 1);
353
+ cur.end = c.endIndex;
354
+ }
355
+ break;
356
+ case 'computed_property':
357
+ case 'willset_didset_block':
358
+ case 'protocol_property_requirements':
359
+ if (cur && !cur.block) cur.block = c;
360
+ break;
361
+ case 'modifiers':
362
+ case 'value_binding_pattern':
363
+ case 'comment':
364
+ case 'multiline_comment':
365
+ case 'type_constraints':
366
+ break;
367
+ default:
368
+ if (cur && !cur.value && !cur.block) {
369
+ cur.value = c;
370
+ cur.end = c.endIndex;
371
+ }
372
+ }
373
+ }
374
+ return out;
375
+ }
376
+
377
+ function isLet(node: Node): boolean {
378
+ for (const c of node.children) {
379
+ if (!c) continue;
380
+ if (c.type === 'value_binding_pattern') return c.firstChild?.type === 'let';
381
+ if (c.type === 'pattern') return c.firstChild?.firstChild?.type === 'let';
382
+ }
383
+ return false;
384
+ }
385
+
386
+ /**
387
+ * Stored and computed properties, protocol property requirements and
388
+ * top-level `let` / `var`. Locals are not symbols: they only contribute a
389
+ * receiver type hint.
390
+ */
391
+ const propertyDeclaration: Visitor = (node, ctx) => {
392
+ const parent = node.parent;
393
+ if (!parent) return;
394
+ const member = MEMBER_BODIES.has(parent.type);
395
+ const bindings = bindingsOf(node);
396
+
397
+ if (!member && parent.type !== 'source_file') {
398
+ if (!ctx.inCallable()) return;
399
+ for (const b of bindings) {
400
+ const type = hintType(b.type) ?? constructedType(b.value);
401
+ if (type) ctx.typeHint(identText(b.name), type);
402
+ }
403
+ return;
404
+ }
405
+
406
+ const mods = modifiersOf(node);
407
+ const exported = memberExported(node, mods);
408
+ const kind: SymbolKind = member ? 'property' : isLet(node) ? 'constant' : 'variable';
409
+ // Inside an extension there is no class symbol to carry the hint; it would land on the file.
410
+ const hintable = !member || ctx.parentKind() !== null;
411
+ const start = sigStart(node, mods);
412
+ const single = bindings.length === 1;
413
+
414
+ for (const b of bindings) {
415
+ const name = identText(b.name);
416
+ let sig: string;
417
+ if (single) {
418
+ let end = b.block ? b.block.startIndex : node.endIndex;
419
+ if (b.value && (b.value.endIndex - b.value.startIndex > MAX_INLINE_VALUE || b.value.endPosition.row !== b.value.startPosition.row)) {
420
+ end = b.value.startIndex;
421
+ } else if (b.block && b.block.type === 'protocol_property_requirements') {
422
+ end = node.endIndex; // `var x: Int { get set }` is short and part of the contract
423
+ }
424
+ sig = sliceSig(ctx, start, end);
425
+ } else {
426
+ // `var a: Int = 0, b: Int`: modifiers + keyword + this binding.
427
+ const head = ctx.src.slice(start, Math.min(bindings[0]!.name.startIndex, start + MAX_SIG_SLICE));
428
+ const end = b.value && b.value.endIndex - b.value.startIndex > MAX_INLINE_VALUE ? b.value.startIndex : b.end;
429
+ sig = head + sliceSig(ctx, b.name.startIndex, end);
430
+ }
431
+ // Only one definition can own the scope of the visited node.
432
+ ctx.def(node, { name, kind, sig, exported, scope: single ? undefined : false });
433
+ if (hintable) {
434
+ const type = hintType(b.type) ?? constructedType(b.value);
435
+ if (type) ctx.typeHint(name, type);
436
+ }
437
+ }
438
+ };
439
+
440
+ /** Function, initialiser and subscript parameters: `label name: Type`. */
441
+ const parameter: Visitor = (node, ctx) => {
442
+ const name = field(node, 'name');
443
+ const type = node.lastNamedChild;
444
+ if (!name || !type || type.startIndex === name.startIndex) return;
445
+ ctx.typeHint(name.text, hintType(type));
446
+ };
447
+
448
+ /** Closure parameters with explicit types: `{ (v: Value) in ... }`. */
449
+ const lambdaParameter: Visitor = (node, ctx) => {
450
+ if (!ctx.inCallable()) return;
451
+ parameter(node, ctx);
452
+ };
453
+
454
+ /** `if let t: Thing = make()`, `guard let ...`, `for item: Item in items`. */
455
+ const typeAnnotation: Visitor = (node, ctx) => {
456
+ const parent = node.parent;
457
+ if (!parent || parent.type === 'property_declaration' || parent.type === 'protocol_property_declaration') return;
458
+ if (!ctx.inCallable()) return;
459
+ const prev = node.previousSibling;
460
+ if (!prev) return;
461
+ const id = prev.type === 'pattern' ? field(prev, 'bound_identifier') : prev;
462
+ if (id && id.type === 'simple_identifier') ctx.typeHint(id.text, hintType(node.namedChild(node.namedChildCount - 1)));
463
+ };
464
+
465
+ /** `if let s = x as? Session`, `guard let s = x as? Session`. */
466
+ const asExpression: Visitor = (node, ctx) => {
467
+ const eq = node.previousSibling;
468
+ if (!eq || eq.type !== '=') return;
469
+ const id = eq.previousSibling;
470
+ if (id && id.type === 'simple_identifier' && ctx.inCallable()) ctx.typeHint(id.text, hintType(field(node, 'name')));
471
+ };
472
+
473
+ /** Receiver text of a member call: `a`, `self.session`, `a?` / `a!` without the sigil, or '?'. */
474
+ function receiverOf(target: Node | null): string {
475
+ if (target && target.type === 'simple_identifier') return identText(target);
476
+ const r = simpleReceiver(target);
477
+ if (!r) return '?';
478
+ if (r === '?') return r;
479
+ return r.replace(/[?!]/g, '') || '?';
480
+ }
481
+
482
+ const callExpression: Visitor = (node, ctx) => {
483
+ const suffix = node.lastChild;
484
+ if (suffix && suffix.type === 'call_suffix') {
485
+ // Subscript access `items[0]` is parsed as a call.
486
+ const args = suffix.firstChild;
487
+ if (args && args.type === 'value_arguments' && args.firstChild?.type === '[') return;
488
+ }
489
+ let callee = node.firstChild;
490
+ for (let i = 0; callee && i < 5; i++) {
491
+ switch (callee.type) {
492
+ case 'simple_identifier':
493
+ ctx.call(identText(callee), node);
494
+ return;
495
+ case 'navigation_expression': {
496
+ const name = field(field(callee, 'suffix'), 'suffix');
497
+ if (!name || name.type !== 'simple_identifier') return;
498
+ const target = field(callee, 'target');
499
+ const method = identText(name);
500
+ if (method === 'init' && target && target.type === 'simple_identifier') {
501
+ // `Foo.init(x)` constructs a Foo.
502
+ const cls = target.text;
503
+ if (/^[A-Z]/.test(cls) && cls !== 'Self') {
504
+ ctx.call(cls, node);
505
+ return;
506
+ }
507
+ }
508
+ // `Foo().bar()` / `Foo<T>().bar()`: the receiver's class is evident.
509
+ // (The grammar puts a leading `try` / `await` on the receiver: `try Foo().bar()`.)
510
+ let made = target;
511
+ if (made && (made.type === 'try_expression' || made.type === 'await_expression')) made = field(made, 'expr');
512
+ const rtype = made && (made.type === 'call_expression' || made.type === 'constructor_expression')
513
+ ? constructedType(made)
514
+ : null;
515
+ ctx.call(method, node, receiverOf(target), rtype);
516
+ return;
517
+ }
518
+ case 'prefix_expression': {
519
+ const target = field(callee, 'target');
520
+ if (callee.firstChild?.type === '.') {
521
+ // Implicit member: `.custom(r: 1)`, `.init(x)`.
522
+ if (target && target.type === 'simple_identifier') ctx.call(target.text, node, '?');
523
+ return;
524
+ }
525
+ callee = target;
526
+ break;
527
+ }
528
+ case 'try_expression':
529
+ case 'await_expression':
530
+ callee = field(callee, 'expr');
531
+ break;
532
+ default:
533
+ // The grammar parses `a + f(x)` as a call whose callee is `a + f`: the real callee is the right operand.
534
+ callee = field(callee, 'rhs');
535
+ }
536
+ }
537
+ };
538
+
539
+ /** `Foo<Int>(x: 1)`. */
540
+ const constructorExpression: Visitor = (node, ctx) => {
541
+ const name = userTypeName(field(node, 'constructed_type'));
542
+ if (name) ctx.call(name.slice(name.lastIndexOf('.') + 1), node);
543
+ };
544
+
545
+ const IMPORT_KINDS = new Set(['struct', 'class', 'enum', 'protocol', 'typealias', 'func', 'let', 'var']);
546
+
547
+ /** `import Foundation`, `@testable import X`, `import struct Foo.Bar`. */
548
+ const importDeclaration: Visitor = (node, ctx) => {
549
+ let path: Node | null = null;
550
+ let kinded = false;
551
+ for (const c of node.children) {
552
+ if (!c) continue;
553
+ if (c.type === 'identifier') { path = c; break; }
554
+ if (!c.isNamed && IMPORT_KINDS.has(c.type)) kinded = true;
555
+ }
556
+ if (!path || path.endIndex - path.startIndex > 200) return;
557
+ const source = path.text.replace(/\s+/g, '');
558
+ const dot = source.lastIndexOf('.');
559
+ if (kinded && dot > 0) ctx.imp(source.slice(0, dot), node, [source.slice(dot + 1)]);
560
+ else ctx.imp(source, node);
561
+ };
562
+
563
+ export const swift: LangSpec = {
564
+ id: 'swift',
565
+ grammar: 'swift',
566
+ extensions: ['.swift'],
567
+ identifiers: ['simple_identifier', 'type_identifier'],
568
+ comments: ['comment', 'multiline_comment'],
569
+ visit: {
570
+ class_declaration: classDeclaration,
571
+ protocol_declaration: protocolDeclaration,
572
+ function_declaration: functionDeclaration,
573
+ protocol_function_declaration: functionDeclaration,
574
+ init_declaration: fixedName('init'),
575
+ deinit_declaration: fixedName('deinit'),
576
+ subscript_declaration: fixedName('subscript'),
577
+ typealias_declaration: typealiasDeclaration,
578
+ associatedtype_declaration: typealiasDeclaration,
579
+ enum_entry: enumEntry,
580
+ property_declaration: propertyDeclaration,
581
+ protocol_property_declaration: propertyDeclaration,
582
+ parameter,
583
+ lambda_parameter: lambdaParameter,
584
+ type_annotation: typeAnnotation,
585
+ as_expression: asExpression,
586
+ call_expression: callExpression,
587
+ constructor_expression: constructorExpression,
588
+ import_declaration: importDeclaration,
589
+ },
590
+ };