@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,80 @@
1
+ // `view-bag-import` — errors when a file that defines a component
2
+ // imports view-bag primitives (text, each, show, branch, memo,
3
+ // selector) directly from @llui/dom. The view bag (`view: ({ text,
4
+ // each, … }) => …`) is typed to the component's State; the direct
5
+ // import is generic. Migrated from
6
+ // `@llui/eslint-plugin/src/rules/view-bag-import.ts`.
7
+ import ts from 'typescript';
8
+ import { rangeFromOffsets } from '../diagnostic.js';
9
+ import { findComponentCalls } from './_shared.js';
10
+ const VIEW_BAG_NAMES = new Set(['text', 'each', 'show', 'branch', 'memo', 'selector']);
11
+ export function viewBagImportModule() {
12
+ return {
13
+ name: 'view-bag-import',
14
+ compilerVersion: '^0.3.0',
15
+ diagnostics: [
16
+ {
17
+ id: 'llui/view-bag-import',
18
+ description: 'View bag primitive imported from @llui/dom in a file that defines a component — use the view bag instead.',
19
+ },
20
+ ],
21
+ visitors: {
22
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
23
+ const visited = node;
24
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
25
+ const componentCalls = findComponentCalls(sf);
26
+ if (componentCalls.length === 0)
27
+ return;
28
+ // At least one component() must have a `view:` property for the
29
+ // rule to be meaningful (matches the ESLint rule's semantics).
30
+ let definesView = false;
31
+ for (const call of componentCalls) {
32
+ const arg = call.arguments[0];
33
+ if (!arg || !ts.isObjectLiteralExpression(arg))
34
+ continue;
35
+ for (const prop of arg.properties) {
36
+ if (ts.isPropertyAssignment(prop) &&
37
+ ts.isIdentifier(prop.name) &&
38
+ prop.name.text === 'view') {
39
+ definesView = true;
40
+ break;
41
+ }
42
+ }
43
+ if (definesView)
44
+ break;
45
+ }
46
+ if (!definesView)
47
+ return;
48
+ for (const stmt of sf.statements) {
49
+ if (!ts.isImportDeclaration(stmt))
50
+ continue;
51
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
52
+ continue;
53
+ if (stmt.moduleSpecifier.text !== '@llui/dom')
54
+ continue;
55
+ const clause = stmt.importClause;
56
+ if (!clause || !clause.namedBindings || !ts.isNamedImports(clause.namedBindings))
57
+ continue;
58
+ for (const spec of clause.namedBindings.elements) {
59
+ const importedName = (spec.propertyName ?? spec.name).text;
60
+ if (VIEW_BAG_NAMES.has(importedName)) {
61
+ ctx.reportDiagnostic({
62
+ id: 'llui/view-bag-import',
63
+ severity: 'error',
64
+ category: 'style',
65
+ message: `Don't import \`${importedName}\` from '@llui/dom' in a file that defines a ` +
66
+ `component. Use the view bag: \`view: ({ ${importedName}, … }) => [...]\`. ` +
67
+ `The view-bag version is typed to your component's State.`,
68
+ location: {
69
+ file: sf.fileName,
70
+ range: rangeFromOffsets(sf.text, spec.getStart(sf), spec.getEnd()),
71
+ },
72
+ });
73
+ }
74
+ }
75
+ }
76
+ },
77
+ },
78
+ };
79
+ }
80
+ //# sourceMappingURL=view-bag-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-bag-import.js","sourceRoot":"","sources":["../../src/modules/view-bag-import.ts"],"names":[],"mappings":"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","sourcesContent":["// `view-bag-import` — errors when a file that defines a component\n// imports view-bag primitives (text, each, show, branch, memo,\n// selector) directly from @llui/dom. The view bag (`view: ({ text,\n// each, … }) => …`) is typed to the component's State; the direct\n// import is generic. Migrated from\n// `@llui/eslint-plugin/src/rules/view-bag-import.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nconst VIEW_BAG_NAMES = new Set(['text', 'each', 'show', 'branch', 'memo', 'selector'])\n\nexport function viewBagImportModule(): CompilerModule {\n return {\n name: 'view-bag-import',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/view-bag-import',\n description:\n 'View bag primitive imported from @llui/dom in a file that defines a component — use the view bag instead.',\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n const visited = node as ts.SourceFile\n const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n const componentCalls = findComponentCalls(sf)\n if (componentCalls.length === 0) return\n // At least one component() must have a `view:` property for the\n // rule to be meaningful (matches the ESLint rule's semantics).\n let definesView = false\n for (const call of componentCalls) {\n const arg = call.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) continue\n for (const prop of arg.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'view'\n ) {\n definesView = true\n break\n }\n }\n if (definesView) break\n }\n if (!definesView) return\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt)) continue\n if (!ts.isStringLiteral(stmt.moduleSpecifier)) continue\n if (stmt.moduleSpecifier.text !== '@llui/dom') continue\n const clause = stmt.importClause\n if (!clause || !clause.namedBindings || !ts.isNamedImports(clause.namedBindings)) continue\n for (const spec of clause.namedBindings.elements) {\n const importedName = (spec.propertyName ?? spec.name).text\n if (VIEW_BAG_NAMES.has(importedName)) {\n ctx.reportDiagnostic({\n id: 'llui/view-bag-import',\n severity: 'error',\n category: 'style',\n message:\n `Don't import \\`${importedName}\\` from '@llui/dom' in a file that defines a ` +\n `component. Use the view bag: \\`view: ({ ${importedName}, … }) => [...]\\`. ` +\n `The view-bag version is typed to your component's State.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, spec.getStart(sf), spec.getEnd()),\n },\n })\n }\n }\n }\n },\n },\n }\n}\n"]}
@@ -0,0 +1,104 @@
1
+ import ts from 'typescript';
2
+ export type DispatchMode = 'shared' | 'human-only' | 'agent-only';
3
+ /**
4
+ * Whether the annotation map carries any non-default values. Used to
5
+ * gate `__msgAnnotations` emission — annotations whose every field is
6
+ * default are emission-redundant (the runtime treats absence as the
7
+ * same defaults). Saves ~50 bytes per component for un-annotated Msg
8
+ * unions, which dominates the corpus.
9
+ */
10
+ export declare function hasNonDefaultAnnotation(a: Record<string, MessageAnnotations>): boolean;
11
+ /**
12
+ * Build a TS object-literal expression for the annotation map. Used by
13
+ * `msgAnnotationsModule` for `__msgAnnotations` emission. Variant
14
+ * names are emitted as string literals (not identifiers) so
15
+ * discriminants containing `/`, `-`, reserved words, etc. produce
16
+ * valid JS.
17
+ */
18
+ export declare function annotationsToObjectLiteral(a: Record<string, MessageAnnotations>): ts.ObjectLiteralExpression;
19
+ export type MessageAnnotations = {
20
+ intent: string | null;
21
+ alwaysAffordable: boolean;
22
+ requiresConfirm: boolean;
23
+ dispatchMode: DispatchMode;
24
+ /**
25
+ * Concrete example dispatches the LLM can copy from. Populated by
26
+ * `@example("text")` JSDoc tags. Each tag becomes one entry, in
27
+ * source order, so authors can mix scenarios ("typical case",
28
+ * "edge case with auth", etc.) without nesting them in a single
29
+ * string.
30
+ */
31
+ examples: string[];
32
+ /**
33
+ * Non-blocking caution. Surfaced verbatim to the agent at affordance
34
+ * time so the LLM can weigh the consequence ("this overwrites the
35
+ * cloud version", "fires analytics that can't be retracted") before
36
+ * dispatching. Distinct from `requiresConfirm`, which is a runtime
37
+ * gate the user must acknowledge.
38
+ */
39
+ warning: string | null;
40
+ /**
41
+ * Effect kinds this variant emits when dispatched, declared by the
42
+ * author via `@emits("kind1", "kind2")`. Lets the agent reason
43
+ * about side effects ("this dispatch hits the cloud, so I should
44
+ * batch") without the compiler having to walk update.ts. Authored
45
+ * rather than auto-extracted because real apps emit effects
46
+ * through helpers (`track('foo')`, `saveDelta(d)`) — auto-detecting
47
+ * those would require helper-return-shape analysis with
48
+ * ergonomically-painful failure modes; the declarative form trades
49
+ * automatic discovery for accuracy and simplicity.
50
+ *
51
+ * Empty when no `@emits` tag is present.
52
+ */
53
+ emits: string[];
54
+ /**
55
+ * Boolean predicate gating whether the variant surfaces in
56
+ * `list_actions`. Authored as `@routeGated("expr")`; the compiler
57
+ * captures the predicate string verbatim and the runtime evaluates
58
+ * it with `state` bound to the current state. The variant only
59
+ * appears in the agent's affordance list when the predicate
60
+ * returns true.
61
+ *
62
+ * Compile-time alternative to `agentAffordances(state) => Msg[]`
63
+ * for the common case of "this Msg is reachable when state.X
64
+ * looks like Y." Co-located with the Msg definition rather than
65
+ * threaded through a separate hook.
66
+ *
67
+ * Examples:
68
+ * @routeGated("state.matrixState.kind === 'loaded'")
69
+ * @routeGated("state.route.kind === 'page' && state.route.slug === 'ranking'")
70
+ * @routeGated("state.auth.status === 'authenticated'")
71
+ *
72
+ * Null when no `@routeGated` tag is present (variant defaults to
73
+ * its dispatchMode-driven affordance behavior).
74
+ */
75
+ routeGate: string | null;
76
+ };
77
+ /**
78
+ * Walk a Msg-like discriminated-union type alias and extract JSDoc
79
+ * annotations attached to each union member. Returns null if no
80
+ * recognizable union is found so callers can skip emission cleanly.
81
+ *
82
+ * Expected JSDoc grammar (order-independent):
83
+ * @intent("human readable")
84
+ * @alwaysAffordable
85
+ * @requiresConfirm
86
+ * @humanOnly — sugar for dispatchMode: 'human-only'
87
+ * @agentOnly — sugar for dispatchMode: 'agent-only'
88
+ *
89
+ * Unknown tags are ignored; malformed @intent (no quoted string) is
90
+ * treated as "no intent". `@humanOnly` and `@agentOnly` are mutually
91
+ * exclusive — if both are present (which the ESLint rule
92
+ * `agent-exclusive-annotations` reports as an error), the parser
93
+ * falls back to `'shared'` so a misconfigured Msg variant doesn't
94
+ * silently lock out one audience.
95
+ */
96
+ export declare function extractMsgAnnotations(source: string,
97
+ /**
98
+ * Name of the type alias to extract from. Defaults to `'Msg'` for
99
+ * convention. Passed by the cross-file resolver when the alias has
100
+ * been renamed through imports/re-exports — its local name in the
101
+ * declaring file may differ from `'Msg'`.
102
+ */
103
+ typeName?: string): Record<string, MessageAnnotations> | null;
104
+ //# sourceMappingURL=msg-annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-annotations.d.ts","sourceRoot":"","sources":["../src/msg-annotations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,GAAG,YAAY,CAAA;AAEjE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,OAAO,CAStF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GACpC,EAAE,CAAC,uBAAuB,CAmD5B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,gBAAgB,EAAE,OAAO,CAAA;IACzB,eAAe,EAAE,OAAO,CAAA;IACxB,YAAY,EAAE,YAAY,CAAA;IAC1B;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB;;;;;;;;;;;;OAYG;IACH,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAaD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM;AACd;;;;;GAKG;AACH,QAAQ,GAAE,MAAc,GACvB,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAgC3C"}
@@ -0,0 +1,242 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * Whether the annotation map carries any non-default values. Used to
4
+ * gate `__msgAnnotations` emission — annotations whose every field is
5
+ * default are emission-redundant (the runtime treats absence as the
6
+ * same defaults). Saves ~50 bytes per component for un-annotated Msg
7
+ * unions, which dominates the corpus.
8
+ */
9
+ export function hasNonDefaultAnnotation(a) {
10
+ for (const v of Object.values(a)) {
11
+ if (v.intent !== null)
12
+ return true;
13
+ if (v.alwaysAffordable)
14
+ return true;
15
+ if (v.requiresConfirm)
16
+ return true;
17
+ if (v.dispatchMode !== 'shared')
18
+ return true;
19
+ if (v.routeGate != null)
20
+ return true;
21
+ }
22
+ return false;
23
+ }
24
+ /**
25
+ * Build a TS object-literal expression for the annotation map. Used by
26
+ * `msgAnnotationsModule` for `__msgAnnotations` emission. Variant
27
+ * names are emitted as string literals (not identifiers) so
28
+ * discriminants containing `/`, `-`, reserved words, etc. produce
29
+ * valid JS.
30
+ */
31
+ export function annotationsToObjectLiteral(a) {
32
+ const props = [];
33
+ for (const [variant, ann] of Object.entries(a)) {
34
+ props.push(ts.factory.createPropertyAssignment(
35
+ // Wrap with createStringLiteral — the printer treats bare strings
36
+ // as identifiers, which produces invalid JS for discriminants
37
+ // containing characters like '/' (e.g. 'Router/RouteChanged'),
38
+ // reserved words ('delete'), or hyphens.
39
+ ts.factory.createStringLiteral(variant), ts.factory.createObjectLiteralExpression([
40
+ ts.factory.createPropertyAssignment('intent', ann.intent === null
41
+ ? ts.factory.createNull()
42
+ : ts.factory.createStringLiteral(ann.intent)),
43
+ ts.factory.createPropertyAssignment('alwaysAffordable', ann.alwaysAffordable ? ts.factory.createTrue() : ts.factory.createFalse()),
44
+ ts.factory.createPropertyAssignment('requiresConfirm', ann.requiresConfirm ? ts.factory.createTrue() : ts.factory.createFalse()),
45
+ ts.factory.createPropertyAssignment('dispatchMode', ts.factory.createStringLiteral(ann.dispatchMode)),
46
+ // Only emit `routeGate` when non-null. The runtime treats
47
+ // missing as the no-gate default ("variant follows its
48
+ // dispatchMode-driven affordance behavior") — keeping the
49
+ // wire shape minimal for the common case of un-gated Msgs.
50
+ // Use loose `!= null` so test fixtures that omit the
51
+ // field entirely also fall through to the no-emit path.
52
+ ...(ann.routeGate != null
53
+ ? [
54
+ ts.factory.createPropertyAssignment('routeGate', ts.factory.createStringLiteral(ann.routeGate)),
55
+ ]
56
+ : []),
57
+ ], true)));
58
+ }
59
+ return ts.factory.createObjectLiteralExpression(props, true);
60
+ }
61
+ const DEFAULT = {
62
+ intent: null,
63
+ alwaysAffordable: false,
64
+ requiresConfirm: false,
65
+ dispatchMode: 'shared',
66
+ examples: [],
67
+ warning: null,
68
+ emits: [],
69
+ routeGate: null,
70
+ };
71
+ /**
72
+ * Walk a Msg-like discriminated-union type alias and extract JSDoc
73
+ * annotations attached to each union member. Returns null if no
74
+ * recognizable union is found so callers can skip emission cleanly.
75
+ *
76
+ * Expected JSDoc grammar (order-independent):
77
+ * @intent("human readable")
78
+ * @alwaysAffordable
79
+ * @requiresConfirm
80
+ * @humanOnly — sugar for dispatchMode: 'human-only'
81
+ * @agentOnly — sugar for dispatchMode: 'agent-only'
82
+ *
83
+ * Unknown tags are ignored; malformed @intent (no quoted string) is
84
+ * treated as "no intent". `@humanOnly` and `@agentOnly` are mutually
85
+ * exclusive — if both are present (which the ESLint rule
86
+ * `agent-exclusive-annotations` reports as an error), the parser
87
+ * falls back to `'shared'` so a misconfigured Msg variant doesn't
88
+ * silently lock out one audience.
89
+ */
90
+ export function extractMsgAnnotations(source,
91
+ /**
92
+ * Name of the type alias to extract from. Defaults to `'Msg'` for
93
+ * convention. Passed by the cross-file resolver when the alias has
94
+ * been renamed through imports/re-exports — its local name in the
95
+ * declaring file may differ from `'Msg'`.
96
+ */
97
+ typeName = 'Msg') {
98
+ const sf = ts.createSourceFile('msg.ts', source, ts.ScriptTarget.Latest, true);
99
+ const aliases = [];
100
+ sf.forEachChild((n) => {
101
+ if (ts.isTypeAliasDeclaration(n))
102
+ aliases.push(n);
103
+ });
104
+ const named = aliases.find((a) => a.name.text === typeName);
105
+ // Fallback: only when looking for the conventional 'Msg' name AND the
106
+ // file has no `type Msg = …`; pick any union type alias. With an
107
+ // explicit `typeName` from the resolver, we don't fall back — that
108
+ // would silently match the wrong alias.
109
+ const alias = named ?? (typeName === 'Msg' ? aliases.find((a) => ts.isUnionTypeNode(a.type)) : undefined);
110
+ if (!alias || !ts.isUnionTypeNode(alias.type))
111
+ return null;
112
+ const result = {};
113
+ const types = alias.type.types;
114
+ for (let i = 0; i < types.length; i++) {
115
+ const member = types[i];
116
+ if (member === undefined || !ts.isTypeLiteralNode(member))
117
+ continue;
118
+ const variant = readDiscriminantLiteral(member);
119
+ if (!variant)
120
+ continue;
121
+ // Leading JSDoc for union member i is scanned from the end of the
122
+ // previous element (or union.pos for the first member), because
123
+ // TypeScript's parser places comment ranges relative to the token
124
+ // that follows them — and the | bar is not part of the TypeLiteralNode.
125
+ const prev = types[i - 1];
126
+ const scanPos = i === 0 || prev === undefined ? alias.type.pos : prev.end;
127
+ const comment = readLeadingJSDoc(source, scanPos);
128
+ result[variant] = parseAnnotations(comment);
129
+ }
130
+ return Object.keys(result).length === 0 ? null : result;
131
+ }
132
+ function readDiscriminantLiteral(lit) {
133
+ for (const m of lit.members) {
134
+ if (!ts.isPropertySignature(m))
135
+ continue;
136
+ if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type')
137
+ continue;
138
+ if (!m.type || !ts.isLiteralTypeNode(m.type))
139
+ continue;
140
+ const literal = m.type.literal;
141
+ if (ts.isStringLiteral(literal))
142
+ return literal.text;
143
+ }
144
+ return null;
145
+ }
146
+ function readLeadingJSDoc(source, scanPos) {
147
+ const ranges = ts.getLeadingCommentRanges(source, scanPos) ?? [];
148
+ const docs = ranges
149
+ .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)
150
+ .map((r) => source.slice(r.pos, r.end))
151
+ .filter((txt) => txt.startsWith('/**'));
152
+ return docs.join('\n');
153
+ }
154
+ function parseAnnotations(comment) {
155
+ if (!comment)
156
+ return { ...DEFAULT, examples: [] };
157
+ const intent = readIntent(comment);
158
+ const human = /@humanOnly\b/.test(comment);
159
+ const agent = /@agentOnly\b/.test(comment);
160
+ // Mutual-exclusion fallback: both tags present means a config bug;
161
+ // the ESLint rule reports it. At parse time, default to 'shared' so
162
+ // we don't silently lock out one audience based on tag order.
163
+ const dispatchMode = human && !agent ? 'human-only' : agent && !human ? 'agent-only' : 'shared';
164
+ return {
165
+ intent,
166
+ alwaysAffordable: /@alwaysAffordable\b/.test(comment),
167
+ requiresConfirm: /@requiresConfirm\b/.test(comment),
168
+ dispatchMode,
169
+ examples: readExamples(comment),
170
+ warning: readWarning(comment),
171
+ emits: readEmits(comment),
172
+ routeGate: readRouteGate(comment),
173
+ };
174
+ }
175
+ /**
176
+ * Match `@routeGated("predicate-expression")` (and curly-quote
177
+ * variant). Returns the verbatim predicate string — the runtime
178
+ * compiles it with `new Function('state', 'return (' + src + ')')`
179
+ * and evaluates against the current state to gate affordances.
180
+ *
181
+ * Mirrors `@validates`'s grammar but with `state` as the bound
182
+ * variable instead of `v` (since the predicate sees the whole app
183
+ * state, not a single field value).
184
+ */
185
+ function readRouteGate(comment) {
186
+ const match = comment.match(/@routeGated\s*\(\s*["“]([^"”]*)["”]\s*\)/);
187
+ return match?.[1] ?? null;
188
+ }
189
+ /**
190
+ * Match `@emits("k1", "k2", ...)` — comma-separated list of effect
191
+ * kind strings. Each entry can use straight or curly quotes; the
192
+ * separator is `,` with arbitrary whitespace. Returns the kinds in
193
+ * source order (deduped). Empty when the tag is absent or has no
194
+ * quoted strings.
195
+ */
196
+ function readEmits(comment) {
197
+ // Match the whole `@emits(...)` parenthesized group so we can
198
+ // re-parse the inner content for individual quoted strings. The
199
+ // outer match is non-greedy on the closing paren to avoid eating
200
+ // through later JSDoc.
201
+ const outer = comment.match(/@emits\s*\(([^)]*)\)/);
202
+ if (!outer || outer[1] === undefined)
203
+ return [];
204
+ const inner = outer[1];
205
+ const seen = new Set();
206
+ const out = [];
207
+ const re = /["“]([^"”]*)["”]/g;
208
+ let m;
209
+ while ((m = re.exec(inner)) !== null) {
210
+ const v = m[1];
211
+ if (v === undefined || seen.has(v))
212
+ continue;
213
+ seen.add(v);
214
+ out.push(v);
215
+ }
216
+ return out;
217
+ }
218
+ function readIntent(comment) {
219
+ const match = comment.match(/@intent\s*\(\s*["\u201c]([^"\u201d]*)["\u201d]\s*\)/);
220
+ return match?.[1] ?? null;
221
+ }
222
+ /**
223
+ * Match every `@example("\u2026")` (and curly-quote variant) in source
224
+ * order. Multiple tags on one variant are common \u2014 typical-case,
225
+ * edge-case-with-auth, etc. \u2014 so the parser collects all of them
226
+ * rather than picking the first.
227
+ */
228
+ function readExamples(comment) {
229
+ const out = [];
230
+ const re = /@example\s*\(\s*["\u201c]([^"\u201d]*)["\u201d]\s*\)/g;
231
+ let m;
232
+ while ((m = re.exec(comment)) !== null) {
233
+ if (m[1] !== undefined)
234
+ out.push(m[1]);
235
+ }
236
+ return out;
237
+ }
238
+ function readWarning(comment) {
239
+ const match = comment.match(/@warning\s*\(\s*["\u201c]([^"\u201d]*)["\u201d]\s*\)/);
240
+ return match?.[1] ?? null;
241
+ }
242
+ //# sourceMappingURL=msg-annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-annotations.js","sourceRoot":"","sources":["../src/msg-annotations.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\nexport type DispatchMode = 'shared' | 'human-only' | 'agent-only'\n\n/**\n * Whether the annotation map carries any non-default values. Used to\n * gate `__msgAnnotations` emission — annotations whose every field is\n * default are emission-redundant (the runtime treats absence as the\n * same defaults). Saves ~50 bytes per component for un-annotated Msg\n * unions, which dominates the corpus.\n */\nexport function hasNonDefaultAnnotation(a: Record<string, MessageAnnotations>): boolean {\n for (const v of Object.values(a)) {\n if (v.intent !== null) return true\n if (v.alwaysAffordable) return true\n if (v.requiresConfirm) return true\n if (v.dispatchMode !== 'shared') return true\n if (v.routeGate != null) return true\n }\n return false\n}\n\n/**\n * Build a TS object-literal expression for the annotation map. Used by\n * `msgAnnotationsModule` for `__msgAnnotations` emission. Variant\n * names are emitted as string literals (not identifiers) so\n * discriminants containing `/`, `-`, reserved words, etc. produce\n * valid JS.\n */\nexport function annotationsToObjectLiteral(\n a: Record<string, MessageAnnotations>,\n): ts.ObjectLiteralExpression {\n const props: ts.PropertyAssignment[] = []\n for (const [variant, ann] of Object.entries(a)) {\n props.push(\n ts.factory.createPropertyAssignment(\n // Wrap with createStringLiteral — the printer treats bare strings\n // as identifiers, which produces invalid JS for discriminants\n // containing characters like '/' (e.g. 'Router/RouteChanged'),\n // reserved words ('delete'), or hyphens.\n ts.factory.createStringLiteral(variant),\n ts.factory.createObjectLiteralExpression(\n [\n ts.factory.createPropertyAssignment(\n 'intent',\n ann.intent === null\n ? ts.factory.createNull()\n : ts.factory.createStringLiteral(ann.intent),\n ),\n ts.factory.createPropertyAssignment(\n 'alwaysAffordable',\n ann.alwaysAffordable ? ts.factory.createTrue() : ts.factory.createFalse(),\n ),\n ts.factory.createPropertyAssignment(\n 'requiresConfirm',\n ann.requiresConfirm ? ts.factory.createTrue() : ts.factory.createFalse(),\n ),\n ts.factory.createPropertyAssignment(\n 'dispatchMode',\n ts.factory.createStringLiteral(ann.dispatchMode),\n ),\n // Only emit `routeGate` when non-null. The runtime treats\n // missing as the no-gate default (\"variant follows its\n // dispatchMode-driven affordance behavior\") — keeping the\n // wire shape minimal for the common case of un-gated Msgs.\n // Use loose `!= null` so test fixtures that omit the\n // field entirely also fall through to the no-emit path.\n ...(ann.routeGate != null\n ? [\n ts.factory.createPropertyAssignment(\n 'routeGate',\n ts.factory.createStringLiteral(ann.routeGate),\n ),\n ]\n : []),\n ],\n true,\n ),\n ),\n )\n }\n return ts.factory.createObjectLiteralExpression(props, true)\n}\n\nexport type MessageAnnotations = {\n intent: string | null\n alwaysAffordable: boolean\n requiresConfirm: boolean\n dispatchMode: DispatchMode\n /**\n * Concrete example dispatches the LLM can copy from. Populated by\n * `@example(\"text\")` JSDoc tags. Each tag becomes one entry, in\n * source order, so authors can mix scenarios (\"typical case\",\n * \"edge case with auth\", etc.) without nesting them in a single\n * string.\n */\n examples: string[]\n /**\n * Non-blocking caution. Surfaced verbatim to the agent at affordance\n * time so the LLM can weigh the consequence (\"this overwrites the\n * cloud version\", \"fires analytics that can't be retracted\") before\n * dispatching. Distinct from `requiresConfirm`, which is a runtime\n * gate the user must acknowledge.\n */\n warning: string | null\n /**\n * Effect kinds this variant emits when dispatched, declared by the\n * author via `@emits(\"kind1\", \"kind2\")`. Lets the agent reason\n * about side effects (\"this dispatch hits the cloud, so I should\n * batch\") without the compiler having to walk update.ts. Authored\n * rather than auto-extracted because real apps emit effects\n * through helpers (`track('foo')`, `saveDelta(d)`) — auto-detecting\n * those would require helper-return-shape analysis with\n * ergonomically-painful failure modes; the declarative form trades\n * automatic discovery for accuracy and simplicity.\n *\n * Empty when no `@emits` tag is present.\n */\n emits: string[]\n /**\n * Boolean predicate gating whether the variant surfaces in\n * `list_actions`. Authored as `@routeGated(\"expr\")`; the compiler\n * captures the predicate string verbatim and the runtime evaluates\n * it with `state` bound to the current state. The variant only\n * appears in the agent's affordance list when the predicate\n * returns true.\n *\n * Compile-time alternative to `agentAffordances(state) => Msg[]`\n * for the common case of \"this Msg is reachable when state.X\n * looks like Y.\" Co-located with the Msg definition rather than\n * threaded through a separate hook.\n *\n * Examples:\n * @routeGated(\"state.matrixState.kind === 'loaded'\")\n * @routeGated(\"state.route.kind === 'page' && state.route.slug === 'ranking'\")\n * @routeGated(\"state.auth.status === 'authenticated'\")\n *\n * Null when no `@routeGated` tag is present (variant defaults to\n * its dispatchMode-driven affordance behavior).\n */\n routeGate: string | null\n}\n\nconst DEFAULT: MessageAnnotations = {\n intent: null,\n alwaysAffordable: false,\n requiresConfirm: false,\n dispatchMode: 'shared',\n examples: [],\n warning: null,\n emits: [],\n routeGate: null,\n}\n\n/**\n * Walk a Msg-like discriminated-union type alias and extract JSDoc\n * annotations attached to each union member. Returns null if no\n * recognizable union is found so callers can skip emission cleanly.\n *\n * Expected JSDoc grammar (order-independent):\n * @intent(\"human readable\")\n * @alwaysAffordable\n * @requiresConfirm\n * @humanOnly — sugar for dispatchMode: 'human-only'\n * @agentOnly — sugar for dispatchMode: 'agent-only'\n *\n * Unknown tags are ignored; malformed @intent (no quoted string) is\n * treated as \"no intent\". `@humanOnly` and `@agentOnly` are mutually\n * exclusive — if both are present (which the ESLint rule\n * `agent-exclusive-annotations` reports as an error), the parser\n * falls back to `'shared'` so a misconfigured Msg variant doesn't\n * silently lock out one audience.\n */\nexport function extractMsgAnnotations(\n source: string,\n /**\n * Name of the type alias to extract from. Defaults to `'Msg'` for\n * convention. Passed by the cross-file resolver when the alias has\n * been renamed through imports/re-exports — its local name in the\n * declaring file may differ from `'Msg'`.\n */\n typeName: string = 'Msg',\n): Record<string, MessageAnnotations> | null {\n const sf = ts.createSourceFile('msg.ts', source, ts.ScriptTarget.Latest, true)\n const aliases: ts.TypeAliasDeclaration[] = []\n sf.forEachChild((n) => {\n if (ts.isTypeAliasDeclaration(n)) aliases.push(n)\n })\n const named = aliases.find((a) => a.name.text === typeName)\n // Fallback: only when looking for the conventional 'Msg' name AND the\n // file has no `type Msg = …`; pick any union type alias. With an\n // explicit `typeName` from the resolver, we don't fall back — that\n // would silently match the wrong alias.\n const alias =\n named ?? (typeName === 'Msg' ? aliases.find((a) => ts.isUnionTypeNode(a.type)) : undefined)\n if (!alias || !ts.isUnionTypeNode(alias.type)) return null\n\n const result: Record<string, MessageAnnotations> = {}\n const types = alias.type.types\n for (let i = 0; i < types.length; i++) {\n const member = types[i]\n if (member === undefined || !ts.isTypeLiteralNode(member)) continue\n const variant = readDiscriminantLiteral(member)\n if (!variant) continue\n // Leading JSDoc for union member i is scanned from the end of the\n // previous element (or union.pos for the first member), because\n // TypeScript's parser places comment ranges relative to the token\n // that follows them — and the | bar is not part of the TypeLiteralNode.\n const prev = types[i - 1]\n const scanPos = i === 0 || prev === undefined ? alias.type.pos : prev.end\n const comment = readLeadingJSDoc(source, scanPos)\n result[variant] = parseAnnotations(comment)\n }\n return Object.keys(result).length === 0 ? null : result\n}\n\nfunction readDiscriminantLiteral(lit: ts.TypeLiteralNode): string | null {\n for (const m of lit.members) {\n if (!ts.isPropertySignature(m)) continue\n if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type') continue\n if (!m.type || !ts.isLiteralTypeNode(m.type)) continue\n const literal = m.type.literal\n if (ts.isStringLiteral(literal)) return literal.text\n }\n return null\n}\n\nfunction readLeadingJSDoc(source: string, scanPos: number): string {\n const ranges = ts.getLeadingCommentRanges(source, scanPos) ?? []\n const docs = ranges\n .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)\n .map((r) => source.slice(r.pos, r.end))\n .filter((txt) => txt.startsWith('/**'))\n return docs.join('\\n')\n}\n\nfunction parseAnnotations(comment: string): MessageAnnotations {\n if (!comment) return { ...DEFAULT, examples: [] }\n const intent = readIntent(comment)\n const human = /@humanOnly\\b/.test(comment)\n const agent = /@agentOnly\\b/.test(comment)\n // Mutual-exclusion fallback: both tags present means a config bug;\n // the ESLint rule reports it. At parse time, default to 'shared' so\n // we don't silently lock out one audience based on tag order.\n const dispatchMode: DispatchMode =\n human && !agent ? 'human-only' : agent && !human ? 'agent-only' : 'shared'\n return {\n intent,\n alwaysAffordable: /@alwaysAffordable\\b/.test(comment),\n requiresConfirm: /@requiresConfirm\\b/.test(comment),\n dispatchMode,\n examples: readExamples(comment),\n warning: readWarning(comment),\n emits: readEmits(comment),\n routeGate: readRouteGate(comment),\n }\n}\n\n/**\n * Match `@routeGated(\"predicate-expression\")` (and curly-quote\n * variant). Returns the verbatim predicate string — the runtime\n * compiles it with `new Function('state', 'return (' + src + ')')`\n * and evaluates against the current state to gate affordances.\n *\n * Mirrors `@validates`'s grammar but with `state` as the bound\n * variable instead of `v` (since the predicate sees the whole app\n * state, not a single field value).\n */\nfunction readRouteGate(comment: string): string | null {\n const match = comment.match(/@routeGated\\s*\\(\\s*[\"“]([^\"”]*)[\"”]\\s*\\)/)\n return match?.[1] ?? null\n}\n\n/**\n * Match `@emits(\"k1\", \"k2\", ...)` — comma-separated list of effect\n * kind strings. Each entry can use straight or curly quotes; the\n * separator is `,` with arbitrary whitespace. Returns the kinds in\n * source order (deduped). Empty when the tag is absent or has no\n * quoted strings.\n */\nfunction readEmits(comment: string): string[] {\n // Match the whole `@emits(...)` parenthesized group so we can\n // re-parse the inner content for individual quoted strings. The\n // outer match is non-greedy on the closing paren to avoid eating\n // through later JSDoc.\n const outer = comment.match(/@emits\\s*\\(([^)]*)\\)/)\n if (!outer || outer[1] === undefined) return []\n const inner = outer[1]\n const seen = new Set<string>()\n const out: string[] = []\n const re = /[\"“]([^\"”]*)[\"”]/g\n let m: RegExpExecArray | null\n while ((m = re.exec(inner)) !== null) {\n const v = m[1]\n if (v === undefined || seen.has(v)) continue\n seen.add(v)\n out.push(v)\n }\n return out\n}\n\nfunction readIntent(comment: string): string | null {\n const match = comment.match(/@intent\\s*\\(\\s*[\"\\u201c]([^\"\\u201d]*)[\"\\u201d]\\s*\\)/)\n return match?.[1] ?? null\n}\n\n/**\n * Match every `@example(\"\\u2026\")` (and curly-quote variant) in source\n * order. Multiple tags on one variant are common \\u2014 typical-case,\n * edge-case-with-auth, etc. \\u2014 so the parser collects all of them\n * rather than picking the first.\n */\nfunction readExamples(comment: string): string[] {\n const out: string[] = []\n const re = /@example\\s*\\(\\s*[\"\\u201c]([^\"\\u201d]*)[\"\\u201d]\\s*\\)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(comment)) !== null) {\n if (m[1] !== undefined) out.push(m[1])\n }\n return out\n}\n\nfunction readWarning(comment: string): string | null {\n const match = comment.match(/@warning\\s*\\(\\s*[\"\\u201c]([^\"\\u201d]*)[\"\\u201d]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
@@ -0,0 +1,130 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * The "bare type" of a field. Covers five cases:
4
+ * - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`
5
+ * - literal union: `{enum: ['a', 'b']}` for strings, `{enum: [1, 2, 3]}`
6
+ * for numbers, `{enum: [true]}` for booleans. Mixed-type literal
7
+ * unions stay `'unknown'`.
8
+ * - nested object shape: `{kind: 'object', shape: {...}}` — emitted when
9
+ * a field's type is a local interface/type alias the extractor could
10
+ * follow (depth-limited; cross-file references stay `'unknown'`).
11
+ * - array of element type: `{kind: 'array', element: <bare type>}`.
12
+ * - discriminated union of objects: `{kind: 'discriminated-union',
13
+ * discriminant: 'kind', variants: {a: {...}, b: {...}}}`. Emitted
14
+ * when every member of a union is an object literal sharing one
15
+ * literal-string property name with distinct values. Symmetric with
16
+ * how the top-level Msg union itself is encoded — same shape,
17
+ * recursed.
18
+ *
19
+ * The synthesizer in `@llui/agent`'s `list_actions` walks these to build
20
+ * copy-paste-ready payload examples; the validator in `send_message`
21
+ * walks them too (treating object/array as "any" since deep validation
22
+ * is the reducer's job).
23
+ */
24
+ export type MsgFieldType = string | {
25
+ enum: ReadonlyArray<string | number | boolean>;
26
+ } | {
27
+ kind: 'object';
28
+ shape: Record<string, MsgField>;
29
+ } | {
30
+ kind: 'array';
31
+ element: MsgFieldType;
32
+ } | {
33
+ kind: 'discriminated-union';
34
+ discriminant: string;
35
+ variants: Record<string, Record<string, MsgField>>;
36
+ };
37
+ /**
38
+ * Rich per-field descriptor. Emitted only when there's something
39
+ * beyond the bare type to communicate — optionality, an explicit
40
+ * priority hint, a freeform agent hint, or a runtime validation
41
+ * predicate. When everything but `type` is unset, the producer emits
42
+ * the bare `MsgFieldType` instead so variants without annotations
43
+ * stay byte-cheap in the bundle.
44
+ */
45
+ export interface MsgFieldRich {
46
+ type: MsgFieldType;
47
+ /** Mirrors TypeScript's `?:` optional marker. Required fields omit this. */
48
+ optional?: boolean;
49
+ /**
50
+ * Strength signal for optional fields. Borrows RFC 2119's `SHOULD`:
51
+ * the LLM ought to fill it in unless it has a specific reason not
52
+ * to. Required fields don't carry a priority — TS already conveys
53
+ * "must" via the type system. Currently the only level; future
54
+ * extensions could add `'recommended'` or similar.
55
+ */
56
+ priority?: 'should';
57
+ /** Freeform consequence-shaped explanation. Surfaced verbatim to
58
+ * the LLM at affordance time. */
59
+ hint?: string;
60
+ /**
61
+ * Boolean JS expression that must hold for the field's value to be
62
+ * accepted. The expression has `v` bound to the field's runtime
63
+ * value; everything else is global (Math, JSON, RegExp, etc.).
64
+ * Authored as `@validates("expr")` JSDoc — the compiler captures
65
+ * the source string verbatim and the validator compiles it lazily
66
+ * with `new Function`, caching across calls.
67
+ *
68
+ * Examples:
69
+ * @validates("v >= 0 && v <= 100") // weight 0–100
70
+ * @validates("v.length > 0") // non-empty string
71
+ * @validates("/^[a-z0-9-]+$/.test(v)") // slug format
72
+ *
73
+ * The predicate runs ONLY at the agent boundary. Human-driven
74
+ * dispatches bypass it because TypeScript already validated the
75
+ * call site. Use for invariants the type system can't express
76
+ * (numeric ranges, format predicates, length bounds).
77
+ */
78
+ validates?: string;
79
+ }
80
+ export type MsgField = MsgFieldType | MsgFieldRich;
81
+ export interface MsgSchema {
82
+ discriminant: string;
83
+ variants: Record<string, Record<string, MsgField>>;
84
+ }
85
+ /** True when `f` is a rich descriptor (object with `type` key). */
86
+ export declare function isRichField(f: MsgField): f is MsgFieldRich;
87
+ /**
88
+ * Build a TS expression for a single field descriptor in a MsgSchema's
89
+ * variant map. Used by `msgSchemaToLiteral` (this file) for the
90
+ * `__msgSchema` / `__effectSchema` emissions. Migrated from inline
91
+ * `buildFieldDescriptorExpr` in transform.ts (v2c/decomp-5).
92
+ */
93
+ export declare function buildFieldDescriptorExpr(descriptor: MsgField, f: ts.NodeFactory): ts.Expression;
94
+ /**
95
+ * Build the full `{ discriminant, variants }` object literal for a
96
+ * MsgSchema. Symmetric for `__msgSchema` and `__effectSchema` emission
97
+ * (both use the discriminated-union shape).
98
+ */
99
+ export declare function msgSchemaToLiteral(schema: MsgSchema, f: ts.NodeFactory): ts.ObjectLiteralExpression;
100
+ /** Extracts the bare type from either descriptor form. */
101
+ export declare function fieldType(f: MsgField): MsgFieldType;
102
+ export declare function extractMsgSchema(source: string, typeName?: string): MsgSchema | null;
103
+ export declare function extractEffectSchema(source: string, typeName?: string): MsgSchema | null;
104
+ /**
105
+ * Index of type aliases and interfaces visible from a source file,
106
+ * keyed by name. Lets the field-type resolver follow `Criterion[]` →
107
+ * `interface Criterion { … }` and emit a nested object shape rather
108
+ * than `'unknown'`.
109
+ *
110
+ * The cross-file resolver pipeline (`cross-file-resolver.ts`) builds
111
+ * an enriched index that includes types imported from sibling files —
112
+ * follow `GridSorting` → `'rank' | 'crit-X' | 'crit-Y'` → `{enum: […]}`
113
+ * even when the alias lives in `./state.ts` not the Msg-defining file.
114
+ */
115
+ export type TypeIndex = Map<string, ts.TypeNode | ts.InterfaceDeclaration>;
116
+ /**
117
+ * Build a single field descriptor from a property signature: type,
118
+ * optionality, and any `@should("…")` JSDoc hint. Emits the compact
119
+ * bare form when there's nothing extra to communicate; otherwise the
120
+ * rich `{type, optional?, priority?, hint?}` shape.
121
+ *
122
+ * Exported so the cross-file resolver (which walks the same property
123
+ * signatures when the Msg type lives in a different file from the
124
+ * `component()` call) can produce identical descriptors. Without
125
+ * sharing this helper, JSDoc hints would silently disappear whenever
126
+ * a Msg union got resolved across module boundaries.
127
+ */
128
+ export declare function buildFieldDescriptor(member: ts.PropertySignature, source: string, typeIndex?: TypeIndex): MsgField;
129
+ export declare function resolveFieldType(type: ts.TypeNode, typeIndex?: TypeIndex, depth?: number): MsgFieldType;
130
+ //# sourceMappingURL=msg-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-schema.d.ts","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN;IAAE,IAAI,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACxC;IACE,IAAI,EAAE,qBAAqB,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;CACnD,CAAA;AAEL;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB;sCACkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;;;;;;OAiBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,YAAY,CAAA;AAElD,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;CACnD;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,IAAI,YAAY,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,UAAU,CAqE/F;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,CAAC,EAAE,EAAE,CAAC,WAAW,GAChB,EAAE,CAAC,uBAAuB,CA0B5B;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,GAAG,YAAY,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAc,GAAG,SAAS,GAAG,IAAI,CAE3F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAiB,GAAG,SAAS,GAAG,IAAI,CAEjG;AAqBD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC,oBAAoB,CAAC,CAAA;AAsD1E;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,EAAE,CAAC,iBAAiB,EAC5B,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,SAAqB,GAC/B,QAAQ,CAwBV;AAmVD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,EAAE,CAAC,QAAQ,EACjB,SAAS,GAAE,SAAqB,EAChC,KAAK,SAAkB,GACtB,YAAY,CA8Hd"}