@llui/compiler 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accessor-resolver.d.ts +58 -0
  3. package/dist/accessor-resolver.d.ts.map +1 -0
  4. package/dist/accessor-resolver.js +119 -0
  5. package/dist/accessor-resolver.js.map +1 -0
  6. package/dist/binding-descriptors.d.ts +105 -0
  7. package/dist/binding-descriptors.d.ts.map +1 -0
  8. package/dist/binding-descriptors.js +340 -0
  9. package/dist/binding-descriptors.js.map +1 -0
  10. package/dist/collect-deps.d.ts +49 -0
  11. package/dist/collect-deps.d.ts.map +1 -0
  12. package/dist/collect-deps.js +444 -0
  13. package/dist/collect-deps.js.map +1 -0
  14. package/dist/compiler-cache.d.ts +20 -0
  15. package/dist/compiler-cache.d.ts.map +1 -0
  16. package/dist/compiler-cache.js +20 -0
  17. package/dist/compiler-cache.js.map +1 -0
  18. package/dist/cross-file-resolver.d.ts +109 -0
  19. package/dist/cross-file-resolver.d.ts.map +1 -0
  20. package/dist/cross-file-resolver.js +530 -0
  21. package/dist/cross-file-resolver.js.map +1 -0
  22. package/dist/cross-file-walker.d.ts +63 -0
  23. package/dist/cross-file-walker.d.ts.map +1 -0
  24. package/dist/cross-file-walker.js +516 -0
  25. package/dist/cross-file-walker.js.map +1 -0
  26. package/dist/diagnostic.d.ts +76 -0
  27. package/dist/diagnostic.d.ts.map +1 -0
  28. package/dist/diagnostic.js +59 -0
  29. package/dist/diagnostic.js.map +1 -0
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +37 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/introspection-factory.d.ts +54 -0
  35. package/dist/introspection-factory.d.ts.map +1 -0
  36. package/dist/introspection-factory.js +46 -0
  37. package/dist/introspection-factory.js.map +1 -0
  38. package/dist/manifest.d.ts +144 -0
  39. package/dist/manifest.d.ts.map +1 -0
  40. package/dist/manifest.js +209 -0
  41. package/dist/manifest.js.map +1 -0
  42. package/dist/module.d.ts +222 -0
  43. package/dist/module.d.ts.map +1 -0
  44. package/dist/module.js +256 -0
  45. package/dist/module.js.map +1 -0
  46. package/dist/modules/_element-helpers.d.ts +4 -0
  47. package/dist/modules/_element-helpers.d.ts.map +1 -0
  48. package/dist/modules/_element-helpers.js +138 -0
  49. package/dist/modules/_element-helpers.js.map +1 -0
  50. package/dist/modules/_msg-variants.d.ts +10 -0
  51. package/dist/modules/_msg-variants.d.ts.map +1 -0
  52. package/dist/modules/_msg-variants.js +97 -0
  53. package/dist/modules/_msg-variants.js.map +1 -0
  54. package/dist/modules/_shared.d.ts +16 -0
  55. package/dist/modules/_shared.d.ts.map +1 -0
  56. package/dist/modules/_shared.js +30 -0
  57. package/dist/modules/_shared.js.map +1 -0
  58. package/dist/modules/accessibility.d.ts +3 -0
  59. package/dist/modules/accessibility.d.ts.map +1 -0
  60. package/dist/modules/accessibility.js +82 -0
  61. package/dist/modules/accessibility.js.map +1 -0
  62. package/dist/modules/accessor-side-effect.d.ts +3 -0
  63. package/dist/modules/accessor-side-effect.d.ts.map +1 -0
  64. package/dist/modules/accessor-side-effect.js +113 -0
  65. package/dist/modules/accessor-side-effect.js.map +1 -0
  66. package/dist/modules/agent-emits-drift.d.ts +3 -0
  67. package/dist/modules/agent-emits-drift.d.ts.map +1 -0
  68. package/dist/modules/agent-emits-drift.js +158 -0
  69. package/dist/modules/agent-emits-drift.js.map +1 -0
  70. package/dist/modules/agent-example-on-payload.d.ts +3 -0
  71. package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
  72. package/dist/modules/agent-example-on-payload.js +53 -0
  73. package/dist/modules/agent-example-on-payload.js.map +1 -0
  74. package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
  75. package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
  76. package/dist/modules/agent-exclusive-annotations.js +68 -0
  77. package/dist/modules/agent-exclusive-annotations.js.map +1 -0
  78. package/dist/modules/agent-missing-intent.d.ts +3 -0
  79. package/dist/modules/agent-missing-intent.d.ts.map +1 -0
  80. package/dist/modules/agent-missing-intent.js +47 -0
  81. package/dist/modules/agent-missing-intent.js.map +1 -0
  82. package/dist/modules/agent-msg-resolvable.d.ts +3 -0
  83. package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
  84. package/dist/modules/agent-msg-resolvable.js +161 -0
  85. package/dist/modules/agent-msg-resolvable.js.map +1 -0
  86. package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
  87. package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
  88. package/dist/modules/agent-nonextractable-handler.js +127 -0
  89. package/dist/modules/agent-nonextractable-handler.js.map +1 -0
  90. package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
  91. package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
  92. package/dist/modules/agent-optional-field-undocumented.js +67 -0
  93. package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
  94. package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
  95. package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
  96. package/dist/modules/agent-tagsend-translator-missing.js +58 -0
  97. package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
  98. package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
  99. package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
  100. package/dist/modules/agent-warning-on-confirm.js +46 -0
  101. package/dist/modules/agent-warning-on-confirm.js.map +1 -0
  102. package/dist/modules/async-update.d.ts +3 -0
  103. package/dist/modules/async-update.d.ts.map +1 -0
  104. package/dist/modules/async-update.js +86 -0
  105. package/dist/modules/async-update.js.map +1 -0
  106. package/dist/modules/binding-descriptors.d.ts +4 -0
  107. package/dist/modules/binding-descriptors.d.ts.map +1 -0
  108. package/dist/modules/binding-descriptors.js +48 -0
  109. package/dist/modules/binding-descriptors.js.map +1 -0
  110. package/dist/modules/bitmask-overflow.d.ts +3 -0
  111. package/dist/modules/bitmask-overflow.d.ts.map +1 -0
  112. package/dist/modules/bitmask-overflow.js +152 -0
  113. package/dist/modules/bitmask-overflow.js.map +1 -0
  114. package/dist/modules/compiler-stamp.d.ts +3 -0
  115. package/dist/modules/compiler-stamp.d.ts.map +1 -0
  116. package/dist/modules/compiler-stamp.js +44 -0
  117. package/dist/modules/compiler-stamp.js.map +1 -0
  118. package/dist/modules/component-meta.d.ts +3 -0
  119. package/dist/modules/component-meta.d.ts.map +1 -0
  120. package/dist/modules/component-meta.js +44 -0
  121. package/dist/modules/component-meta.js.map +1 -0
  122. package/dist/modules/controlled-input.d.ts +3 -0
  123. package/dist/modules/controlled-input.d.ts.map +1 -0
  124. package/dist/modules/controlled-input.js +68 -0
  125. package/dist/modules/controlled-input.js.map +1 -0
  126. package/dist/modules/core-synthesis.d.ts +18 -0
  127. package/dist/modules/core-synthesis.d.ts.map +1 -0
  128. package/dist/modules/core-synthesis.js +748 -0
  129. package/dist/modules/core-synthesis.js.map +1 -0
  130. package/dist/modules/direct-state-in-view.d.ts +3 -0
  131. package/dist/modules/direct-state-in-view.d.ts.map +1 -0
  132. package/dist/modules/direct-state-in-view.js +103 -0
  133. package/dist/modules/direct-state-in-view.js.map +1 -0
  134. package/dist/modules/each-closure-violation.d.ts +3 -0
  135. package/dist/modules/each-closure-violation.d.ts.map +1 -0
  136. package/dist/modules/each-closure-violation.js +255 -0
  137. package/dist/modules/each-closure-violation.js.map +1 -0
  138. package/dist/modules/each-memo.d.ts +15 -0
  139. package/dist/modules/each-memo.d.ts.map +1 -0
  140. package/dist/modules/each-memo.js +115 -0
  141. package/dist/modules/each-memo.js.map +1 -0
  142. package/dist/modules/effect-without-handler.d.ts +3 -0
  143. package/dist/modules/effect-without-handler.d.ts.map +1 -0
  144. package/dist/modules/effect-without-handler.js +92 -0
  145. package/dist/modules/effect-without-handler.js.map +1 -0
  146. package/dist/modules/element-rewrite.d.ts +22 -0
  147. package/dist/modules/element-rewrite.d.ts.map +1 -0
  148. package/dist/modules/element-rewrite.js +1017 -0
  149. package/dist/modules/element-rewrite.js.map +1 -0
  150. package/dist/modules/empty-props.d.ts +3 -0
  151. package/dist/modules/empty-props.d.ts.map +1 -0
  152. package/dist/modules/empty-props.js +50 -0
  153. package/dist/modules/empty-props.js.map +1 -0
  154. package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
  155. package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
  156. package/dist/modules/exhaustive-effect-handling.js +61 -0
  157. package/dist/modules/exhaustive-effect-handling.js.map +1 -0
  158. package/dist/modules/exhaustive-update.d.ts +3 -0
  159. package/dist/modules/exhaustive-update.d.ts.map +1 -0
  160. package/dist/modules/exhaustive-update.js +146 -0
  161. package/dist/modules/exhaustive-update.js.map +1 -0
  162. package/dist/modules/forgotten-spread.d.ts +3 -0
  163. package/dist/modules/forgotten-spread.d.ts.map +1 -0
  164. package/dist/modules/forgotten-spread.js +51 -0
  165. package/dist/modules/forgotten-spread.js.map +1 -0
  166. package/dist/modules/form-boilerplate.d.ts +3 -0
  167. package/dist/modules/form-boilerplate.d.ts.map +1 -0
  168. package/dist/modules/form-boilerplate.js +101 -0
  169. package/dist/modules/form-boilerplate.js.map +1 -0
  170. package/dist/modules/imperative-dom-in-view.d.ts +3 -0
  171. package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
  172. package/dist/modules/imperative-dom-in-view.js +123 -0
  173. package/dist/modules/imperative-dom-in-view.js.map +1 -0
  174. package/dist/modules/item-dedup.d.ts +7 -0
  175. package/dist/modules/item-dedup.d.ts.map +1 -0
  176. package/dist/modules/item-dedup.js +204 -0
  177. package/dist/modules/item-dedup.js.map +1 -0
  178. package/dist/modules/map-on-state-array.d.ts +3 -0
  179. package/dist/modules/map-on-state-array.d.ts.map +1 -0
  180. package/dist/modules/map-on-state-array.js +84 -0
  181. package/dist/modules/map-on-state-array.js.map +1 -0
  182. package/dist/modules/mask-legend.d.ts +10 -0
  183. package/dist/modules/mask-legend.d.ts.map +1 -0
  184. package/dist/modules/mask-legend.js +50 -0
  185. package/dist/modules/mask-legend.js.map +1 -0
  186. package/dist/modules/missing-memo.d.ts +3 -0
  187. package/dist/modules/missing-memo.d.ts.map +1 -0
  188. package/dist/modules/missing-memo.js +114 -0
  189. package/dist/modules/missing-memo.js.map +1 -0
  190. package/dist/modules/msg-annotations.d.ts +9 -0
  191. package/dist/modules/msg-annotations.d.ts.map +1 -0
  192. package/dist/modules/msg-annotations.js +54 -0
  193. package/dist/modules/msg-annotations.js.map +1 -0
  194. package/dist/modules/msg-schema.d.ts +10 -0
  195. package/dist/modules/msg-schema.d.ts.map +1 -0
  196. package/dist/modules/msg-schema.js +70 -0
  197. package/dist/modules/msg-schema.js.map +1 -0
  198. package/dist/modules/namespace-import.d.ts +3 -0
  199. package/dist/modules/namespace-import.d.ts.map +1 -0
  200. package/dist/modules/namespace-import.js +80 -0
  201. package/dist/modules/namespace-import.js.map +1 -0
  202. package/dist/modules/nested-send-in-update.d.ts +3 -0
  203. package/dist/modules/nested-send-in-update.d.ts.map +1 -0
  204. package/dist/modules/nested-send-in-update.js +77 -0
  205. package/dist/modules/nested-send-in-update.js.map +1 -0
  206. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
  207. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
  208. package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
  209. package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
  210. package/dist/modules/no-eager-item-accessor.d.ts +3 -0
  211. package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
  212. package/dist/modules/no-eager-item-accessor.js +74 -0
  213. package/dist/modules/no-eager-item-accessor.js.map +1 -0
  214. package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
  215. package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
  216. package/dist/modules/no-let-reactive-accessor.js +227 -0
  217. package/dist/modules/no-let-reactive-accessor.js.map +1 -0
  218. package/dist/modules/no-list-render-in-sample.d.ts +3 -0
  219. package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
  220. package/dist/modules/no-list-render-in-sample.js +89 -0
  221. package/dist/modules/no-list-render-in-sample.js.map +1 -0
  222. package/dist/modules/no-sample-in-accessor.d.ts +3 -0
  223. package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
  224. package/dist/modules/no-sample-in-accessor.js +141 -0
  225. package/dist/modules/no-sample-in-accessor.js.map +1 -0
  226. package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
  227. package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
  228. package/dist/modules/no-sample-in-reactive-position.js +72 -0
  229. package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
  230. package/dist/modules/pure-update-function.d.ts +3 -0
  231. package/dist/modules/pure-update-function.d.ts.map +1 -0
  232. package/dist/modules/pure-update-function.js +127 -0
  233. package/dist/modules/pure-update-function.js.map +1 -0
  234. package/dist/modules/reactive-paths.d.ts +3 -0
  235. package/dist/modules/reactive-paths.d.ts.map +1 -0
  236. package/dist/modules/reactive-paths.js +77 -0
  237. package/dist/modules/reactive-paths.js.map +1 -0
  238. package/dist/modules/row-factory.d.ts +12 -0
  239. package/dist/modules/row-factory.d.ts.map +1 -0
  240. package/dist/modules/row-factory.js +385 -0
  241. package/dist/modules/row-factory.js.map +1 -0
  242. package/dist/modules/schema-hash.d.ts +15 -0
  243. package/dist/modules/schema-hash.d.ts.map +1 -0
  244. package/dist/modules/schema-hash.js +70 -0
  245. package/dist/modules/schema-hash.js.map +1 -0
  246. package/dist/modules/spread-in-children.d.ts +3 -0
  247. package/dist/modules/spread-in-children.d.ts.map +1 -0
  248. package/dist/modules/spread-in-children.js +144 -0
  249. package/dist/modules/spread-in-children.js.map +1 -0
  250. package/dist/modules/state-mutation.d.ts +3 -0
  251. package/dist/modules/state-mutation.d.ts.map +1 -0
  252. package/dist/modules/state-mutation.js +138 -0
  253. package/dist/modules/state-mutation.js.map +1 -0
  254. package/dist/modules/state-schema.d.ts +8 -0
  255. package/dist/modules/state-schema.d.ts.map +1 -0
  256. package/dist/modules/state-schema.js +55 -0
  257. package/dist/modules/state-schema.js.map +1 -0
  258. package/dist/modules/static-items.d.ts +3 -0
  259. package/dist/modules/static-items.d.ts.map +1 -0
  260. package/dist/modules/static-items.js +125 -0
  261. package/dist/modules/static-items.js.map +1 -0
  262. package/dist/modules/static-on.d.ts +3 -0
  263. package/dist/modules/static-on.d.ts.map +1 -0
  264. package/dist/modules/static-on.js +100 -0
  265. package/dist/modules/static-on.js.map +1 -0
  266. package/dist/modules/string-effect-callback.d.ts +3 -0
  267. package/dist/modules/string-effect-callback.d.ts.map +1 -0
  268. package/dist/modules/string-effect-callback.js +50 -0
  269. package/dist/modules/string-effect-callback.js.map +1 -0
  270. package/dist/modules/structural-mask.d.ts +8 -0
  271. package/dist/modules/structural-mask.d.ts.map +1 -0
  272. package/dist/modules/structural-mask.js +76 -0
  273. package/dist/modules/structural-mask.js.map +1 -0
  274. package/dist/modules/subapp-requires-reason.d.ts +3 -0
  275. package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
  276. package/dist/modules/subapp-requires-reason.js +129 -0
  277. package/dist/modules/subapp-requires-reason.js.map +1 -0
  278. package/dist/modules/text-mask.d.ts +12 -0
  279. package/dist/modules/text-mask.d.ts.map +1 -0
  280. package/dist/modules/text-mask.js +63 -0
  281. package/dist/modules/text-mask.js.map +1 -0
  282. package/dist/modules/view-bag-import.d.ts +3 -0
  283. package/dist/modules/view-bag-import.d.ts.map +1 -0
  284. package/dist/modules/view-bag-import.js +80 -0
  285. package/dist/modules/view-bag-import.js.map +1 -0
  286. package/dist/msg-annotations.d.ts +104 -0
  287. package/dist/msg-annotations.d.ts.map +1 -0
  288. package/dist/msg-annotations.js +242 -0
  289. package/dist/msg-annotations.js.map +1 -0
  290. package/dist/msg-schema.d.ts +130 -0
  291. package/dist/msg-schema.d.ts.map +1 -0
  292. package/dist/msg-schema.js +770 -0
  293. package/dist/msg-schema.js.map +1 -0
  294. package/dist/schema-hash.d.ts +16 -0
  295. package/dist/schema-hash.d.ts.map +1 -0
  296. package/dist/schema-hash.js +31 -0
  297. package/dist/schema-hash.js.map +1 -0
  298. package/dist/state-schema.d.ts +41 -0
  299. package/dist/state-schema.d.ts.map +1 -0
  300. package/dist/state-schema.js +156 -0
  301. package/dist/state-schema.js.map +1 -0
  302. package/dist/transform.d.ts +109 -0
  303. package/dist/transform.d.ts.map +1 -0
  304. package/dist/transform.js +1390 -0
  305. package/dist/transform.js.map +1 -0
  306. package/dist/version.d.ts +11 -0
  307. package/dist/version.d.ts.map +1 -0
  308. package/dist/version.js +11 -0
  309. package/dist/version.js.map +1 -0
  310. package/package.json +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler-cache.js","sourceRoot":"","sources":["../src/compiler-cache.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,GAAG,EAAE,CAAA;AAEtB,MAAM,OAAO,aAAa;IACP,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAA;IAE9D,GAAG,CAAC,aAAqB,EAAE,KAAyB;QAClD,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC;YAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QACnE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QACpC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;IAED,GAAG,CAAC,aAAqB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,aAAqB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACtC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAA","sourcesContent":["export interface BindingSourceEntry {\n bindingIndex: number\n file: string\n line: number\n column: number\n}\n\nexport interface CompilerCacheEntry {\n preSource: string\n postSource: string\n msgMaskMap: Record<string, number>\n bindingSources: BindingSourceEntry[]\n}\n\nconst MAX_ENTRIES = 50\n\nexport class CompilerCache {\n private readonly cache = new Map<string, CompilerCacheEntry>()\n\n set(componentName: string, entry: CompilerCacheEntry): void {\n if (this.cache.has(componentName)) this.cache.delete(componentName)\n this.cache.set(componentName, entry)\n if (this.cache.size > MAX_ENTRIES) {\n this.cache.delete(this.cache.keys().next().value!)\n }\n }\n\n get(componentName: string): CompilerCacheEntry | undefined {\n return this.cache.get(componentName)\n }\n\n has(componentName: string): boolean {\n return this.cache.has(componentName)\n }\n}\n\nexport const compilerCache = new CompilerCache()\n"]}
