clast-ai 0.1.2 → 0.2.1

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 (294) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +11 -9
  3. package/README.md +468 -398
  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 +266 -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/migrate.d.ts +35 -0
  59. package/dist/migrate.js +194 -0
  60. package/dist/migrate.js.map +1 -0
  61. package/dist/parser/extractor.d.ts +21 -16
  62. package/dist/parser/extractor.js +700 -228
  63. package/dist/parser/extractor.js.map +1 -1
  64. package/dist/parser/index.d.ts +17 -21
  65. package/dist/parser/index.js +115 -171
  66. package/dist/parser/index.js.map +1 -1
  67. package/dist/parser/langs/c.d.ts +3 -0
  68. package/dist/parser/langs/c.js +879 -0
  69. package/dist/parser/langs/c.js.map +1 -0
  70. package/dist/parser/langs/csharp.d.ts +2 -0
  71. package/dist/parser/langs/csharp.js +548 -0
  72. package/dist/parser/langs/csharp.js.map +1 -0
  73. package/dist/parser/langs/dart.d.ts +2 -0
  74. package/dist/parser/langs/dart.js +893 -0
  75. package/dist/parser/langs/dart.js.map +1 -0
  76. package/dist/parser/langs/go.d.ts +2 -0
  77. package/dist/parser/langs/go.js +455 -0
  78. package/dist/parser/langs/go.js.map +1 -0
  79. package/dist/parser/langs/java.d.ts +2 -0
  80. package/dist/parser/langs/java.js +390 -0
  81. package/dist/parser/langs/java.js.map +1 -0
  82. package/dist/parser/langs/php.d.ts +2 -0
  83. package/dist/parser/langs/php.js +486 -0
  84. package/dist/parser/langs/php.js.map +1 -0
  85. package/dist/parser/langs/python.d.ts +2 -0
  86. package/dist/parser/langs/python.js +216 -0
  87. package/dist/parser/langs/python.js.map +1 -0
  88. package/dist/parser/langs/ruby.d.ts +2 -0
  89. package/dist/parser/langs/ruby.js +602 -0
  90. package/dist/parser/langs/ruby.js.map +1 -0
  91. package/dist/parser/langs/rust.d.ts +2 -0
  92. package/dist/parser/langs/rust.js +931 -0
  93. package/dist/parser/langs/rust.js.map +1 -0
  94. package/dist/parser/langs/swift.d.ts +2 -0
  95. package/dist/parser/langs/swift.js +616 -0
  96. package/dist/parser/langs/swift.js.map +1 -0
  97. package/dist/parser/langs/typescript.d.ts +4 -0
  98. package/dist/parser/langs/typescript.js +535 -0
  99. package/dist/parser/langs/typescript.js.map +1 -0
  100. package/dist/parser/registry.d.ts +16 -0
  101. package/dist/parser/registry.js +87 -0
  102. package/dist/parser/registry.js.map +1 -0
  103. package/dist/parser/returns.d.ts +9 -0
  104. package/dist/parser/returns.js +152 -0
  105. package/dist/parser/returns.js.map +1 -0
  106. package/dist/parser/spec.d.ts +165 -0
  107. package/dist/parser/spec.js +188 -0
  108. package/dist/parser/spec.js.map +1 -0
  109. package/dist/query/arity.d.ts +27 -0
  110. package/dist/query/arity.js +187 -0
  111. package/dist/query/arity.js.map +1 -0
  112. package/dist/query/callgraph.d.ts +113 -0
  113. package/dist/query/callgraph.js +332 -0
  114. package/dist/query/callgraph.js.map +1 -0
  115. package/dist/query/format.d.ts +36 -0
  116. package/dist/query/format.js +136 -0
  117. package/dist/query/format.js.map +1 -0
  118. package/dist/query/hierarchy.d.ts +44 -0
  119. package/dist/query/hierarchy.js +441 -0
  120. package/dist/query/hierarchy.js.map +1 -0
  121. package/dist/query/resolve.d.ts +265 -0
  122. package/dist/query/resolve.js +1957 -0
  123. package/dist/query/resolve.js.map +1 -0
  124. package/dist/query/search.d.ts +35 -0
  125. package/dist/query/search.js +379 -0
  126. package/dist/query/search.js.map +1 -0
  127. package/dist/query/words.d.ts +30 -0
  128. package/dist/query/words.js +201 -0
  129. package/dist/query/words.js.map +1 -0
  130. package/dist/server/index.d.ts +1 -3
  131. package/dist/server/index.js +313 -73
  132. package/dist/server/index.js.map +1 -1
  133. package/dist/tools/context.d.ts +35 -0
  134. package/dist/tools/context.js +79 -0
  135. package/dist/tools/context.js.map +1 -0
  136. package/dist/tools/explore.d.ts +51 -0
  137. package/dist/tools/explore.js +779 -0
  138. package/dist/tools/explore.js.map +1 -0
  139. package/dist/tools/impact.d.ts +21 -0
  140. package/dist/tools/impact.js +743 -0
  141. package/dist/tools/impact.js.map +1 -0
  142. package/dist/tools/search.d.ts +59 -0
  143. package/dist/tools/search.js +843 -0
  144. package/dist/tools/search.js.map +1 -0
  145. package/dist/tools/stats.d.ts +26 -0
  146. package/dist/tools/stats.js +604 -0
  147. package/dist/tools/stats.js.map +1 -0
  148. package/dist/types.d.ts +95 -40
  149. package/dist/types.js +9 -1
  150. package/dist/types.js.map +1 -1
  151. package/dist/version.d.ts +2 -0
  152. package/dist/version.js +3 -0
  153. package/dist/version.js.map +1 -0
  154. package/dist/watcher/index.d.ts +18 -16
  155. package/dist/watcher/index.js +94 -77
  156. package/dist/watcher/index.js.map +1 -1
  157. package/package.json +21 -24
  158. package/plugin/server.mjs +72 -0
  159. package/skills/audit/SKILL.md +37 -0
  160. package/skills/deadcode/SKILL.md +33 -0
  161. package/skills/explore/SKILL.md +54 -0
  162. package/skills/impact/SKILL.md +24 -0
  163. package/skills/status/SKILL.md +11 -0
  164. package/src/cli.ts +265 -0
  165. package/src/config/defaults.ts +50 -32
  166. package/src/config/index.ts +23 -14
  167. package/src/db/index.ts +540 -43
  168. package/src/engine.ts +30 -0
  169. package/src/indexer/imports.ts +241 -0
  170. package/src/indexer/index.ts +369 -0
  171. package/src/indexer/process.ts +63 -0
  172. package/src/indexer/scan.ts +226 -0
  173. package/src/indexer/worker.ts +24 -0
  174. package/src/llm/comment-generator.ts +52 -103
  175. package/src/llm/index.ts +80 -80
  176. package/src/llm/prompts.ts +95 -95
  177. package/src/migrate.ts +213 -0
  178. package/src/parser/extractor.ts +643 -256
  179. package/src/parser/index.ts +105 -205
  180. package/src/parser/langs/c.ts +860 -0
  181. package/src/parser/langs/csharp.ts +519 -0
  182. package/src/parser/langs/dart.ts +820 -0
  183. package/src/parser/langs/go.ts +437 -0
  184. package/src/parser/langs/java.ts +361 -0
  185. package/src/parser/langs/php.ts +450 -0
  186. package/src/parser/langs/python.ts +206 -0
  187. package/src/parser/langs/ruby.ts +554 -0
  188. package/src/parser/langs/rust.ts +904 -0
  189. package/src/parser/langs/swift.ts +590 -0
  190. package/src/parser/langs/typescript.ts +499 -0
  191. package/src/parser/registry.ts +88 -0
  192. package/src/parser/returns.ts +137 -0
  193. package/src/parser/spec.ts +309 -0
  194. package/src/query/arity.ts +173 -0
  195. package/src/query/callgraph.ts +473 -0
  196. package/src/query/format.ts +136 -0
  197. package/src/query/hierarchy.ts +447 -0
  198. package/src/query/resolve.ts +1972 -0
  199. package/src/query/search.ts +430 -0
  200. package/src/query/words.ts +183 -0
  201. package/src/server/index.ts +390 -79
  202. package/src/tools/context.ts +82 -0
  203. package/src/tools/explore.ts +860 -0
  204. package/src/tools/impact.ts +748 -0
  205. package/src/tools/search.ts +886 -0
  206. package/src/tools/stats.ts +633 -0
  207. package/src/types.ts +113 -45
  208. package/src/version.ts +2 -0
  209. package/src/watcher/index.ts +82 -88
  210. package/tsconfig.json +6 -3
  211. package/.mcp.json +0 -8
  212. package/commands/clast-status.md +0 -9
  213. package/dist/config/defaults.d.ts.map +0 -1
  214. package/dist/config/index.d.ts.map +0 -1
  215. package/dist/db/index.d.ts.map +0 -1
  216. package/dist/db/queries.d.ts +0 -44
  217. package/dist/db/queries.d.ts.map +0 -1
  218. package/dist/db/queries.js +0 -251
  219. package/dist/db/queries.js.map +0 -1
  220. package/dist/db/schema.d.ts +0 -3
  221. package/dist/db/schema.d.ts.map +0 -1
  222. package/dist/db/schema.js +0 -94
  223. package/dist/db/schema.js.map +0 -1
  224. package/dist/db/types.d.ts +0 -44
  225. package/dist/db/types.d.ts.map +0 -1
  226. package/dist/db/types.js +0 -2
  227. package/dist/db/types.js.map +0 -1
  228. package/dist/llm/comment-generator.d.ts.map +0 -1
  229. package/dist/llm/index.d.ts.map +0 -1
  230. package/dist/llm/prompts.d.ts.map +0 -1
  231. package/dist/parser/call-resolver.d.ts +0 -15
  232. package/dist/parser/call-resolver.d.ts.map +0 -1
  233. package/dist/parser/call-resolver.js +0 -16
  234. package/dist/parser/call-resolver.js.map +0 -1
  235. package/dist/parser/extractor.d.ts.map +0 -1
  236. package/dist/parser/index.d.ts.map +0 -1
  237. package/dist/parser/languages.d.ts +0 -11
  238. package/dist/parser/languages.d.ts.map +0 -1
  239. package/dist/parser/languages.js +0 -74
  240. package/dist/parser/languages.js.map +0 -1
  241. package/dist/server/index.d.ts.map +0 -1
  242. package/dist/server/register-tools.d.ts +0 -7
  243. package/dist/server/register-tools.d.ts.map +0 -1
  244. package/dist/server/register-tools.js +0 -17
  245. package/dist/server/register-tools.js.map +0 -1
  246. package/dist/server/tools/clast-call-graph.d.ts +0 -4
  247. package/dist/server/tools/clast-call-graph.d.ts.map +0 -1
  248. package/dist/server/tools/clast-call-graph.js +0 -70
  249. package/dist/server/tools/clast-call-graph.js.map +0 -1
  250. package/dist/server/tools/clast-comment.d.ts +0 -5
  251. package/dist/server/tools/clast-comment.d.ts.map +0 -1
  252. package/dist/server/tools/clast-comment.js +0 -64
  253. package/dist/server/tools/clast-comment.js.map +0 -1
  254. package/dist/server/tools/clast-file-summary.d.ts +0 -4
  255. package/dist/server/tools/clast-file-summary.d.ts.map +0 -1
  256. package/dist/server/tools/clast-file-summary.js +0 -42
  257. package/dist/server/tools/clast-file-summary.js.map +0 -1
  258. package/dist/server/tools/clast-get-context.d.ts +0 -4
  259. package/dist/server/tools/clast-get-context.d.ts.map +0 -1
  260. package/dist/server/tools/clast-get-context.js +0 -80
  261. package/dist/server/tools/clast-get-context.js.map +0 -1
  262. package/dist/server/tools/clast-reindex.d.ts +0 -5
  263. package/dist/server/tools/clast-reindex.d.ts.map +0 -1
  264. package/dist/server/tools/clast-reindex.js +0 -36
  265. package/dist/server/tools/clast-reindex.js.map +0 -1
  266. package/dist/server/tools/clast-search.d.ts +0 -4
  267. package/dist/server/tools/clast-search.d.ts.map +0 -1
  268. package/dist/server/tools/clast-search.js +0 -29
  269. package/dist/server/tools/clast-search.js.map +0 -1
  270. package/dist/server/tools/clast-status.d.ts +0 -5
  271. package/dist/server/tools/clast-status.d.ts.map +0 -1
  272. package/dist/server/tools/clast-status.js +0 -23
  273. package/dist/server/tools/clast-status.js.map +0 -1
  274. package/dist/types.d.ts.map +0 -1
  275. package/dist/watcher/gitignore.d.ts +0 -9
  276. package/dist/watcher/gitignore.d.ts.map +0 -1
  277. package/dist/watcher/gitignore.js +0 -38
  278. package/dist/watcher/gitignore.js.map +0 -1
  279. package/dist/watcher/index.d.ts.map +0 -1
  280. package/skills/clast-explore/SKILL.md +0 -41
  281. package/src/db/queries.ts +0 -332
  282. package/src/db/schema.ts +0 -106
  283. package/src/db/types.ts +0 -47
  284. package/src/parser/call-resolver.ts +0 -17
  285. package/src/parser/languages.ts +0 -86
  286. package/src/server/register-tools.ts +0 -30
  287. package/src/server/tools/clast-call-graph.ts +0 -82
  288. package/src/server/tools/clast-comment.ts +0 -76
  289. package/src/server/tools/clast-file-summary.ts +0 -59
  290. package/src/server/tools/clast-get-context.ts +0 -97
  291. package/src/server/tools/clast-reindex.ts +0 -52
  292. package/src/server/tools/clast-search.ts +0 -38
  293. package/src/server/tools/clast-status.ts +0 -37
  294. package/src/watcher/gitignore.ts +0 -42