@@ -0,0 +1,109 @@
1
+ import ts from 'typescript';
2
+ import { type MessageAnnotations } from '@llui/compiler';
3
+ import { type MsgSchema } from './msg-schema.js';
4
+ /**
5
+ * Cross-file type resolver.
6
+ *
7
+ * The schema/annotation extractors (`extractMsgAnnotations`,
8
+ * `extractMsgSchema`, `extractStateSchema`, `extractEffectSchema`) only
9
+ * see the source string for the file currently being transformed. When
10
+ * a developer keeps the `Msg` (or `State` / `Effect`) union in a
11
+ * separate file and imports it where `component()` is called, those
12
+ * extractors silently return `null` — the plugin emits no annotations,
13
+ * runtime LAP validation is disabled, and Claude can dispatch arbitrary
14
+ * `type` strings that fall through to `assertNever`.
15
+ *
16
+ * This module follows imports and re-exports to find the source file
17
+ * that declares the requested type alias, returning that file's source
18
+ * string + the local name of the alias there. Extractors then run
19
+ * against that source and produce the same output they would have for
20
+ * a co-located declaration.
21
+ *
22
+ * Limitations:
23
+ * - Composition (`type Msg = ImportedA | { type: 'b' }`): only the
24
+ * locally-declared variants are extracted; the imported half isn't
25
+ * walked recursively into. The lint rule `agent-msg-resolvable`
26
+ * catches this case at lint time.
27
+ * - Namespace imports (`import * as ns from './msg'`) and `export *`:
28
+ * not followed. Same lint coverage.
29
+ * - Generic types: not parameterized resolution; the type argument
30
+ * must resolve to a concrete type alias.
31
+ */
32
+ export interface ResolveContext {
33
+ /**
34
+ * Resolve a module specifier (e.g. `'./msg'`, `'@scope/pkg'`) against
35
+ * the importing file's path. Returns the absolute filesystem path of
36
+ * the resolved module, or `null` if it cannot be resolved (the type
37
+ * stays unresolved and the extractor falls back to local-only mode).
38
+ */
39
+ resolveModule: (spec: string, importerPath: string) => Promise<string | null>;
40
+ /**
41
+ * Read the source contents of an absolute module path. The contents
42
+ * are parsed by TypeScript so they should be valid TS/TSX. The plugin
43
+ *'s vite hook plumbs `fs/promises.readFile` here; tests provide an
44
+ * in-memory map.
45
+ */
46
+ readSource: (absolutePath: string) => Promise<string>;
47
+ }
48
+ export interface ResolvedTypeSource {
49
+ /** The full source string of the file declaring the type alias. */
50
+ source: string;
51
+ /** The local name of the alias *in that file* (after rename chains). */
52
+ localName: string;
53
+ /** Absolute path of the file declaring the alias (debug aid). */
54
+ filePath: string;
55
+ }
56
+ /**
57
+ * Walk imports + re-exports to find where a type alias is actually
58
+ * declared. Returns the source string and local name of the alias in
59
+ * its declaring file. Returns `null` if the chain leads to an unresolved
60
+ * module, a re-export through `export *`, a namespace import, or a
61
+ * dead-end (alias not declared anywhere we can see).
62
+ */
63
+ export declare function findTypeSource(typeName: string, source: string, filePath: string, ctx: ResolveContext, visited?: Set<string>): Promise<ResolvedTypeSource | null>;
64
+ /**
65
+ * Annotation extractor that walks composed Msg unions across files.
66
+ *
67
+ * Given a Msg type that may be a union of inline `{ type: 'literal' }`
68
+ * objects AND TypeReferences (e.g.
69
+ * `type Msg = ImportedFoo | { type: 'extra' }`), recursively follow
70
+ * each TypeReference via `findTypeSource` and merge its variants into
71
+ * the returned map.
72
+ *
73
+ * Composition + cross-file is the union of two failure modes the
74
+ * file-local sync extractor silently mishandles. This function
75
+ * produces the same map the runtime expects regardless of how the
76
+ * developer organized the type declarations.
77
+ *
78
+ * Conflict policy: if two composed branches contribute the same
79
+ * discriminant string (e.g. both halves declare `{ type: 'inc' }`),
80
+ * the first one walked wins. The lint rule `agent-msg-resolvable`
81
+ * fires before this point on most pathological cases; ESLint's
82
+ * type-checker would flag the duplicate independently.
83
+ */
84
+ export declare function extractMsgAnnotationsCrossFile(source: string, typeName: string, filePath: string, ctx: ResolveContext): Promise<Record<string, MessageAnnotations> | null>;
85
+ /**
86
+ * Cross-file companion to `extractMsgSchema` / `extractEffectSchema`.
87
+ *
88
+ * Discriminated-union schema extractor that follows composed
89
+ * TypeReferences through the resolver. Same recursion shape as
90
+ * `extractMsgAnnotationsCrossFile`, just collecting field shapes
91
+ * instead of JSDoc annotations.
92
+ */
93
+ export declare function extractDiscriminatedUnionSchemaCrossFile(source: string, typeName: string, filePath: string, ctx: ResolveContext): Promise<MsgSchema | null>;
94
+ /**
95
+ * Inspect the type arguments of a `component<...>()` call and return
96
+ * the textual identifier for each known position. Returns `null` for
97
+ * positions whose type argument isn't a plain identifier (e.g.
98
+ * inline literal types, generic instantiations, namespace-qualified
99
+ * names). Identifiers are what the resolver can chase; everything else
100
+ * we leave to the local extractor's existing behavior.
101
+ *
102
+ * Order: `[State, Msg, Effect]` matching `component<State, Msg, Effect>`.
103
+ */
104
+ export declare function readComponentTypeArgNames(call: ts.CallExpression): {
105
+ state: string | null;
106
+ msg: string | null;
107
+ effect: string | null;
108
+ };
109
+ //# sourceMappingURL=cross-file-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-file-resolver.d.ts","sourceRoot":"","sources":["../src/cross-file-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,KAAK,kBAAkB,EAA4C,MAAM,gBAAgB,CAAA;AAClG,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,iBAAiB,CAAA;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC7E;;;;;OAKG;IACH,UAAU,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,GAAG,CAAC,MAAM,CAAa,GAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAwGpC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAC,CAKpD;AAoLD;;;;;;;GAOG;AACH,wBAAsB,wCAAwC,CAC5D,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAK3B;AA4ND;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,CASA"}
@@ -0,0 +1,530 @@
1
+ import ts from 'typescript';
2
+ import {} from '@llui/compiler';
3
+ import { buildFieldDescriptor, } from './msg-schema.js';
4
+ /**
5
+ * Walk imports + re-exports to find where a type alias is actually
6
+ * declared. Returns the source string and local name of the alias in
7
+ * its declaring file. Returns `null` if the chain leads to an unresolved
8
+ * module, a re-export through `export *`, a namespace import, or a
9
+ * dead-end (alias not declared anywhere we can see).
10
+ */
11
+ export async function findTypeSource(typeName, source, filePath, ctx, visited = new Set()) {
12
+ // Cycle prevention — re-export A → A is a tight loop that some
13
+ // pathological re-export chains can produce. Bail rather than
14
+ // infinitely recurse.
15
+ if (visited.has(`${filePath}::${typeName}`))
16
+ return null;
17
+ visited.add(`${filePath}::${typeName}`);
18
+ const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
19
+ // 1. Local declaration wins. `type X = ...` or `interface X { ... }`
20
+ // (extractors only support type aliases today, but check both so
21
+ // the resolver itself isn't a footgun for future extractors).
22
+ for (const stmt of sf.statements) {
23
+ if (ts.isTypeAliasDeclaration(stmt) && stmt.name.text === typeName) {
24
+ return { source, localName: typeName, filePath };
25
+ }
26
+ if (ts.isInterfaceDeclaration(stmt) && stmt.name.text === typeName) {
27
+ return { source, localName: typeName, filePath };
28
+ }
29
+ }
30
+ // 2. Re-export with name: `export { X } from './y'` or
31
+ // `export { X as Y } from './y'`. Walk to the source module.
32
+ for (const stmt of sf.statements) {
33
+ if (!ts.isExportDeclaration(stmt))
34
+ continue;
35
+ if (!stmt.exportClause || !ts.isNamedExports(stmt.exportClause))
36
+ continue;
37
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
38
+ continue;
39
+ for (const spec of stmt.exportClause.elements) {
40
+ const exportedName = spec.name.text;
41
+ if (exportedName !== typeName)
42
+ continue;
43
+ // The name in the source module is `propertyName` if present
44
+ // (e.g. `export { Msg as M } from './msg'` exports as M but the
45
+ // source module has it as Msg).
46
+ const sourceName = spec.propertyName?.text ?? spec.name.text;
47
+ const resolved = await ctx.resolveModule(stmt.moduleSpecifier.text, filePath);
48
+ if (!resolved)
49
+ return null;
50
+ const subSource = await ctx.readSource(resolved);
51
+ return findTypeSource(sourceName, subSource, resolved, ctx, visited);
52
+ }
53
+ }
54
+ // 3. Local re-binding: `export { X } from elsewhere` shorthand was
55
+ // handled above. A separate case is `import { X } from ... ; export
56
+ // { X }` — the import already declares X locally, so step 5 picks
57
+ // it up.
58
+ // 4. Star re-exports: `export * from './y'`. The barrel re-exports
59
+ // every named member of `./y` under the same name. Walk each
60
+ // barrel target and return the first hit. Order: textual order
61
+ // in the source file (matches TypeScript's behaviour for
62
+ // multi-barrel name collisions, where the first declared wins).
63
+ //
64
+ // Multiple `export *` declarations are common in monorepo barrel
65
+ // files (`export * from './msg'; export * from './effects'`).
66
+ // Without this step, the resolver returns `null` and the plugin
67
+ // silently emits empty annotations for any consumer that points
68
+ // at a barrel.
69
+ for (const stmt of sf.statements) {
70
+ if (!ts.isExportDeclaration(stmt))
71
+ continue;
72
+ // `export * from './y'` has no exportClause; `export {} from './y'`
73
+ // is a different beast (re-exports nothing). Skip the latter.
74
+ if (stmt.exportClause)
75
+ continue;
76
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
77
+ continue;
78
+ const resolved = await ctx.resolveModule(stmt.moduleSpecifier.text, filePath);
79
+ if (!resolved)
80
+ continue;
81
+ let subSource;
82
+ try {
83
+ subSource = await ctx.readSource(resolved);
84
+ }
85
+ catch {
86
+ // Module path resolved but the file isn't readable (deleted,
87
+ // dynamic-only, etc.). Continue to the next barrel.
88
+ continue;
89
+ }
90
+ const found = await findTypeSource(typeName, subSource, resolved, ctx, visited);
91
+ if (found)
92
+ return found;
93
+ }
94
+ // 5. Imports: `import { X } from './y'` or `import { X as Y } from './y'`.
95
+ // Walk to the source module using the original (imported) name.
96
+ for (const stmt of sf.statements) {
97
+ if (!ts.isImportDeclaration(stmt))
98
+ continue;
99
+ if (!stmt.importClause)
100
+ continue;
101
+ if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
102
+ continue;
103
+ const bindings = stmt.importClause.namedBindings;
104
+ if (!bindings || !ts.isNamedImports(bindings))
105
+ continue;
106
+ for (const elem of bindings.elements) {
107
+ const localName = elem.name.text;
108
+ if (localName !== typeName)
109
+ continue;
110
+ // The remote name is `propertyName` when there's a rename, else
111
+ // the local name itself.
112
+ const remoteName = elem.propertyName?.text ?? elem.name.text;
113
+ const resolved = await ctx.resolveModule(stmt.moduleSpecifier.text, filePath);
114
+ if (!resolved)
115
+ return null;
116
+ const subSource = await ctx.readSource(resolved);
117
+ return findTypeSource(remoteName, subSource, resolved, ctx, visited);
118
+ }
119
+ }
120
+ // Not found in this file and no import/re-export to follow.
121
+ return null;
122
+ }
123
+ /**
124
+ * Annotation extractor that walks composed Msg unions across files.
125
+ *
126
+ * Given a Msg type that may be a union of inline `{ type: 'literal' }`
127
+ * objects AND TypeReferences (e.g.
128
+ * `type Msg = ImportedFoo | { type: 'extra' }`), recursively follow
129
+ * each TypeReference via `findTypeSource` and merge its variants into
130
+ * the returned map.
131
+ *
132
+ * Composition + cross-file is the union of two failure modes the
133
+ * file-local sync extractor silently mishandles. This function
134
+ * produces the same map the runtime expects regardless of how the
135
+ * developer organized the type declarations.
136
+ *
137
+ * Conflict policy: if two composed branches contribute the same
138
+ * discriminant string (e.g. both halves declare `{ type: 'inc' }`),
139
+ * the first one walked wins. The lint rule `agent-msg-resolvable`
140
+ * fires before this point on most pathological cases; ESLint's
141
+ * type-checker would flag the duplicate independently.
142
+ */
143
+ export async function extractMsgAnnotationsCrossFile(source, typeName, filePath, ctx) {
144
+ const out = {};
145
+ const ok = await collectMsgVariants(typeName, source, filePath, ctx, out, new Set());
146
+ if (!ok)
147
+ return null;
148
+ return Object.keys(out).length === 0 ? null : out;
149
+ }
150
+ async function collectMsgVariants(typeName, source, filePath, ctx, out, visitedAliases) {
151
+ const located = await findTypeSource(typeName, source, filePath, ctx, new Set());
152
+ if (!located)
153
+ return false;
154
+ const aliasKey = `${located.filePath}::${located.localName}`;
155
+ if (visitedAliases.has(aliasKey))
156
+ return true;
157
+ visitedAliases.add(aliasKey);
158
+ const sf = ts.createSourceFile(located.filePath, located.source, ts.ScriptTarget.Latest, true);
159
+ const aliases = [];
160
+ sf.forEachChild((n) => {
161
+ if (ts.isTypeAliasDeclaration(n))
162
+ aliases.push(n);
163
+ });
164
+ const alias = aliases.find((a) => a.name.text === located.localName);
165
+ if (!alias)
166
+ return false;
167
+ // Single-variant alias: `type Foo = { type: 'a', ... }`. Treat as a
168
+ // one-element union so a Msg variant can be its own type alias.
169
+ const memberNodes = ts.isUnionTypeNode(alias.type)
170
+ ? [...alias.type.types]
171
+ : [alias.type];
172
+ for (let i = 0; i < memberNodes.length; i++) {
173
+ const member = memberNodes[i];
174
+ if (ts.isTypeLiteralNode(member)) {
175
+ const variant = readDiscriminantLiteral(member);
176
+ if (!variant)
177
+ continue;
178
+ const comment = readLeadingJSDocForMember(located.source, alias, memberNodes, i);
179
+ if (out[variant] === undefined) {
180
+ out[variant] = parseMessageAnnotations(comment);
181
+ }
182
+ continue;
183
+ }
184
+ if (ts.isTypeReferenceNode(member) && ts.isIdentifier(member.typeName)) {
185
+ // Composed: recurse through the resolver.
186
+ await collectMsgVariants(member.typeName.text, located.source, located.filePath, ctx, out, visitedAliases);
187
+ continue;
188
+ }
189
+ // Other shapes (intersections, conditional types, namespace-qualified
190
+ // names) aren't followed. Lint catches this.
191
+ }
192
+ return true;
193
+ }
194
+ function readDiscriminantLiteral(lit) {
195
+ for (const m of lit.members) {
196
+ if (!ts.isPropertySignature(m))
197
+ continue;
198
+ if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type')
199
+ continue;
200
+ if (!m.type || !ts.isLiteralTypeNode(m.type))
201
+ continue;
202
+ const literal = m.type.literal;
203
+ if (ts.isStringLiteral(literal))
204
+ return literal.text;
205
+ }
206
+ return null;
207
+ }
208
+ /**
209
+ * Read leading JSDoc for a union member at index `i` of `members`.
210
+ * The JSDoc lives between the previous element's end and the current
211
+ * element's start (or between the type alias start and the first
212
+ * element for `i === 0`). Mirrors the logic in
213
+ * `extractMsgAnnotations` so the cross-file path produces the same
214
+ * output for the same input.
215
+ */
216
+ function readLeadingJSDocForMember(source, alias, members, i) {
217
+ const prev = members[i - 1];
218
+ const member = members[i];
219
+ // For non-union (single-variant) aliases the union pos is the alias
220
+ // body's pos.
221
+ const unionPos = ts.isUnionTypeNode(alias.type) ? alias.type.pos : alias.type.pos;
222
+ const scanPos = i === 0 || prev === undefined ? unionPos : prev.end;
223
+ const ranges = ts.getLeadingCommentRanges(source, scanPos) ?? [];
224
+ const docs = ranges
225
+ .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)
226
+ .map((r) => source.slice(r.pos, r.end))
227
+ .filter((txt) => txt.startsWith('/**'));
228
+ // Cut off comments that appear AFTER the member starts (rare but
229
+ // possible with weird formatting).
230
+ const _end = member.pos;
231
+ return docs.join('\n');
232
+ }
233
+ function parseMessageAnnotations(comment) {
234
+ if (!comment)
235
+ return defaultMessageAnnotations();
236
+ const intent = readIntentTag(comment);
237
+ const human = /@humanOnly\b/.test(comment);
238
+ const agent = /@agentOnly\b/.test(comment);
239
+ const dispatchMode = human && !agent ? 'human-only' : agent && !human ? 'agent-only' : 'shared';
240
+ return {
241
+ intent,
242
+ alwaysAffordable: /@alwaysAffordable\b/.test(comment),
243
+ requiresConfirm: /@requiresConfirm\b/.test(comment),
244
+ dispatchMode,
245
+ examples: readExamplesTag(comment),
246
+ warning: readWarningTag(comment),
247
+ emits: readEmitsTag(comment),
248
+ routeGate: readRouteGateTag(comment),
249
+ };
250
+ }
251
+ function readRouteGateTag(comment) {
252
+ const match = comment.match(/@routeGated\s*\(\s*["“]([^"”]*)["”]\s*\)/);
253
+ return match?.[1] ?? null;
254
+ }
255
+ function readEmitsTag(comment) {
256
+ const outer = comment.match(/@emits\s*\(([^)]*)\)/);
257
+ if (!outer || outer[1] === undefined)
258
+ return [];
259
+ const inner = outer[1];
260
+ const seen = new Set();
261
+ const out = [];
262
+ const re = /["“]([^"”]*)["”]/g;
263
+ let m;
264
+ while ((m = re.exec(inner)) !== null) {
265
+ const v = m[1];
266
+ if (v === undefined || seen.has(v))
267
+ continue;
268
+ seen.add(v);
269
+ out.push(v);
270
+ }
271
+ return out;
272
+ }
273
+ function readExamplesTag(comment) {
274
+ const out = [];
275
+ const re = /@example\s*\(\s*["“]([^"”]*)["”]\s*\)/g;
276
+ let m;
277
+ while ((m = re.exec(comment)) !== null) {
278
+ if (m[1] !== undefined)
279
+ out.push(m[1]);
280
+ }
281
+ return out;
282
+ }
283
+ function readWarningTag(comment) {
284
+ const match = comment.match(/@warning\s*\(\s*["“]([^"”]*)["”]\s*\)/);
285
+ return match?.[1] ?? null;
286
+ }
287
+ function defaultMessageAnnotations() {
288
+ return {
289
+ intent: null,
290
+ alwaysAffordable: false,
291
+ requiresConfirm: false,
292
+ dispatchMode: 'shared',
293
+ examples: [],
294
+ warning: null,
295
+ emits: [],
296
+ routeGate: null,
297
+ };
298
+ }
299
+ function readIntentTag(comment) {
300
+ const match = comment.match(/@intent\s*\(\s*["“]([^"”]*)["”]\s*\)/);
301
+ return match?.[1] ?? null;
302
+ }
303
+ /**
304
+ * Cross-file companion to `extractMsgSchema` / `extractEffectSchema`.
305
+ *
306
+ * Discriminated-union schema extractor that follows composed
307
+ * TypeReferences through the resolver. Same recursion shape as
308
+ * `extractMsgAnnotationsCrossFile`, just collecting field shapes
309
+ * instead of JSDoc annotations.
310
+ */
311
+ export async function extractDiscriminatedUnionSchemaCrossFile(source, typeName, filePath, ctx) {
312
+ const variants = {};
313
+ const ok = await collectSchemaVariants(typeName, source, filePath, ctx, variants, new Set());
314
+ if (!ok)
315
+ return null;
316
+ return Object.keys(variants).length === 0 ? null : { discriminant: 'type', variants };
317
+ }
318
+ async function collectSchemaVariants(typeName, source, filePath, ctx, variants, visitedAliases) {
319
+ const located = await findTypeSource(typeName, source, filePath, ctx, new Set());
320
+ if (!located)
321
+ return false;
322
+ const aliasKey = `${located.filePath}::${located.localName}`;
323
+ if (visitedAliases.has(aliasKey))
324
+ return true;
325
+ visitedAliases.add(aliasKey);
326
+ const sf = ts.createSourceFile(located.filePath, located.source, ts.ScriptTarget.Latest, true);
327
+ const aliases = [];
328
+ sf.forEachChild((n) => {
329
+ if (ts.isTypeAliasDeclaration(n))
330
+ aliases.push(n);
331
+ });
332
+ const alias = aliases.find((a) => a.name.text === located.localName);
333
+ if (!alias)
334
+ return false;
335
+ const memberNodes = ts.isUnionTypeNode(alias.type)
336
+ ? [...alias.type.types]
337
+ : [alias.type];
338
+ // Build a typeIndex that combines this file's local types with any
339
+ // *imported* type aliases referenced inside the variant payloads.
340
+ // Without this enrichment, a field typed as `GridSorting` (declared
341
+ // in `./state.ts` and imported here) would resolve to `'unknown'`
342
+ // because the local index doesn't know about it. The synthesizer
343
+ // would then emit `null` and the agent would have to guess at the
344
+ // permissible literal-union values.
345
+ const typeIndex = await buildEnrichedTypeIndex(sf, located.source, located.filePath, ctx);
346
+ for (const member of memberNodes) {
347
+ if (ts.isTypeLiteralNode(member)) {
348
+ collectOneVariant(member, variants, located.source, typeIndex);
349
+ continue;
350
+ }
351
+ if (ts.isTypeReferenceNode(member) && ts.isIdentifier(member.typeName)) {
352
+ await collectSchemaVariants(member.typeName.text, located.source, located.filePath, ctx, variants, visitedAliases);
353
+ continue;
354
+ }
355
+ }
356
+ return true;
357
+ }
358
+ function collectOneVariant(lit, variants, source, typeIndex) {
359
+ let discriminantValue = null;
360
+ const fields = {};
361
+ for (const member of lit.members) {
362
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
363
+ continue;
364
+ const name = member.name.text;
365
+ const memberType = member.type;
366
+ if (name === 'type' && memberType) {
367
+ if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {
368
+ discriminantValue = memberType.literal.text;
369
+ }
370
+ continue;
371
+ }
372
+ fields[name] = buildFieldDescriptor(member, source, typeIndex);
373
+ }
374
+ if (discriminantValue && variants[discriminantValue] === undefined) {
375
+ variants[discriminantValue] = fields;
376
+ }
377
+ }
378
+ /**
379
+ * Build a TypeIndex that includes the locally-declared types in `sf`
380
+ * AND any types imported by name into `sf`. Following the imports
381
+ * picks up sibling-file aliases like `GridSorting`, `ScoreMode`,
382
+ * `ConfirmRequest` that an app commonly extracts to a state module.
383
+ *
384
+ * Limitations:
385
+ * - Only follows direct named imports (`import type { X } from './y'`).
386
+ * Namespace imports and `export *` aren't followed (the lint rule
387
+ * `agent-msg-resolvable` already catches the namespace case).
388
+ * - The resolved external type must itself be a type alias or
389
+ * interface in the target file — chained re-exports beyond the first
390
+ * hop fall back to `'unknown'`.
391
+ * - Best-effort: any failure to resolve an import is silent. The
392
+ * field type just stays `'unknown'` as it would have without
393
+ * enrichment.
394
+ */
395
+ async function buildEnrichedTypeIndex(sf, source, filePath, ctx) {
396
+ const index = new Map();
397
+ // 1. Locally-declared aliases / interfaces.
398
+ for (const stmt of sf.statements) {
399
+ if (ts.isTypeAliasDeclaration(stmt)) {
400
+ index.set(stmt.name.text, stmt.type);
401
+ }
402
+ else if (ts.isInterfaceDeclaration(stmt)) {
403
+ index.set(stmt.name.text, stmt);
404
+ }
405
+ }
406
+ // 2. Walk imports transitively. Each file's named imports are
407
+ // resolved, the target declarations are added to the index under
408
+ // their local name, and the target's OWN file is then queued so
409
+ // its imports are followed too. This is what makes
410
+ // `Matrix/AddCriteria.criteria[].type.ease` resolve all the way
411
+ // to its discriminated-union descriptor: `Criterion` is imported
412
+ // from `@decisive/domain`, and `EaseFunction` is in turn imported
413
+ // by Criterion's home file. Without transitivity the inner types
414
+ // collapse to `'unknown'` and the agent has to guess the shape.
415
+ //
416
+ // Type-only imports (`import type { X }`) are followed exactly
417
+ // the same as value imports — TypeScript's `isTypeOnly` flag
418
+ // doesn't change the referent.
419
+ //
420
+ // Name collisions are first-write-wins: a local declaration
421
+ // shadows an imported one of the same name, and the first
422
+ // transitively-discovered import wins over later same-name
423
+ // imports. Intentional — root files almost always import the
424
+ // canonical name, and shallower-import names are more likely
425
+ // correct than deep-import collisions.
426
+ const fileQueue = [
427
+ { source, filePath, sf },
428
+ ];
429
+ const visitedFiles = new Set([filePath]);
430
+ while (fileQueue.length > 0) {
431
+ const cur = fileQueue.shift();
432
+ if (!cur)
433
+ break;
434
+ // Add this file's *own* local type declarations to the index so
435
+ // sibling references inside the file's exported types resolve.
436
+ // Without this, a Criterion in domain.ts referencing EaseMode
437
+ // (declared right next to it) would collapse to 'unknown' even
438
+ // though we already followed the import chain to domain.ts.
439
+ // First-write-wins: a local declaration in the entry file
440
+ // shadows a same-named declaration in a transitively-walked
441
+ // file (intentional — entry-file names are canonical).
442
+ if (cur.filePath !== filePath) {
443
+ for (const stmt of cur.sf.statements) {
444
+ if (ts.isTypeAliasDeclaration(stmt)) {
445
+ if (!index.has(stmt.name.text))
446
+ index.set(stmt.name.text, stmt.type);
447
+ }
448
+ else if (ts.isInterfaceDeclaration(stmt)) {
449
+ if (!index.has(stmt.name.text))
450
+ index.set(stmt.name.text, stmt);
451
+ }
452
+ }
453
+ }
454
+ for (const stmt of cur.sf.statements) {
455
+ if (!ts.isImportDeclaration(stmt))
456
+ continue;
457
+ const named = stmt.importClause?.namedBindings;
458
+ if (!named || !ts.isNamedImports(named))
459
+ continue;
460
+ for (const spec of named.elements) {
461
+ const localName = spec.name.text;
462
+ const importedName = spec.propertyName?.text ?? localName;
463
+ if (index.has(localName))
464
+ continue;
465
+ // Best-effort: any failure to resolve / read silently bails.
466
+ // Bare-specifier imports like `'fs'` resolve to vite's
467
+ // `__vite-browser-external` sentinel, which then ENOENTs at
468
+ // readSource — those imports aren't type-relevant for schema
469
+ // extraction anyway, so the failure is benign.
470
+ let located;
471
+ try {
472
+ located = await findTypeSource(importedName, cur.source, cur.filePath, ctx, new Set());
473
+ }
474
+ catch {
475
+ located = null;
476
+ }
477
+ if (!located)
478
+ continue;
479
+ const targetSf = ts.createSourceFile(located.filePath, located.source, ts.ScriptTarget.Latest, true);
480
+ let added = false;
481
+ for (const targetStmt of targetSf.statements) {
482
+ if (ts.isTypeAliasDeclaration(targetStmt) && targetStmt.name.text === located.localName) {
483
+ index.set(localName, targetStmt.type);
484
+ added = true;
485
+ break;
486
+ }
487
+ if (ts.isInterfaceDeclaration(targetStmt) && targetStmt.name.text === located.localName) {
488
+ index.set(localName, targetStmt);
489
+ added = true;
490
+ break;
491
+ }
492
+ }
493
+ // Queue the target file so its own imports — and own local
494
+ // declarations — flow into the index. Only queue once per file.
495
+ if (added && !visitedFiles.has(located.filePath)) {
496
+ visitedFiles.add(located.filePath);
497
+ fileQueue.push({
498
+ source: located.source,
499
+ filePath: located.filePath,
500
+ sf: targetSf,
501
+ });
502
+ }
503
+ }
504
+ }
505
+ }
506
+ return index;
507
+ }
508
+ /**
509
+ * Inspect the type arguments of a `component<...>()` call and return
510
+ * the textual identifier for each known position. Returns `null` for
511
+ * positions whose type argument isn't a plain identifier (e.g.
512
+ * inline literal types, generic instantiations, namespace-qualified
513
+ * names). Identifiers are what the resolver can chase; everything else
514
+ * we leave to the local extractor's existing behavior.
515
+ *
516
+ * Order: `[State, Msg, Effect]` matching `component<State, Msg, Effect>`.
517
+ */
518
+ export function readComponentTypeArgNames(call) {
519
+ const args = call.typeArguments;
520
+ const get = (i) => {
521
+ const t = args?.[i];
522
+ if (!t)
523
+ return null;
524
+ if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName))
525
+ return t.typeName.text;
526
+ return null;
527
+ };
528
+ return { state: get(0), msg: get(1), effect: get(2) };
529
+ }
530
+ //# sourceMappingURL=cross-file-resolver.js.map