@@ -0,0 +1,904 @@
1
+ import type { Node } from 'web-tree-sitter';
2
+ import { cleanComment } from '../extractor.js';
3
+ import type { Ctx, DefOpts, LangSpec, Visitor } from '../spec.js';
4
+ import { field, simpleReceiver } from '../spec.js';
5
+
6
+ const MAX_SIG_SLICE = 600;
7
+ const MAX_DOC = 600;
8
+ const MAX_DOC_LINES = 40;
9
+ const MAX_TYPE_TEXT = 80;
10
+
11
+ /** Prelude enum constructors: never user symbols, extremely frequent. */
12
+ const NOISE_CALLS = new Set(['Some', 'Ok', 'Err']);
13
+
14
+ /** Smart pointers / cells: `Arc::new(Foo::new())` is used like a `Foo`. */
15
+ const VALUE_WRAPPERS = new Set(['Arc', 'Rc', 'Box', 'Mutex', 'RwLock', 'RefCell', 'Cell', 'Pin']);
16
+
17
+ /** An item found in macro arguments, applied when the walker reaches its body token tree. */
18
+ interface Pending {
19
+ /** Symbol to open on the body tree ... */
20
+ def?: DefOpts;
21
+ /** ... or a naming-only scope (`impl` blocks). */
22
+ scope?: string;
23
+ /** Trait implemented by the `impl` block. */
24
+ trait?: string | null;
25
+ /** Functions directly inside are methods (trait / impl body). */
26
+ member?: boolean;
27
+ /** Register the symbol as a type for `impl Trait for Type` heritage. */
28
+ isType?: boolean;
29
+ /** `macro_rules!` definition: its body is a template that defines nothing. */
30
+ macroDef?: boolean;
31
+ /** Declared types of parameters / fields: [name, type text]. */
32
+ hints?: Array<[string, string]>;
33
+ }
34
+
35
+ /**
36
+ * Per-file state. A fresh `Ctx` is created for every extraction, so keying on
37
+ * it gives file-local state without touching the framework.
38
+ */
39
+ interface FileState {
40
+ /** Type name -> symbol index, for `impl Trait for Type` heritage. */
41
+ types: Map<string, number>;
42
+ /** End offset of the attribute being walked (token trees inside are not code). */
43
+ attrEnd: number;
44
+ /** End offset of the `macro_rules!` definition being walked (templates define nothing). */
45
+ macroDefEnd: number;
46
+ /** Keyed by the start offset of the body token tree. */
47
+ pending: Map<number, Pending>;
48
+ /** Type of the `impl` block being walked (what `Self` means) and its end offset. */
49
+ implName: string | null;
50
+ implEnd: number;
51
+ }
52
+ const states = new WeakMap<Ctx, FileState>();
53
+ function state(ctx: Ctx): FileState {
54
+ let s = states.get(ctx);
55
+ if (!s) states.set(ctx, (s = { types: new Map(), attrEnd: -1, macroDefEnd: -1, pending: new Map(), implName: null, implEnd: -1 }));
56
+ return s;
57
+ }
58
+
59
+ function isComment(node: Node): boolean {
60
+ return node.type === 'line_comment' || node.type === 'block_comment';
61
+ }
62
+
63
+ function isPub(node: Node): boolean {
64
+ return node.firstChild?.type === 'visibility_modifier';
65
+ }
66
+
67
+ function joinDoc(parts: Node[]): string | undefined {
68
+ if (parts.length === 0) return undefined;
69
+ // Long docs: the beginning is the summary, keep that.
70
+ const doc = parts.slice(0, MAX_DOC_LINES).map(c => cleanComment(c.text)).join('\n').trim();
71
+ return doc.length > MAX_DOC ? doc.slice(0, MAX_DOC - 3) + '...' : doc;
72
+ }
73
+
74
+ /**
75
+ * Doc comment of an item. The framework only looks at comment siblings
76
+ * directly in front of the item, so attributes between doc and item
77
+ * (`/// doc` + `#[derive(..)]` + item) hide it, and for long `///` blocks
78
+ * (one node per line) it keeps the end instead of the beginning. Returns
79
+ * undefined when there is no comment.
80
+ */
81
+ function leadDoc(node: Node): string | undefined {
82
+ let prev = node.previousSibling;
83
+ if (!prev || (prev.type !== 'attribute_item' && !isComment(prev))) return undefined;
84
+ const parts: Node[] = [];
85
+ let nextRow = node.startPosition.row;
86
+ for (let i = 0; prev && i < 400; i++) {
87
+ if (prev.type === 'attribute_item') {
88
+ nextRow = prev.startPosition.row;
89
+ } else if (isComment(prev)) {
90
+ // Line doc comments include their newline: they end at column 0 of the next row.
91
+ const end = prev.endPosition;
92
+ if ((end.column === 0 ? end.row - 1 : end.row) < nextRow - 1) break;
93
+ const before = prev.previousSibling;
94
+ if (before && !isComment(before) && before.endPosition.row === prev.startPosition.row) break;
95
+ parts.unshift(prev);
96
+ nextRow = prev.startPosition.row;
97
+ } else {
98
+ break;
99
+ }
100
+ prev = prev.previousSibling;
101
+ }
102
+ return joinDoc(parts);
103
+ }
104
+
105
+ /** True if the item is preceded by an attribute whose name is `name` (e.g. `macro_export`). */
106
+ function hasAttr(node: Node, name: string): boolean {
107
+ let prev = node.previousSibling;
108
+ for (let i = 0; prev && i < 20; i++) {
109
+ if (prev.type === 'attribute_item') {
110
+ if (prev.namedChildren[0]?.namedChildren[0]?.text === name) return true;
111
+ } else if (!isComment(prev)) {
112
+ return false;
113
+ }
114
+ prev = prev.previousSibling;
115
+ }
116
+ return false;
117
+ }
118
+
119
+ /** Source text of an item up to `end`, without trailing `;` / `,` / whitespace. */
120
+ function itemText(node: Node, ctx: Ctx, end = node.endIndex): string {
121
+ const start = node.startIndex;
122
+ return ctx.src.slice(start, Math.min(end, start + MAX_SIG_SLICE)).replace(/[;,\s]+$/, '');
123
+ }
124
+
125
+ /** Bare name of a type / path node: `S<T>` -> S, `a::b::C` -> C, `&'a mut Foo` -> Foo. */
126
+ function typeName(node: Node | null | undefined): string | null {
127
+ for (let i = 0; node && i < 6; i++) {
128
+ switch (node.type) {
129
+ case 'type_identifier':
130
+ case 'identifier':
131
+ case 'primitive_type':
132
+ return node.text;
133
+ case 'scoped_type_identifier':
134
+ case 'scoped_identifier':
135
+ node = field(node, 'name');
136
+ break;
137
+ case 'generic_type':
138
+ case 'generic_type_with_turbofish':
139
+ case 'reference_type':
140
+ case 'pointer_type':
141
+ node = field(node, 'type');
142
+ break;
143
+ case 'array_type':
144
+ node = field(node, 'element');
145
+ break;
146
+ case 'dynamic_type':
147
+ case 'abstract_type':
148
+ node = field(node, 'trait');
149
+ break;
150
+ default:
151
+ return null;
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+
157
+ /**
158
+ * Raw type text prepared for ctx.typeHint: references, raw pointers and
159
+ * `mut` / `dyn` / `impl` are dropped (`&'a mut Foo` -> Foo); `Self` becomes
160
+ * the type of the enclosing `impl` block.
161
+ */
162
+ function cleanType(raw: string, at: number, st: FileState): string | null {
163
+ const t = raw.replace(/[&*]\s*(?:'\w+\s*)?/g, '').replace(/\b(?:mut|dyn|impl|const)\s+/g, '').trim();
164
+ if (t === 'Self') return at < st.implEnd ? st.implName : null;
165
+ return t || null;
166
+ }
167
+
168
+ function typeText(node: Node | null | undefined, st: FileState): string | null {
169
+ if (!node || node.endIndex - node.startIndex > MAX_TYPE_TEXT) return null;
170
+ return cleanType(node.text, node.startIndex, st);
171
+ }
172
+
173
+ function isTypeName(name: string | null): name is string {
174
+ return !!name && name.length > 1 && name[0]! >= 'A' && name[0]! <= 'Z';
175
+ }
176
+
177
+ /**
178
+ * Type of the value an expression obviously constructs: `Foo { .. }`,
179
+ * `Foo::new(..)` / `Foo::default()` / `m::Foo::with_capacity(..)` (a path
180
+ * whose type segment is capitalised; enum variants `E::V(..)` yield the enum),
181
+ * tuple structs `Foo(..)`, also behind `?`, `.await`, `&` and smart pointer
182
+ * constructors (`Arc::new(Foo::new())`). Other calls are not guessed.
183
+ */
184
+ function valueType(v: Node | null | undefined, st: FileState): string | null {
185
+ for (let i = 0; v && i < 6; i++) {
186
+ switch (v.type) {
187
+ case 'try_expression':
188
+ case 'await_expression':
189
+ case 'parenthesized_expression':
190
+ v = v.namedChildren[0];
191
+ break;
192
+ case 'reference_expression':
193
+ v = field(v, 'value');
194
+ break;
195
+ case 'struct_expression': {
196
+ const name = typeName(field(v, 'name'));
197
+ if (name === 'Self') return v.startIndex < st.implEnd ? st.implName : null;
198
+ return isTypeName(name) ? name : null;
199
+ }
200
+ case 'call_expression': {
201
+ let fn = field(v, 'function');
202
+ if (fn && fn.type === 'generic_function') fn = field(fn, 'function');
203
+ if (!fn) return null;
204
+ if (fn.type === 'identifier') {
205
+ const name = fn.text;
206
+ return isTypeName(name) && !NOISE_CALLS.has(name) ? name : null;
207
+ }
208
+ if (fn.type !== 'scoped_identifier') return null;
209
+ const type = typeName(field(fn, 'path'));
210
+ if (type === 'Self') return v.startIndex < st.implEnd ? st.implName : null;
211
+ if (!isTypeName(type)) {
212
+ // `module::Foo(..)`: tuple struct behind a module path.
213
+ const name = field(fn, 'name')?.text ?? null;
214
+ return type && isTypeName(name) && !NOISE_CALLS.has(name) ? name : null;
215
+ }
216
+ if (!VALUE_WRAPPERS.has(type)) return type;
217
+ const ctor = field(fn, 'name')?.text;
218
+ if (ctor !== 'new' && ctor !== 'pin') return null;
219
+ v = field(v, 'arguments')?.namedChildren[0];
220
+ break;
221
+ }
222
+ default:
223
+ return null;
224
+ }
225
+ }
226
+ return null;
227
+ }
228
+
229
+ /** `impl_item` / `trait_item` that directly contains the member, if any. */
230
+ function container(node: Node): Node | null {
231
+ const list = node.parent;
232
+ if (!list || list.type !== 'declaration_list') return null;
233
+ const owner = list.parent;
234
+ return owner && (owner.type === 'impl_item' || owner.type === 'trait_item') ? owner : null;
235
+ }
236
+
237
+ /** Visibility of a member: own `pub`, or the visibility of the trait that declares it. */
238
+ function memberExported(node: Node, owner: Node | null): boolean {
239
+ if (isPub(node)) return true;
240
+ return !!owner && owner.type === 'trait_item' && isPub(owner);
241
+ }
242
+
243
+ function define(node: Node, ctx: Ctx, opts: DefOpts): number {
244
+ const doc = leadDoc(node);
245
+ if (doc !== undefined) opts.doc = doc;
246
+ return ctx.def(node, opts);
247
+ }
248
+
249
+ const functionItem: Visitor = (node, ctx) => {
250
+ const name = field(node, 'name');
251
+ if (!name) return;
252
+ const owner = container(node);
253
+ // Without a body (trait methods, extern functions) the signature is the whole item.
254
+ define(node, ctx, {
255
+ name: name.text,
256
+ kind: owner ? 'method' : 'function',
257
+ sig: itemText(node, ctx, field(node, 'body')?.startIndex),
258
+ exported: memberExported(node, owner),
259
+ });
260
+ };
261
+
262
+ function fields(list: Node | null, ctx: Ctx): void {
263
+ if (!list || list.type !== 'field_declaration_list') return;
264
+ for (const f of list.namedChildren) {
265
+ if (!f || f.type !== 'field_declaration') continue;
266
+ const name = field(f, 'name');
267
+ if (!name) continue;
268
+ define(f, ctx, { name: name.text, kind: 'field', body: null, scope: false, exported: isPub(f) });
269
+ ctx.typeHint(name.text, typeText(field(f, 'type'), state(ctx)));
270
+ }
271
+ }
272
+
273
+ const structItem: Visitor = (node, ctx) => {
274
+ const name = field(node, 'name');
275
+ if (!name) return;
276
+ const body = field(node, 'body');
277
+ const opts: DefOpts = { name: name.text, kind: 'struct', exported: isPub(node) };
278
+ // Unit and tuple structs: keep the whole declaration (`pub struct Id(pub u32)`).
279
+ if (!body || body.type !== 'field_declaration_list') opts.sig = itemText(node, ctx);
280
+ const sym = define(node, ctx, opts);
281
+ state(ctx).types.set(name.text, sym);
282
+ fields(body, ctx);
283
+ };
284
+
285
+ const enumItem: Visitor = (node, ctx) => {
286
+ const name = field(node, 'name');
287
+ if (!name) return;
288
+ const exported = isPub(node);
289
+ const sym = define(node, ctx, { name: name.text, kind: 'enum', exported });
290
+ state(ctx).types.set(name.text, sym);
291
+ for (const v of field(node, 'body')?.namedChildren ?? []) {
292
+ if (!v || v.type !== 'enum_variant') continue;
293
+ const vName = field(v, 'name');
294
+ if (!vName) continue;
295
+ // Struct-like variants: cut at the field list (it may contain doc comments).
296
+ const vBody = field(v, 'body');
297
+ const end = vBody && vBody.type === 'field_declaration_list' ? vBody.startIndex : v.endIndex;
298
+ define(v, ctx, { name: vName.text, kind: 'constant', sig: itemText(v, ctx, end), scope: false, exported });
299
+ }
300
+ };
301
+
302
+ const traitItem: Visitor = (node, ctx) => {
303
+ const name = field(node, 'name');
304
+ if (!name) return;
305
+ const sym = define(node, ctx, { name: name.text, kind: 'trait', exported: isPub(node) });
306
+ for (const b of field(node, 'bounds')?.namedChildren ?? []) {
307
+ if (!b || b.type === 'lifetime') continue;
308
+ ctx.inherit(sym, heritageName(b), 'extends');
309
+ }
310
+ };
311
+
312
+ const SMART_POINTERS = new Set(['Arc', 'Rc', 'Box', 'Pin']);
313
+
314
+ /** Supertype name with its module path (`futures_io::AsyncRead`), so that it is not taken for a local trait. */
315
+ function heritageName(node: Node | null | undefined): string | null {
316
+ for (let i = 0; node && i < 3 && (node.type === 'generic_type' || node.type === 'higher_ranked_trait_bound'); i++) {
317
+ node = field(node, 'type');
318
+ }
319
+ if (node && node.type === 'scoped_type_identifier') return node.text.replace(/\s+/g, '');
320
+ return typeName(node);
321
+ }
322
+
323
+ /**
324
+ * The type an impl block is for. `impl Schedule for Arc<Handle>` adds methods
325
+ * that are called on handles, so the wrapper is looked through; a blanket
326
+ * `impl<T: Read> Read for Box<T>` stays with the wrapper.
327
+ */
328
+ function implTarget(node: Node): string | null {
329
+ let type = field(node, 'type');
330
+ const params = new Set<string>();
331
+ for (const p of field(node, 'type_parameters')?.namedChildren ?? []) {
332
+ const n = p ? (field(p, 'name') ?? (p.type === 'type_identifier' ? p : p.namedChildren[0])) : null;
333
+ if (n) params.add(n.text);
334
+ }
335
+ for (let i = 0; type && i < 4; i++) {
336
+ if (type.type === 'reference_type') {
337
+ type = field(type, 'type');
338
+ continue;
339
+ }
340
+ if (type.type !== 'generic_type') break;
341
+ const base = typeName(field(type, 'type'));
342
+ if (!base || !SMART_POINTERS.has(base)) break;
343
+ const inner = field(type, 'type_arguments')?.namedChildren.find(c => c && c.type !== 'lifetime') ?? null;
344
+ const innerName = typeName(inner);
345
+ if (!inner || !innerName || params.has(innerName) || innerName === 'Self') break;
346
+ type = inner;
347
+ }
348
+ return typeName(type);
349
+ }
350
+
351
+ /** `impl Type` / `impl Trait for Type`: naming scope only, never a symbol. */
352
+ const implItem: Visitor = (node, ctx) => {
353
+ const name = implTarget(node);
354
+ if (!name) return;
355
+ ctx.scope(name);
356
+ const st = state(ctx);
357
+ st.implName = name;
358
+ st.implEnd = node.endIndex;
359
+ const trait = field(node, 'trait');
360
+ if (!trait) return;
361
+ // Heritage needs the symbol of the implementing type: only known when the
362
+ // type is defined earlier in the same file.
363
+ const sym = st.types.get(name);
364
+ if (sym !== undefined) ctx.inherit(sym, heritageName(trait), 'implements');
365
+ };
366
+
367
+ const modItem: Visitor = (node, ctx) => {
368
+ const name = field(node, 'name');
369
+ if (!name) return;
370
+ if (!field(node, 'body')) {
371
+ // `mod foo;` pulls in foo.rs / foo/mod.rs
372
+ ctx.imp('self::' + name.text, node, [name.text]);
373
+ return;
374
+ }
375
+ define(node, ctx, { name: name.text, kind: 'namespace', exported: isPub(node) });
376
+ };
377
+
378
+ /** `const` / `static`: module level and associated constants, not locals. */
379
+ const constItem: Visitor = (node, ctx) => {
380
+ const name = field(node, 'name');
381
+ if (!name || ctx.inCallable()) return;
382
+ const value = field(node, 'value');
383
+ // Keep short values in the signature, cut long initialisers.
384
+ const long = value && (value.endIndex - value.startIndex > 80 || value.startPosition.row !== value.endPosition.row);
385
+ define(node, ctx, {
386
+ name: name.text,
387
+ kind: 'constant',
388
+ sig: itemText(node, ctx, long ? value.startIndex : node.endIndex).replace(/\s*=$/, ''),
389
+ scope: false,
390
+ exported: memberExported(node, container(node)),
391
+ });
392
+ };
393
+
394
+ const typeItem: Visitor = (node, ctx) => {
395
+ const name = field(node, 'name');
396
+ if (!name) return;
397
+ const owner = container(node);
398
+ const sym = define(node, ctx, {
399
+ name: name.text,
400
+ kind: 'type',
401
+ sig: itemText(node, ctx),
402
+ scope: false,
403
+ exported: memberExported(node, owner),
404
+ });
405
+ if (!owner) state(ctx).types.set(name.text, sym);
406
+ };
407
+
408
+ const macroDefinition: Visitor = (node, ctx) => {
409
+ const name = field(node, 'name');
410
+ if (!name) return;
411
+ state(ctx).macroDefEnd = node.endIndex;
412
+ define(node, ctx, {
413
+ name: name.text,
414
+ kind: 'macro',
415
+ sig: 'macro_rules! ' + name.text,
416
+ exported: hasAttr(node, 'macro_export'),
417
+ });
418
+ };
419
+
420
+ function recordCall(fn: Node | null, node: Node, ctx: Ctx): void {
421
+ for (let i = 0; fn && i < 3; i++) {
422
+ switch (fn.type) {
423
+ case 'identifier': {
424
+ const name = fn.text;
425
+ if (!NOISE_CALLS.has(name)) ctx.call(name, node);
426
+ return;
427
+ }
428
+ case 'scoped_identifier': {
429
+ const path = field(fn, 'path');
430
+ // `Vec::<u8>::new` -> receiver `Vec`
431
+ const recv = path && (path.type === 'generic_type' || path.type === 'bracketed_type')
432
+ ? typeName(path)
433
+ : simpleReceiver(path);
434
+ ctx.call(field(fn, 'name')?.text, node, recv ?? '?');
435
+ return;
436
+ }
437
+ case 'field_expression':
438
+ // Always a receiver ('?' for method chains): a method call is never a bare call.
439
+ ctx.call(field(fn, 'field')?.text, node, simpleReceiver(field(fn, 'value')) ?? '?');
440
+ return;
441
+ case 'generic_function': // `foo::<T>()`, `x.collect::<Vec<_>>()`
442
+ fn = field(fn, 'function');
443
+ break;
444
+ default:
445
+ return;
446
+ }
447
+ }
448
+ }
449
+
450
+ const callExpression: Visitor = (node, ctx) => {
451
+ recordCall(field(node, 'function'), node, ctx);
452
+ };
453
+
454
+ const macroInvocation: Visitor = (node, ctx) => {
455
+ const m = field(node, 'macro');
456
+ if (!m) return;
457
+ if (m.type === 'identifier') ctx.call(m.text, node);
458
+ else if (m.type === 'scoped_identifier') ctx.call(field(m, 'name')?.text, node, simpleReceiver(field(m, 'path')) ?? '?');
459
+ };
460
+
461
+ /** `x: &Foo`, `mut x: Foo` in function and closure parameter lists. */
462
+ const parameter: Visitor = (node, ctx) => {
463
+ const list = node.parent;
464
+ if (!list) return;
465
+ if (list.type !== 'closure_parameters') {
466
+ const owner = list.parent?.type;
467
+ if (owner !== 'function_item' && owner !== 'function_signature_item') return;
468
+ }
469
+ const pat = field(node, 'pattern');
470
+ if (!pat || pat.type !== 'identifier') return;
471
+ const type = typeText(field(node, 'type'), state(ctx));
472
+ if (type) ctx.typeHint(pat.text, type);
473
+ };
474
+
475
+ /** `let x: Foo = ..`, `let x = Foo::new(..)`, `let x = Foo { .. }` */
476
+ const letDeclaration: Visitor = (node, ctx) => {
477
+ const pat = field(node, 'pattern');
478
+ if (!pat || pat.type !== 'identifier') return;
479
+ const st = state(ctx);
480
+ const declared = field(node, 'type');
481
+ const type = declared ? typeText(declared, st) : valueType(field(node, 'value'), st);
482
+ if (type) ctx.typeHint(pat.text, type);
483
+ };
484
+
485
+ /** `Foo { a, b }` uses the type. */
486
+ const structExpression: Visitor = (node, ctx) => {
487
+ const name = typeName(field(node, 'name'));
488
+ if (name && name !== 'Self') ctx.call(name, node);
489
+ };
490
+
491
+ const attributeItem: Visitor = (node, ctx) => {
492
+ state(ctx).attrEnd = node.endIndex;
493
+ };
494
+
495
+ // ---------------------------------------------------------------------------
496
+ // Macro arguments (token trees)
497
+ // ---------------------------------------------------------------------------
498
+
499
+ const ITEM_KEYWORDS = new Set(['fn', 'struct', 'enum', 'union', 'trait', 'mod', 'impl', 'const', 'static', 'type']);
500
+ const ITEM_MODIFIERS = new Set(['pub', 'async', 'unsafe', 'const', 'extern', 'default', 'string_literal']);
501
+ const ITEM_KINDS: Record<string, DefOpts['kind']> = {
502
+ struct: 'struct', union: 'struct', enum: 'enum', trait: 'trait', mod: 'namespace',
503
+ const: 'constant', static: 'constant', type: 'type',
504
+ };
505
+
506
+ function isGroup(node: Node | undefined | null, open: string): node is Node {
507
+ return !!node && node.type === 'token_tree' && node.firstChild?.type === open;
508
+ }
509
+
510
+ /**
511
+ * Line range of an item recovered from macro arguments: from its first token
512
+ * (visibility / `async` / keyword) to its last one. There is no single node
513
+ * that covers it, and the framework takes the range of a definition from a
514
+ * node, so this builds the few node properties `Ctx.def` reads.
515
+ * `doc` is the last doc comment in front of the item (for the doc line).
516
+ */
517
+ function span(first: Node, last: Node, doc: Node | null | undefined): Node {
518
+ return {
519
+ startIndex: first.startIndex,
520
+ endIndex: last.endIndex,
521
+ startPosition: first.startPosition,
522
+ endPosition: last.endPosition,
523
+ previousSibling: doc ?? null,
524
+ parent: null,
525
+ } as unknown as Node;
526
+ }
527
+
528
+ /** Doc comments in front of token `cs[s]`, skipping `#[attr]` pairs. */
529
+ function docBefore(cs: Node[], s: number): Node[] {
530
+ const parts: Node[] = [];
531
+ for (let d = s - 1; d >= 0 && parts.length < 400;) {
532
+ const t = cs[d]!;
533
+ if (isComment(t)) {
534
+ parts.unshift(t);
535
+ d--;
536
+ } else if (isGroup(t, '[') && d >= 1 && cs[d - 1]!.type === '#') {
537
+ d -= 2;
538
+ } else {
539
+ break;
540
+ }
541
+ }
542
+ return parts;
543
+ }
544
+
545
+ /**
546
+ * Declared types in a parameter list or struct body given as a token group:
547
+ * `(future: F, meta: SpawnMeta<'_>)`, `{ #[pin] pub inner: Inner, .. }`.
548
+ */
549
+ function groupHints(group: Node, ctx: Ctx, st: FileState): Array<[string, string]> {
550
+ const out: Array<[string, string]> = [];
551
+ const len = group.endIndex - group.startIndex;
552
+ if (len < 5 || len > 4000) return out;
553
+ const text = ctx.src.slice(group.startIndex + 1, group.endIndex - 1).replace(/\/\/[^\n]*/g, '');
554
+ const entry = (start: number, end: number): void => {
555
+ const m = /^\s*(?:#\[[^\]]*\]\s*)*(?:pub(?:\s*\([^)]*\))?\s+)?(?:mut\s+)?([A-Za-z_]\w*)\s*:(?!:)\s*(\S[^]*)$/.exec(text.slice(start, end));
556
+ if (!m || m[2]!.length > MAX_TYPE_TEXT) return;
557
+ const type = cleanType(m[2]!, group.startIndex, st);
558
+ if (type) out.push([m[1]!, type]);
559
+ };
560
+ let depth = 0;
561
+ let start = 0;
562
+ for (let i = 0; i < text.length; i++) {
563
+ const ch = text.charCodeAt(i);
564
+ if (ch === 60 || ch === 40 || ch === 91 || ch === 123) depth++; // < ( [ {
565
+ else if (ch === 41 || ch === 93 || ch === 125) depth--; // ) ] }
566
+ else if (ch === 62) { // > but not -> / =>
567
+ const before = text.charCodeAt(i - 1);
568
+ if (before !== 45 && before !== 61) depth--;
569
+ } else if (ch === 44 && depth === 0) { // ,
570
+ entry(start, i);
571
+ start = i + 1;
572
+ }
573
+ }
574
+ entry(start, text.length);
575
+ return out;
576
+ }
577
+
578
+ /**
579
+ * `macro_rules! name { .. }` inside macro arguments (`doc! { macro_rules! select { .. } }`).
580
+ * `cs[i]` is the `macro_rules` identifier.
581
+ */
582
+ function macroRules(cs: Node[], i: number, st: FileState): void {
583
+ const name = cs[i + 2];
584
+ const body = cs[i + 3];
585
+ if (cs[i + 1]?.type !== '!' || !name || name.type !== 'identifier' || !body || body.type !== 'token_tree') return;
586
+ if (st.pending.has(body.startIndex)) return;
587
+ let exported = false;
588
+ for (let d = i - 1; d >= 1; d--) {
589
+ const t = cs[d]!;
590
+ if (isComment(t)) continue;
591
+ if (!isGroup(t, '[') || cs[d - 1]!.type !== '#') break;
592
+ if (t.endIndex - t.startIndex < 40 && t.text.includes('macro_export')) exported = true;
593
+ d--;
594
+ }
595
+ const docParts = docBefore(cs, i);
596
+ st.pending.set(body.startIndex, {
597
+ def: {
598
+ name: name.text,
599
+ kind: 'macro',
600
+ sig: 'macro_rules! ' + name.text,
601
+ doc: joinDoc(docParts) ?? '',
602
+ exported,
603
+ range: span(cs[i]!, body, docParts[docParts.length - 1]),
604
+ },
605
+ macroDef: true,
606
+ });
607
+ }
608
+
609
+ /**
610
+ * Receiver of a call recovered from tokens; `cs[k]` is the called name.
611
+ * `x.f(..)` -> x, `self.q.f(..)` -> self.q, `A::B::f(..)` -> A::B, call
612
+ * chains and other expressions -> '?', a bare `f(..)` -> null.
613
+ */
614
+ function tokenReceiver(cs: Node[], k: number): string | null {
615
+ const sep = cs[k - 1]?.type;
616
+ if (sep !== '.' && sep !== '::') return null;
617
+ let recv = '';
618
+ for (let j = k - 2, parts = 0; j >= 0; j -= 2, parts++) {
619
+ const t = cs[j]!;
620
+ const type = t.type;
621
+ if (type !== 'identifier' && type !== 'self' && type !== 'crate' && type !== 'super') return '?';
622
+ if (parts >= (sep === '.' ? 2 : 4)) return '?';
623
+ recv = recv ? t.text + sep + recv : t.text;
624
+ const before = cs[j - 1]?.type;
625
+ if (before === sep) continue;
626
+ return before === '.' || before === '::' ? '?' : recv;
627
+ }
628
+ return recv || '?';
629
+ }
630
+
631
+ /** `impl<T> fmt::Debug for &'a Foo<T> where ..` -> [Foo, Debug] */
632
+ function implHeader(header: string): [string, string | null] | null {
633
+ let h = header;
634
+ for (let i = 0; i < 8; i++) {
635
+ const next = h.replace(/<[^<>]*>/g, '');
636
+ if (next === h) break;
637
+ h = next;
638
+ }
639
+ const m = /^impl\s*(?:!?\s*([\w:]+)\s+for\s+)?(?:[&*]\s*(?:'\w+\s+)?|(?:mut|const|dyn)\s+)*([\w:]+)/.exec(h);
640
+ if (!m) return null;
641
+ const last = (p: string) => p.slice(p.lastIndexOf(':') + 1);
642
+ const name = last(m[2]!);
643
+ return name ? [name, m[1] ? last(m[1]) : null] : null;
644
+ }
645
+
646
+ /**
647
+ * Items inside macro arguments (`cfg_rt! { pub fn spawn() {..} }`,
648
+ * `pin_project! { struct X {..} }`) are plain token sequences. Recognise
649
+ * `<keyword> <name> ... { body }` and define the symbol when the walker
650
+ * reaches the body tree, so that it scopes the nested tokens like a parsed
651
+ * item does. `cs[i]` is the keyword token.
652
+ */
653
+ function macroItem(cs: Node[], i: number, member: boolean, ctx: Ctx, st: FileState): void {
654
+ const kw = cs[i]!.type;
655
+
656
+ if (kw === 'impl') {
657
+ const before = i > 0 ? cs[i - 1]!.type : ';';
658
+ if (before !== ';' && before !== '{' && before !== 'token_tree' && before !== 'unsafe' && before !== 'default' &&
659
+ before !== 'line_comment' && before !== 'block_comment') return; // `-> impl Trait`, `x: impl Trait`
660
+ for (let j = i + 1; j < cs.length && j < i + 80; j++) {
661
+ const t = cs[j]!;
662
+ if (t.type === ';') return;
663
+ if (!isGroup(t, '{')) continue;
664
+ if (st.pending.has(t.startIndex)) return;
665
+ const start = cs[i]!.startIndex;
666
+ const parsed = implHeader(ctx.src.slice(start, Math.min(t.startIndex, start + 400)));
667
+ if (!parsed) return;
668
+ st.pending.set(t.startIndex, { scope: parsed[0], trait: parsed[1], member: true });
669
+ return;
670
+ }
671
+ return;
672
+ }
673
+
674
+ let nameNode = cs[i + 1];
675
+ if (nameNode && nameNode.type === 'mutable_specifier') nameNode = cs[i + 2];
676
+ if (!nameNode || nameNode.type !== 'identifier') return;
677
+ const name = nameNode.text;
678
+
679
+ // Modifiers in front of the keyword: `pub(crate) async unsafe fn`
680
+ let s = i;
681
+ let exported = false;
682
+ while (s > 0) {
683
+ const t = cs[s - 1]!.type;
684
+ if (ITEM_MODIFIERS.has(t)) {
685
+ if (t === 'pub') exported = true;
686
+ s--;
687
+ } else if (t === 'token_tree' && s >= 2 && cs[s - 2]!.type === 'pub') {
688
+ exported = true;
689
+ s -= 2;
690
+ } else {
691
+ break;
692
+ }
693
+ }
694
+ const docParts = docBefore(cs, s);
695
+ const lastDoc = docParts[docParts.length - 1];
696
+ const doc = joinDoc(docParts) ?? '';
697
+ const first = cs[s]!;
698
+ const start = first.startIndex;
699
+ const sigTo = (end: number) => ctx.src.slice(start, Math.min(end, start + MAX_SIG_SLICE)).replace(/[;,\s]+$/, '');
700
+
701
+ if (kw === 'const' || kw === 'static' || kw === 'type') {
702
+ if (kw !== 'type' && ctx.inCallable()) return;
703
+ let eq = -1;
704
+ for (let j = i + 2; j < cs.length && j < i + 200; j++) {
705
+ const t = cs[j]!;
706
+ if (t.type === '=') {
707
+ if (eq < 0) eq = t.startIndex;
708
+ } else if (t.type === ';') {
709
+ const long = eq >= 0 && t.startIndex - eq > 80;
710
+ ctx.def(nameNode, {
711
+ name, kind: ITEM_KINDS[kw]!, sig: sigTo(long ? eq : t.startIndex), doc, scope: false,
712
+ range: span(first, t, lastDoc),
713
+ exported: exported || (member && kw === 'type'),
714
+ });
715
+ return;
716
+ }
717
+ }
718
+ return;
719
+ }
720
+
721
+ let body: Node | null = null;
722
+ let params: Node | null = null;
723
+ let last: Node | null = null;
724
+ let end = -1;
725
+ for (let j = i + 2; j < cs.length && j < i + 120; j++) {
726
+ const t = cs[j]!;
727
+ if (t.type === ';') { end = t.startIndex; last = t; break; }
728
+ if (isGroup(t, '{')) { body = t; break; }
729
+ if (!params && kw === 'fn' && isGroup(t, '(')) params = t;
730
+ }
731
+ if (kw === 'mod' && !body) {
732
+ if (end >= 0) ctx.imp('self::' + name, nameNode, [name]);
733
+ return;
734
+ }
735
+ if (!body && end < 0) return;
736
+ const isType = kw === 'struct' || kw === 'union' || kw === 'enum';
737
+ const opts: DefOpts = {
738
+ name,
739
+ kind: kw === 'fn' ? (member ? 'method' : 'function') : ITEM_KINDS[kw]!,
740
+ sig: sigTo(body ? body.startIndex : end),
741
+ doc,
742
+ exported,
743
+ // The whole item, not only its body: `pub fn spawn<F>(..) -> .. where .. { .. }`
744
+ range: span(first, body ?? last ?? nameNode, lastDoc),
745
+ };
746
+ if (body && !st.pending.has(body.startIndex)) {
747
+ const typed = kw === 'fn' ? params : kw === 'struct' || kw === 'union' ? body : null;
748
+ const hints = typed ? groupHints(typed, ctx, st) : undefined;
749
+ st.pending.set(body.startIndex, { def: opts, member: kw === 'trait', isType, hints });
750
+ } else {
751
+ opts.scope = false;
752
+ const sym = ctx.def(nameNode, opts);
753
+ if (isType) st.types.set(name, sym);
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Macro arguments are not parsed as expressions or items. Recover the obvious
759
+ * calls: an identifier directly followed by a parenthesised group (`foo(..)`,
760
+ * `x.bar(..)`, `A::new(..)`) or by `!` and a group (nested macros); and the
761
+ * items (see macroItem).
762
+ */
763
+ const tokenTree: Visitor = (node, ctx) => {
764
+ const st = state(ctx);
765
+ if (node.startIndex < st.attrEnd) return;
766
+ if (node.firstChild?.type === '[' && node.previousSibling?.type === '#') {
767
+ st.attrEnd = node.endIndex; // `#[cfg(..)]` inside macro arguments / macro templates
768
+ return;
769
+ }
770
+ let member = false;
771
+ const p = st.pending.get(node.startIndex);
772
+ if (p) {
773
+ st.pending.delete(node.startIndex);
774
+ if (p.def) {
775
+ const sym = ctx.def(node, p.def);
776
+ if (p.isType) st.types.set(p.def.name, sym);
777
+ if (p.macroDef) st.macroDefEnd = node.endIndex;
778
+ if (p.hints) for (const [name, type] of p.hints) ctx.typeHint(name, type);
779
+ } else if (p.scope) {
780
+ ctx.scope(p.scope);
781
+ const sym = st.types.get(p.scope);
782
+ if (p.trait && sym !== undefined) ctx.inherit(sym, p.trait, 'implements');
783
+ }
784
+ member = !!p.member;
785
+ }
786
+ const items = node.startIndex >= st.macroDefEnd;
787
+ const cs = node.children as Node[];
788
+ for (let i = 0; i < cs.length; i++) {
789
+ const c = cs[i];
790
+ if (!c) continue;
791
+ const type = c.type;
792
+ if (type === 'token_tree') {
793
+ const prev = i > 0 ? cs[i - 1] : null;
794
+ if (!prev) continue;
795
+ if (prev.type === 'identifier') {
796
+ // `fn name(..)` is a declaration, not a call.
797
+ if (c.firstChild?.type === '(' && !(i > 1 && cs[i - 2]?.type === 'fn')) {
798
+ const name = prev.text;
799
+ if (!NOISE_CALLS.has(name)) ctx.call(name, prev, tokenReceiver(cs, i - 1));
800
+ }
801
+ } else if (prev.type === '!') {
802
+ const m = i > 1 ? cs[i - 2] : null;
803
+ if (m && m.type === 'identifier') ctx.call(m.text, m);
804
+ }
805
+ } else if (!items) {
806
+ continue;
807
+ } else if (ITEM_KEYWORDS.has(type)) {
808
+ macroItem(cs, i, member, ctx, st);
809
+ } else if (type === 'identifier' && c.endIndex - c.startIndex === 11 && c.text === 'macro_rules') {
810
+ macroRules(cs, i, st);
811
+ }
812
+ }
813
+ };
814
+
815
+ // ---------------------------------------------------------------------------
816
+ // Imports
817
+ // ---------------------------------------------------------------------------
818
+
819
+ function useTree(node: Node | null, prefix: string, decl: Node, ctx: Ctx, depth: number): void {
820
+ if (!node || depth > 8) return;
821
+ const join = (p: string) => (prefix ? prefix + '::' + p : p);
822
+ switch (node.type) {
823
+ case 'identifier':
824
+ case 'crate':
825
+ case 'super':
826
+ case 'metavariable':
827
+ ctx.imp(join(node.text), decl, [node.text]);
828
+ return;
829
+ case 'self': // `use a::b::{self}` imports `a::b`
830
+ if (prefix) ctx.imp(prefix, decl, [prefix.slice(prefix.lastIndexOf(':') + 1)]);
831
+ return;
832
+ case 'scoped_identifier': {
833
+ const name = field(node, 'name');
834
+ ctx.imp(join(node.text), decl, name ? [name.text] : []);
835
+ return;
836
+ }
837
+ case 'use_as_clause': {
838
+ const path = field(node, 'path');
839
+ const alias = field(node, 'alias');
840
+ if (!path) return;
841
+ const source = path.type === 'self' && prefix ? prefix : join(path.text);
842
+ const original = source.slice(source.lastIndexOf('::') + 1).replace(/^:/, '');
843
+ // `use a::Foo as Bar`: bind the alias, remember what it stands for.
844
+ ctx.imp(source, decl, alias ? [alias.text !== original && /^\w+$/.test(original) ? `${alias.text}=${original}` : alias.text] : []);
845
+ return;
846
+ }
847
+ case 'use_wildcard': {
848
+ const path = node.namedChildren[0];
849
+ ctx.imp(path ? join(path.text) + '::*' : (prefix ? prefix + '::*' : '*'), decl, ['*']);
850
+ return;
851
+ }
852
+ case 'scoped_use_list': {
853
+ const path = field(node, 'path');
854
+ useTree(field(node, 'list'), path ? join(path.text) : prefix, decl, ctx, depth + 1);
855
+ return;
856
+ }
857
+ case 'use_list':
858
+ for (const c of node.namedChildren) if (c) useTree(c, prefix, decl, ctx, depth + 1);
859
+ return;
860
+ }
861
+ }
862
+
863
+ const useDeclaration: Visitor = (node, ctx) => {
864
+ useTree(field(node, 'argument'), '', node, ctx, 0);
865
+ };
866
+
867
+ const externCrate: Visitor = (node, ctx) => {
868
+ const name = field(node, 'name');
869
+ if (name) ctx.imp(name.text, node, [field(node, 'alias')?.text ?? name.text]);
870
+ };
871
+
872
+ export const rust: LangSpec = {
873
+ id: 'rust',
874
+ grammar: 'rust',
875
+ extensions: ['.rs'],
876
+ packageTypes: true,
877
+ identifiers: ['identifier', 'type_identifier', 'field_identifier'],
878
+ comments: ['line_comment', 'block_comment'],
879
+ visit: {
880
+ function_item: functionItem,
881
+ function_signature_item: functionItem,
882
+ struct_item: structItem,
883
+ union_item: structItem,
884
+ enum_item: enumItem,
885
+ trait_item: traitItem,
886
+ impl_item: implItem,
887
+ mod_item: modItem,
888
+ const_item: constItem,
889
+ static_item: constItem,
890
+ type_item: typeItem,
891
+ associated_type: typeItem,
892
+ macro_definition: macroDefinition,
893
+ call_expression: callExpression,
894
+ macro_invocation: macroInvocation,
895
+ struct_expression: structExpression,
896
+ parameter: parameter,
897
+ let_declaration: letDeclaration,
898
+ attribute_item: attributeItem,
899
+ inner_attribute_item: attributeItem,
900
+ token_tree: tokenTree,
901
+ use_declaration: useDeclaration,
902
+ extern_crate_declaration: externCrate,
903
+ },
904
+ };