@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":"_shared.js","sourceRoot":"","sources":["../../src/modules/_shared.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,yDAAyD;AAEzD,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAiB;IAClD,MAAM,GAAG,GAAwB,EAAE,CAAA;IACnC,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;QAChC,IACE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACtB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;YAC7B,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW,EACjC,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACb,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAC1B,CAAC,CAAA;IACD,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["// Internal helpers shared across modules. Not part of the public\n// CompilerModule surface — strictly implementation glue.\n\nimport ts from 'typescript'\n\n/**\n * Walk a SourceFile and collect every `component(...)` CallExpression.\n * Used by per-target emission modules to capture their targets in `emit`\n * (not `visit`) so the refs match the post-Phase-2b AST.\n *\n * Phase 2b's `transformCall*` hooks rebuild ancestor nodes via\n * `ts.visitEachChild` whenever any descendant is rewritten — e.g. a\n * `text()` rewrite inside a `component()` call's view ladder\n * invalidates every component-call ref captured during the visitor\n * walk (Phase 2). Calling `findComponentCalls(analysis.sourceFile)` in\n * `emit` returns refs into the post-Phase-2b tree, which is the same\n * tree the umbrella's per-statement visitor walks.\n */\nexport function findComponentCalls(sf: ts.SourceFile): ts.CallExpression[] {\n const out: ts.CallExpression[] = []\n const walk = (n: ts.Node): void => {\n if (\n ts.isCallExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === 'component'\n ) {\n out.push(n)\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n return out\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function accessibilityModule(): CompilerModule;
3
+ //# sourceMappingURL=accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/modules/accessibility.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAgBlD,wBAAgB,mBAAmB,IAAI,cAAc,CA6DpD"}
@@ -0,0 +1,82 @@
1
+ // `accessibility` — two a11y nudges:
2
+ // - `<img>` without `alt` → screen readers read filename
3
+ // - `onClick` on a non-interactive element without `role` → keyboard
4
+ // users can't reach it
5
+ // Migrated from `@llui/eslint-plugin/src/rules/accessibility.ts`.
6
+ import ts from 'typescript';
7
+ import { rangeFromOffsets } from '../diagnostic.js';
8
+ import { ELEMENT_HELPERS, INTERACTIVE_ELEMENTS } from './_element-helpers.js';
9
+ function staticPropKeys(obj) {
10
+ const out = new Set();
11
+ for (const p of obj.properties) {
12
+ if (ts.isPropertyAssignment(p)) {
13
+ if (ts.isIdentifier(p.name))
14
+ out.add(p.name.text);
15
+ else if (ts.isStringLiteral(p.name))
16
+ out.add(p.name.text);
17
+ }
18
+ else if (ts.isShorthandPropertyAssignment(p)) {
19
+ out.add(p.name.text);
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+ export function accessibilityModule() {
25
+ return {
26
+ name: 'accessibility',
27
+ compilerVersion: '^0.3.0',
28
+ diagnostics: [
29
+ {
30
+ id: 'llui/accessibility',
31
+ description: 'Missing alt on <img>, or onClick on non-interactive element without role.',
32
+ },
33
+ ],
34
+ visitors: {
35
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
36
+ const visited = node;
37
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
38
+ const walk = (n) => {
39
+ if (ts.isCallExpression(n) &&
40
+ ts.isIdentifier(n.expression) &&
41
+ ELEMENT_HELPERS.has(n.expression.text)) {
42
+ const tag = n.expression.text;
43
+ const props = n.arguments[0];
44
+ if (props && ts.isObjectLiteralExpression(props)) {
45
+ const keys = staticPropKeys(props);
46
+ if (tag === 'img' && !keys.has('alt')) {
47
+ ctx.reportDiagnostic({
48
+ id: 'llui/accessibility',
49
+ severity: 'error',
50
+ category: 'style',
51
+ message: `<img> has no \`alt\` attribute. Add alt text for screen readers, or ` +
52
+ `\`alt: ''\` for decorative images.`,
53
+ location: {
54
+ file: sf.fileName,
55
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
56
+ },
57
+ });
58
+ }
59
+ if (keys.has('onClick') && !INTERACTIVE_ELEMENTS.has(tag) && !keys.has('role')) {
60
+ ctx.reportDiagnostic({
61
+ id: 'llui/accessibility',
62
+ severity: 'error',
63
+ category: 'style',
64
+ message: `onClick on <${tag}> without \`role\`. Non-interactive elements with click ` +
65
+ `handlers are not keyboard-accessible. Add \`role: 'button', tabIndex: 0\`, ` +
66
+ `or use <button>.`,
67
+ location: {
68
+ file: sf.fileName,
69
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
70
+ },
71
+ });
72
+ }
73
+ }
74
+ }
75
+ ts.forEachChild(n, walk);
76
+ };
77
+ walk(sf);
78
+ },
79
+ },
80
+ };
81
+ }
82
+ //# sourceMappingURL=accessibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/modules/accessibility.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,2DAA2D;AAC3D,uEAAuE;AACvE,2BAA2B;AAC3B,kEAAkE;AAElE,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAE7E,SAAS,cAAc,CAAC,GAA+B;IACrD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/B,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;iBAC5C,IAAI,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,CAAC;aAAM,IAAI,EAAE,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,oBAAoB;gBACxB,WAAW,EAAE,2EAA2E;aACzF;SACF;QACD,QAAQ,EAAE;YACR,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,MAAM,OAAO,GAAG,IAAqB,CAAA;gBACrC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;gBAC5F,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;oBAChC,IACE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;wBACtB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;wBAC7B,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EACtC,CAAC;wBACD,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAAA;wBAC7B,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;wBAC5B,IAAI,KAAK,IAAI,EAAE,CAAC,yBAAyB,CAAC,KAAK,CAAC,EAAE,CAAC;4BACjD,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;4BAClC,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gCACtC,GAAG,CAAC,gBAAgB,CAAC;oCACnB,EAAE,EAAE,oBAAoB;oCACxB,QAAQ,EAAE,OAAO;oCACjB,QAAQ,EAAE,OAAO;oCACjB,OAAO,EACL,sEAAsE;wCACtE,oCAAoC;oCACtC,QAAQ,EAAE;wCACR,IAAI,EAAE,EAAE,CAAC,QAAQ;wCACjB,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;qCAC7D;iCACF,CAAC,CAAA;4BACJ,CAAC;4BACD,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gCAC/E,GAAG,CAAC,gBAAgB,CAAC;oCACnB,EAAE,EAAE,oBAAoB;oCACxB,QAAQ,EAAE,OAAO;oCACjB,QAAQ,EAAE,OAAO;oCACjB,OAAO,EACL,eAAe,GAAG,0DAA0D;wCAC5E,6EAA6E;wCAC7E,kBAAkB;oCACpB,QAAQ,EAAE;wCACR,IAAI,EAAE,EAAE,CAAC,QAAQ;wCACjB,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;qCAC7D;iCACF,CAAC,CAAA;4BACJ,CAAC;wBACH,CAAC;oBACH,CAAC;oBACD,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;gBAC1B,CAAC,CAAA;gBACD,IAAI,CAAC,EAAE,CAAC,CAAA;YACV,CAAC;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["// `accessibility` — two a11y nudges:\n// - `<img>` without `alt` → screen readers read filename\n// - `onClick` on a non-interactive element without `role` → keyboard\n// users can't reach it\n// Migrated from `@llui/eslint-plugin/src/rules/accessibility.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { ELEMENT_HELPERS, INTERACTIVE_ELEMENTS } from './_element-helpers.js'\n\nfunction staticPropKeys(obj: ts.ObjectLiteralExpression): Set<string> {\n const out = new Set<string>()\n for (const p of obj.properties) {\n if (ts.isPropertyAssignment(p)) {\n if (ts.isIdentifier(p.name)) out.add(p.name.text)\n else if (ts.isStringLiteral(p.name)) out.add(p.name.text)\n } else if (ts.isShorthandPropertyAssignment(p)) {\n out.add(p.name.text)\n }\n }\n return out\n}\n\nexport function accessibilityModule(): CompilerModule {\n return {\n name: 'accessibility',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/accessibility',\n description: 'Missing alt on <img>, or onClick on non-interactive element without role.',\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 walk = (n: ts.Node): void => {\n if (\n ts.isCallExpression(n) &&\n ts.isIdentifier(n.expression) &&\n ELEMENT_HELPERS.has(n.expression.text)\n ) {\n const tag = n.expression.text\n const props = n.arguments[0]\n if (props && ts.isObjectLiteralExpression(props)) {\n const keys = staticPropKeys(props)\n if (tag === 'img' && !keys.has('alt')) {\n ctx.reportDiagnostic({\n id: 'llui/accessibility',\n severity: 'error',\n category: 'style',\n message:\n `<img> has no \\`alt\\` attribute. Add alt text for screen readers, or ` +\n `\\`alt: ''\\` for decorative images.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n if (keys.has('onClick') && !INTERACTIVE_ELEMENTS.has(tag) && !keys.has('role')) {\n ctx.reportDiagnostic({\n id: 'llui/accessibility',\n severity: 'error',\n category: 'style',\n message:\n `onClick on <${tag}> without \\`role\\`. Non-interactive elements with click ` +\n `handlers are not keyboard-accessible. Add \\`role: 'button', tabIndex: 0\\`, ` +\n `or use <button>.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function accessorSideEffectModule(): CompilerModule;
3
+ //# sourceMappingURL=accessor-side-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessor-side-effect.d.ts","sourceRoot":"","sources":["../../src/modules/accessor-side-effect.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAuClD,wBAAgB,wBAAwB,IAAI,cAAc,CAqEzD"}
@@ -0,0 +1,113 @@
1
+ // `accessor-side-effect` — errors on `console.{log,warn,error}`,
2
+ // `fetch`, and `alert` calls inside reactive accessor functions
3
+ // (the arrow passed to `text(...)` or to a non-handler property like
4
+ // `class:`). Accessors re-execute on every relevant state change;
5
+ // side effects there fire repeatedly with no flush guarantee.
6
+ // Migrated from `@llui/eslint-plugin/src/rules/accessor-side-effect.ts`.
7
+ import ts from 'typescript';
8
+ import { rangeFromOffsets } from '../diagnostic.js';
9
+ import { findComponentCalls } from './_shared.js';
10
+ const SIDE_EFFECT_CALLEES = new Set(['fetch', 'alert']);
11
+ const CONSOLE_METHODS = new Set(['log', 'warn', 'error']);
12
+ function findViewProperty(call) {
13
+ const arg = call.arguments[0];
14
+ if (!arg || !ts.isObjectLiteralExpression(arg))
15
+ return undefined;
16
+ for (const prop of arg.properties) {
17
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'view') {
18
+ return prop;
19
+ }
20
+ }
21
+ return undefined;
22
+ }
23
+ /**
24
+ * True when `fn` sits in an accessor position: the first argument of a
25
+ * `text(...)` call, or the value of a non-`on*` property assignment
26
+ * (so `class: s => …` qualifies, `onClick: s => …` does not).
27
+ */
28
+ function isAccessorArrow(fn) {
29
+ const parent = fn.parent;
30
+ if (!parent)
31
+ return false;
32
+ if (ts.isCallExpression(parent) &&
33
+ ts.isIdentifier(parent.expression) &&
34
+ parent.expression.text === 'text' &&
35
+ parent.arguments[0] === fn) {
36
+ return true;
37
+ }
38
+ if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
39
+ return !/^on[A-Z]/.test(parent.name.text);
40
+ }
41
+ return false;
42
+ }
43
+ export function accessorSideEffectModule() {
44
+ return {
45
+ name: 'accessor-side-effect',
46
+ compilerVersion: '^0.3.0',
47
+ diagnostics: [
48
+ {
49
+ id: 'llui/accessor-side-effect',
50
+ description: 'Side effect (console, fetch, alert) inside a reactive accessor — accessors re-run on every state change.',
51
+ },
52
+ ],
53
+ visitors: {
54
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
55
+ const visited = node;
56
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
57
+ for (const call of findComponentCalls(sf)) {
58
+ const viewProp = findViewProperty(call);
59
+ if (!viewProp)
60
+ continue;
61
+ const fn = viewProp.initializer;
62
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
63
+ continue;
64
+ const walk = (n, inAccessor) => {
65
+ let nextAccessor = inAccessor;
66
+ if (ts.isArrowFunction(n)) {
67
+ // An accessor boundary either turns the flag on (entering
68
+ // a text() arg / property accessor) or doesn't change it.
69
+ if (isAccessorArrow(n))
70
+ nextAccessor = true;
71
+ }
72
+ if (inAccessor && ts.isCallExpression(n)) {
73
+ // console.{log,warn,error}(…)
74
+ if (ts.isPropertyAccessExpression(n.expression) &&
75
+ ts.isIdentifier(n.expression.expression) &&
76
+ n.expression.expression.text === 'console' &&
77
+ ts.isIdentifier(n.expression.name) &&
78
+ CONSOLE_METHODS.has(n.expression.name.text)) {
79
+ ctx.reportDiagnostic({
80
+ id: 'llui/accessor-side-effect',
81
+ severity: 'error',
82
+ category: 'reactivity',
83
+ message: `\`console.${n.expression.name.text}\` inside a reactive accessor — fires on every state change with no flush guarantee. Move logging to update() or an effect handler.`,
84
+ location: {
85
+ file: sf.fileName,
86
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
87
+ },
88
+ });
89
+ }
90
+ // fetch(…), alert(…)
91
+ if (ts.isIdentifier(n.expression) && SIDE_EFFECT_CALLEES.has(n.expression.text)) {
92
+ ctx.reportDiagnostic({
93
+ id: 'llui/accessor-side-effect',
94
+ severity: 'error',
95
+ category: 'reactivity',
96
+ message: `\`${n.expression.text}()\` inside a reactive accessor — fires on every state change. Move the call to update() (return an effect) or to an effect handler.`,
97
+ location: {
98
+ file: sf.fileName,
99
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
100
+ },
101
+ });
102
+ }
103
+ }
104
+ ts.forEachChild(n, (c) => walk(c, nextAccessor));
105
+ };
106
+ if (fn.body)
107
+ walk(fn.body, false);
108
+ }
109
+ },
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=accessor-side-effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessor-side-effect.js","sourceRoot":"","sources":["../../src/modules/accessor-side-effect.ts"],"names":[],"mappings":"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","sourcesContent":["// `accessor-side-effect` — errors on `console.{log,warn,error}`,\n// `fetch`, and `alert` calls inside reactive accessor functions\n// (the arrow passed to `text(...)` or to a non-handler property like\n// `class:`). Accessors re-execute on every relevant state change;\n// side effects there fire repeatedly with no flush guarantee.\n// Migrated from `@llui/eslint-plugin/src/rules/accessor-side-effect.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nconst SIDE_EFFECT_CALLEES = new Set(['fetch', 'alert'])\nconst CONSOLE_METHODS = new Set(['log', 'warn', 'error'])\n\nfunction findViewProperty(call: ts.CallExpression): ts.PropertyAssignment | undefined {\n const arg = call.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return undefined\n for (const prop of arg.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'view') {\n return prop\n }\n }\n return undefined\n}\n\n/**\n * True when `fn` sits in an accessor position: the first argument of a\n * `text(...)` call, or the value of a non-`on*` property assignment\n * (so `class: s => …` qualifies, `onClick: s => …` does not).\n */\nfunction isAccessorArrow(fn: ts.ArrowFunction): boolean {\n const parent = fn.parent\n if (!parent) return false\n if (\n ts.isCallExpression(parent) &&\n ts.isIdentifier(parent.expression) &&\n parent.expression.text === 'text' &&\n parent.arguments[0] === fn\n ) {\n return true\n }\n if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {\n return !/^on[A-Z]/.test(parent.name.text)\n }\n return false\n}\n\nexport function accessorSideEffectModule(): CompilerModule {\n return {\n name: 'accessor-side-effect',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/accessor-side-effect',\n description:\n 'Side effect (console, fetch, alert) inside a reactive accessor — accessors re-run on every state change.',\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 for (const call of findComponentCalls(sf)) {\n const viewProp = findViewProperty(call)\n if (!viewProp) continue\n const fn = viewProp.initializer\n if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) continue\n\n const walk = (n: ts.Node, inAccessor: boolean): void => {\n let nextAccessor = inAccessor\n if (ts.isArrowFunction(n)) {\n // An accessor boundary either turns the flag on (entering\n // a text() arg / property accessor) or doesn't change it.\n if (isAccessorArrow(n)) nextAccessor = true\n }\n if (inAccessor && ts.isCallExpression(n)) {\n // console.{log,warn,error}(…)\n if (\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.expression) &&\n n.expression.expression.text === 'console' &&\n ts.isIdentifier(n.expression.name) &&\n CONSOLE_METHODS.has(n.expression.name.text)\n ) {\n ctx.reportDiagnostic({\n id: 'llui/accessor-side-effect',\n severity: 'error',\n category: 'reactivity',\n message: `\\`console.${n.expression.name.text}\\` inside a reactive accessor — fires on every state change with no flush guarantee. Move logging to update() or an effect handler.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n // fetch(…), alert(…)\n if (ts.isIdentifier(n.expression) && SIDE_EFFECT_CALLEES.has(n.expression.text)) {\n ctx.reportDiagnostic({\n id: 'llui/accessor-side-effect',\n severity: 'error',\n category: 'reactivity',\n message: `\\`${n.expression.text}()\\` inside a reactive accessor — fires on every state change. Move the call to update() (return an effect) or to an effect handler.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n }\n ts.forEachChild(n, (c) => walk(c, nextAccessor))\n }\n if (fn.body) walk(fn.body, false)\n }\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function agentEmitsDriftModule(): CompilerModule;
3
+ //# sourceMappingURL=agent-emits-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-emits-drift.d.ts","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAmFlD,wBAAgB,qBAAqB,IAAI,cAAc,CAiEtD"}
@@ -0,0 +1,158 @@
1
+ // `agent-emits-drift` — verifies `@emits("k1", "k2")` declarations on
2
+ // Msg variants match the effect kinds the corresponding case in
3
+ // update() actually emits.
4
+ //
5
+ // Two failure modes detected:
6
+ // 1. **Emitted but not declared** — a literal `{ kind: 'X' }` appears
7
+ // in the case's effect array but 'X' isn't in @emits.
8
+ // 2. **Declared but not emitted** — a kind in @emits doesn't appear in
9
+ // any literal effect (and the case has no opaque helper calls that
10
+ // might emit it).
11
+ //
12
+ // File-local scope: Msg union and the update() switch must be in the
13
+ // same source file. Cross-file resolution would require type-checker
14
+ // integration. Migrated from
15
+ // `@llui/eslint-plugin/src/rules/agent-emits-drift.ts`.
16
+ import ts from 'typescript';
17
+ import { rangeFromOffsets } from '../diagnostic.js';
18
+ import { forEachMsgVariant } from './_msg-variants.js';
19
+ function readEmits(commentText) {
20
+ const outer = commentText.match(/@emits\s*\(([^)]*)\)/);
21
+ if (!outer || outer[1] === undefined)
22
+ return [];
23
+ const inner = outer[1];
24
+ const seen = new Set();
25
+ const out = [];
26
+ const re = /["“]([^"”]*)["”]/g;
27
+ let m;
28
+ while ((m = re.exec(inner)) !== null) {
29
+ if (m[1] !== undefined && !seen.has(m[1])) {
30
+ seen.add(m[1]);
31
+ out.push(m[1]);
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ function walkCaseBody(body, info) {
37
+ const walk = (n) => {
38
+ if (ts.isReturnStatement(n) && n.expression) {
39
+ const arg = n.expression;
40
+ if (ts.isArrayLiteralExpression(arg) && arg.elements.length === 2) {
41
+ const effects = arg.elements[1];
42
+ if (effects && ts.isArrayLiteralExpression(effects)) {
43
+ for (const el of effects.elements) {
44
+ if (ts.isObjectLiteralExpression(el)) {
45
+ for (const prop of el.properties) {
46
+ if (ts.isPropertyAssignment(prop) &&
47
+ ts.isIdentifier(prop.name) &&
48
+ prop.name.text === 'kind' &&
49
+ ts.isStringLiteral(prop.initializer)) {
50
+ info.literalKinds.add(prop.initializer.text);
51
+ }
52
+ }
53
+ }
54
+ else if (ts.isCallExpression(el) || ts.isSpreadElement(el)) {
55
+ info.hasOpaqueHelpers = true;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ ts.forEachChild(n, walk);
62
+ };
63
+ walk(body);
64
+ }
65
+ function indexSwitchCases(sf, into) {
66
+ const walk = (n) => {
67
+ if (ts.isSwitchStatement(n)) {
68
+ const disc = n.expression;
69
+ const looksLikeMsgType = ts.isPropertyAccessExpression(disc) &&
70
+ ts.isIdentifier(disc.name) &&
71
+ disc.name.text === 'type';
72
+ if (looksLikeMsgType) {
73
+ for (const sc of n.caseBlock.clauses) {
74
+ if (!ts.isCaseClause(sc))
75
+ continue;
76
+ if (!sc.expression || !ts.isStringLiteral(sc.expression))
77
+ continue;
78
+ const variant = sc.expression.text;
79
+ const info = into.get(variant) ?? {
80
+ literalKinds: new Set(),
81
+ hasOpaqueHelpers: false,
82
+ };
83
+ for (const stmt of sc.statements)
84
+ walkCaseBody(stmt, info);
85
+ into.set(variant, info);
86
+ }
87
+ }
88
+ }
89
+ ts.forEachChild(n, walk);
90
+ };
91
+ walk(sf);
92
+ }
93
+ export function agentEmitsDriftModule() {
94
+ return {
95
+ name: 'agent-emits-drift',
96
+ compilerVersion: '^0.3.0',
97
+ diagnostics: [
98
+ {
99
+ id: 'llui/agent-emits-drift',
100
+ description: '@emits declaration on Msg variant drifts from actual effect emissions in update().',
101
+ },
102
+ ],
103
+ visitors: {
104
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
105
+ const visited = node;
106
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
107
+ const caseInfoByVariant = new Map();
108
+ indexSwitchCases(sf, caseInfoByVariant);
109
+ if (caseInfoByVariant.size === 0)
110
+ return;
111
+ forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {
112
+ const declared = readEmits(leadingCommentText);
113
+ const caseInfo = caseInfoByVariant.get(variant);
114
+ if (!caseInfo)
115
+ return;
116
+ // Drift 1: literal emissions not in @emits — always warn.
117
+ for (const kind of caseInfo.literalKinds) {
118
+ if (!declared.includes(kind)) {
119
+ ctx.reportDiagnostic({
120
+ id: 'llui/agent-emits-drift',
121
+ severity: 'error',
122
+ category: 'agent',
123
+ message: `Msg variant "${variant}" emits effect kind "${kind}" in update() but doesn't ` +
124
+ `declare it in @emits. Either add "${kind}" to the @emits list ` +
125
+ `(\`@emits("${kind}")\`), or remove the literal effect emission.`,
126
+ location: {
127
+ file: sf.fileName,
128
+ range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),
129
+ },
130
+ });
131
+ }
132
+ }
133
+ // Drift 2: @emits-declared but no literal emission in the case.
134
+ // Skip when opaque helpers might be emitting the kind.
135
+ if (!caseInfo.hasOpaqueHelpers) {
136
+ for (const kind of declared) {
137
+ if (!caseInfo.literalKinds.has(kind)) {
138
+ ctx.reportDiagnostic({
139
+ id: 'llui/agent-emits-drift',
140
+ severity: 'error',
141
+ category: 'agent',
142
+ message: `Msg variant "${variant}" declares @emits "${kind}" but no case in update() ` +
143
+ `emits it as a literal effect. Either remove "${kind}" from @emits, or add ` +
144
+ `the emission (\`return [state, [{ kind: '${kind}', … }]]\`).`,
145
+ location: {
146
+ file: sf.fileName,
147
+ range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),
148
+ },
149
+ });
150
+ }
151
+ }
152
+ }
153
+ });
154
+ },
155
+ },
156
+ };
157
+ }
158
+ //# sourceMappingURL=agent-emits-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-emits-drift.js","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"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","sourcesContent":["// `agent-emits-drift` — verifies `@emits(\"k1\", \"k2\")` declarations on\n// Msg variants match the effect kinds the corresponding case in\n// update() actually emits.\n//\n// Two failure modes detected:\n// 1. **Emitted but not declared** — a literal `{ kind: 'X' }` appears\n// in the case's effect array but 'X' isn't in @emits.\n// 2. **Declared but not emitted** — a kind in @emits doesn't appear in\n// any literal effect (and the case has no opaque helper calls that\n// might emit it).\n//\n// File-local scope: Msg union and the update() switch must be in the\n// same source file. Cross-file resolution would require type-checker\n// integration. Migrated from\n// `@llui/eslint-plugin/src/rules/agent-emits-drift.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { forEachMsgVariant } from './_msg-variants.js'\n\ninterface CaseInfo {\n literalKinds: Set<string>\n hasOpaqueHelpers: boolean\n}\n\nfunction readEmits(commentText: string): string[] {\n const outer = commentText.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 if (m[1] !== undefined && !seen.has(m[1])) {\n seen.add(m[1])\n out.push(m[1])\n }\n }\n return out\n}\n\nfunction walkCaseBody(body: ts.Node, info: CaseInfo): void {\n const walk = (n: ts.Node): void => {\n if (ts.isReturnStatement(n) && n.expression) {\n const arg = n.expression\n if (ts.isArrayLiteralExpression(arg) && arg.elements.length === 2) {\n const effects = arg.elements[1]\n if (effects && ts.isArrayLiteralExpression(effects)) {\n for (const el of effects.elements) {\n if (ts.isObjectLiteralExpression(el)) {\n for (const prop of el.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'kind' &&\n ts.isStringLiteral(prop.initializer)\n ) {\n info.literalKinds.add(prop.initializer.text)\n }\n }\n } else if (ts.isCallExpression(el) || ts.isSpreadElement(el)) {\n info.hasOpaqueHelpers = true\n }\n }\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n}\n\nfunction indexSwitchCases(sf: ts.SourceFile, into: Map<string, CaseInfo>): void {\n const walk = (n: ts.Node): void => {\n if (ts.isSwitchStatement(n)) {\n const disc = n.expression\n const looksLikeMsgType =\n ts.isPropertyAccessExpression(disc) &&\n ts.isIdentifier(disc.name) &&\n disc.name.text === 'type'\n if (looksLikeMsgType) {\n for (const sc of n.caseBlock.clauses) {\n if (!ts.isCaseClause(sc)) continue\n if (!sc.expression || !ts.isStringLiteral(sc.expression)) continue\n const variant = sc.expression.text\n const info: CaseInfo = into.get(variant) ?? {\n literalKinds: new Set(),\n hasOpaqueHelpers: false,\n }\n for (const stmt of sc.statements) walkCaseBody(stmt, info)\n into.set(variant, info)\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n}\n\nexport function agentEmitsDriftModule(): CompilerModule {\n return {\n name: 'agent-emits-drift',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/agent-emits-drift',\n description:\n '@emits declaration on Msg variant drifts from actual effect emissions in update().',\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 caseInfoByVariant = new Map<string, CaseInfo>()\n indexSwitchCases(sf, caseInfoByVariant)\n if (caseInfoByVariant.size === 0) return\n forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {\n const declared = readEmits(leadingCommentText)\n const caseInfo = caseInfoByVariant.get(variant)\n if (!caseInfo) return\n // Drift 1: literal emissions not in @emits — always warn.\n for (const kind of caseInfo.literalKinds) {\n if (!declared.includes(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" emits effect kind \"${kind}\" in update() but doesn't ` +\n `declare it in @emits. Either add \"${kind}\" to the @emits list ` +\n `(\\`@emits(\"${kind}\")\\`), or remove the literal effect emission.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n }\n }\n // Drift 2: @emits-declared but no literal emission in the case.\n // Skip when opaque helpers might be emitting the kind.\n if (!caseInfo.hasOpaqueHelpers) {\n for (const kind of declared) {\n if (!caseInfo.literalKinds.has(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" declares @emits \"${kind}\" but no case in update() ` +\n `emits it as a literal effect. Either remove \"${kind}\" from @emits, or add ` +\n `the emission (\\`return [state, [{ kind: '${kind}', … }]]\\`).`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n }\n }\n }\n })\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function agentExampleOnPayloadModule(): CompilerModule;
3
+ //# sourceMappingURL=agent-example-on-payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-example-on-payload.d.ts","sourceRoot":"","sources":["../../src/modules/agent-example-on-payload.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,wBAAgB,2BAA2B,IAAI,cAAc,CAwC5D"}
@@ -0,0 +1,53 @@
1
+ // `agent-example-on-payload` — errors when a payload-bearing Msg
2
+ // variant (fields beyond `type`) has `@intent` but no `@example("…")`.
3
+ // Bare intent says "what does this do"; example says "when do I use it
4
+ // / how is it shaped in practice." Required only on payload-bearing
5
+ // variants — nullary variants (Reset, Open, Close) are obvious.
6
+ // Migrated from `@llui/eslint-plugin/src/rules/agent-example-on-payload.ts`.
7
+ import ts from 'typescript';
8
+ import { rangeFromOffsets } from '../diagnostic.js';
9
+ import { forEachMsgVariant, variantHasPayload } from './_msg-variants.js';
10
+ export function agentExampleOnPayloadModule() {
11
+ return {
12
+ name: 'agent-example-on-payload',
13
+ compilerVersion: '^0.3.0',
14
+ diagnostics: [
15
+ {
16
+ id: 'llui/agent-example-on-payload',
17
+ description: 'Msg variant with payload fields has @intent but no @example — LLM benefits from worked usage.',
18
+ },
19
+ ],
20
+ visitors: {
21
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
22
+ const visited = node;
23
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
24
+ forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {
25
+ if (!variantHasPayload(typeLit))
26
+ return;
27
+ if (/@humanOnly\b/.test(leadingCommentText))
28
+ return;
29
+ // Skip variants without @intent — agent-missing-intent will
30
+ // already fire there. No point double-nagging.
31
+ if (!/@intent\s*\(/.test(leadingCommentText))
32
+ return;
33
+ if (/@example\s*\(/.test(leadingCommentText))
34
+ return;
35
+ ctx.reportDiagnostic({
36
+ id: 'llui/agent-example-on-payload',
37
+ severity: 'error',
38
+ category: 'agent',
39
+ message: `Msg variant "${variant}" has payload fields but no \`@example("…")\`. ` +
40
+ `Add at least one worked example showing typical usage: ` +
41
+ `\`@example("Add a todo with text 'buy milk'")\` — the LLM uses this to ` +
42
+ `disambiguate when/how to dispatch.`,
43
+ location: {
44
+ file: sf.fileName,
45
+ range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),
46
+ },
47
+ });
48
+ });
49
+ },
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=agent-example-on-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-example-on-payload.js","sourceRoot":"","sources":["../../src/modules/agent-example-on-payload.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,gEAAgE;AAChE,6EAA6E;AAE7E,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEzE,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,+BAA+B;gBACnC,WAAW,EACT,+FAA+F;aAClG;SACF;QACD,QAAQ,EAAE;YACR,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,MAAM,OAAO,GAAG,IAAqB,CAAA;gBACrC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;gBAC5F,iBAAiB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE;oBACvE,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;wBAAE,OAAM;oBACvC,IAAI,cAAc,CAAC,IAAI,CAAC,kBAAkB,CAAC;wBAAE,OAAM;oBACnD,4DAA4D;oBAC5D,+CAA+C;oBAC/C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,CAAC;wBAAE,OAAM;oBACpD,IAAI,eAAe,CAAC,IAAI,CAAC,kBAAkB,CAAC;wBAAE,OAAM;oBACpD,GAAG,CAAC,gBAAgB,CAAC;wBACnB,EAAE,EAAE,+BAA+B;wBACnC,QAAQ,EAAE,OAAO;wBACjB,QAAQ,EAAE,OAAO;wBACjB,OAAO,EACL,gBAAgB,OAAO,iDAAiD;4BACxE,yDAAyD;4BACzD,yEAAyE;4BACzE,oCAAoC;wBACtC,QAAQ,EAAE;4BACR,IAAI,EAAE,EAAE,CAAC,QAAQ;4BACjB,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;yBACzE;qBACF,CAAC,CAAA;gBACJ,CAAC,CAAC,CAAA;YACJ,CAAC;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["// `agent-example-on-payload` — errors when a payload-bearing Msg\n// variant (fields beyond `type`) has `@intent` but no `@example(\"…\")`.\n// Bare intent says \"what does this do\"; example says \"when do I use it\n// / how is it shaped in practice.\" Required only on payload-bearing\n// variants — nullary variants (Reset, Open, Close) are obvious.\n// Migrated from `@llui/eslint-plugin/src/rules/agent-example-on-payload.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { forEachMsgVariant, variantHasPayload } from './_msg-variants.js'\n\nexport function agentExampleOnPayloadModule(): CompilerModule {\n return {\n name: 'agent-example-on-payload',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/agent-example-on-payload',\n description:\n 'Msg variant with payload fields has @intent but no @example — LLM benefits from worked usage.',\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 forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {\n if (!variantHasPayload(typeLit)) return\n if (/@humanOnly\\b/.test(leadingCommentText)) return\n // Skip variants without @intent — agent-missing-intent will\n // already fire there. No point double-nagging.\n if (!/@intent\\s*\\(/.test(leadingCommentText)) return\n if (/@example\\s*\\(/.test(leadingCommentText)) return\n ctx.reportDiagnostic({\n id: 'llui/agent-example-on-payload',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" has payload fields but no \\`@example(\"…\")\\`. ` +\n `Add at least one worked example showing typical usage: ` +\n `\\`@example(\"Add a todo with text 'buy milk'\")\\` — the LLM uses this to ` +\n `disambiguate when/how to dispatch.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n })\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function agentExclusiveAnnotationsModule(): CompilerModule;
3
+ //# sourceMappingURL=agent-exclusive-annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-exclusive-annotations.d.ts","sourceRoot":"","sources":["../../src/modules/agent-exclusive-annotations.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,wBAAgB,+BAA+B,IAAI,cAAc,CAwDhE"}
@@ -0,0 +1,68 @@
1
+ // `agent-exclusive-annotations` — errors on Msg variant annotation
2
+ // combinations that are mutually exclusive:
3
+ // - @humanOnly + @agentOnly: incoherent (opposite audiences).
4
+ // - @humanOnly + @requiresConfirm: redundant (humanOnly dominates).
5
+ // - @humanOnly + @alwaysAffordable: redundant.
6
+ // Migrated from `@llui/eslint-plugin/src/rules/agent-exclusive-annotations.ts`.
7
+ import ts from 'typescript';
8
+ import { rangeFromOffsets } from '../diagnostic.js';
9
+ import { forEachMsgVariant } from './_msg-variants.js';
10
+ export function agentExclusiveAnnotationsModule() {
11
+ return {
12
+ name: 'agent-exclusive-annotations',
13
+ compilerVersion: '^0.3.0',
14
+ diagnostics: [
15
+ {
16
+ id: 'llui/agent-exclusive-annotations',
17
+ description: '@humanOnly and @agentOnly are mutually exclusive; @humanOnly also conflicts with @requiresConfirm / @alwaysAffordable.',
18
+ },
19
+ ],
20
+ visitors: {
21
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
22
+ const visited = node;
23
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
24
+ forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {
25
+ const hasHumanOnly = /@humanOnly\b/.test(leadingCommentText);
26
+ const hasAgentOnly = /@agentOnly\b/.test(leadingCommentText);
27
+ if (hasHumanOnly && hasAgentOnly) {
28
+ ctx.reportDiagnostic({
29
+ id: 'llui/agent-exclusive-annotations',
30
+ severity: 'error',
31
+ category: 'agent',
32
+ message: `Msg variant "${variant}" has both @humanOnly and @agentOnly. ` +
33
+ `Pick one — they describe opposite dispatch audiences. ` +
34
+ `Use @humanOnly if the LLM should never see this variant; @agentOnly if only the LLM should.`,
35
+ location: {
36
+ file: sf.fileName,
37
+ range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),
38
+ },
39
+ });
40
+ return;
41
+ }
42
+ if (!hasHumanOnly)
43
+ return;
44
+ const conflicts = [];
45
+ if (/@requiresConfirm\b/.test(leadingCommentText))
46
+ conflicts.push('@requiresConfirm');
47
+ if (/@alwaysAffordable\b/.test(leadingCommentText))
48
+ conflicts.push('@alwaysAffordable');
49
+ if (conflicts.length === 0)
50
+ return;
51
+ ctx.reportDiagnostic({
52
+ id: 'llui/agent-exclusive-annotations',
53
+ severity: 'error',
54
+ category: 'agent',
55
+ message: `Msg variant "${variant}" has @humanOnly combined with ${conflicts.join(' and ')}; ` +
56
+ `@humanOnly dominates and makes the other redundant — the LLM never sees this variant, ` +
57
+ `so confirmation / affordance hints have no audience. Remove the ${conflicts.join(' / ')} tag${conflicts.length > 1 ? 's' : ''}.`,
58
+ location: {
59
+ file: sf.fileName,
60
+ range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),
61
+ },
62
+ });
63
+ });
64
+ },
65
+ },
66
+ };
67
+ }
68
+ //# sourceMappingURL=agent-exclusive-annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-exclusive-annotations.js","sourceRoot":"","sources":["../../src/modules/agent-exclusive-annotations.ts"],"names":[],"mappings":"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","sourcesContent":["// `agent-exclusive-annotations` — errors on Msg variant annotation\n// combinations that are mutually exclusive:\n// - @humanOnly + @agentOnly: incoherent (opposite audiences).\n// - @humanOnly + @requiresConfirm: redundant (humanOnly dominates).\n// - @humanOnly + @alwaysAffordable: redundant.\n// Migrated from `@llui/eslint-plugin/src/rules/agent-exclusive-annotations.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { forEachMsgVariant } from './_msg-variants.js'\n\nexport function agentExclusiveAnnotationsModule(): CompilerModule {\n return {\n name: 'agent-exclusive-annotations',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/agent-exclusive-annotations',\n description:\n '@humanOnly and @agentOnly are mutually exclusive; @humanOnly also conflicts with @requiresConfirm / @alwaysAffordable.',\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 forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {\n const hasHumanOnly = /@humanOnly\\b/.test(leadingCommentText)\n const hasAgentOnly = /@agentOnly\\b/.test(leadingCommentText)\n if (hasHumanOnly && hasAgentOnly) {\n ctx.reportDiagnostic({\n id: 'llui/agent-exclusive-annotations',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" has both @humanOnly and @agentOnly. ` +\n `Pick one — they describe opposite dispatch audiences. ` +\n `Use @humanOnly if the LLM should never see this variant; @agentOnly if only the LLM should.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n return\n }\n if (!hasHumanOnly) return\n const conflicts: string[] = []\n if (/@requiresConfirm\\b/.test(leadingCommentText)) conflicts.push('@requiresConfirm')\n if (/@alwaysAffordable\\b/.test(leadingCommentText)) conflicts.push('@alwaysAffordable')\n if (conflicts.length === 0) return\n ctx.reportDiagnostic({\n id: 'llui/agent-exclusive-annotations',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" has @humanOnly combined with ${conflicts.join(' and ')}; ` +\n `@humanOnly dominates and makes the other redundant — the LLM never sees this variant, ` +\n `so confirmation / affordance hints have no audience. Remove the ${conflicts.join(' / ')} tag${conflicts.length > 1 ? 's' : ''}.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n })\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function agentMissingIntentModule(): CompilerModule;
3
+ //# sourceMappingURL=agent-missing-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-missing-intent.d.ts","sourceRoot":"","sources":["../../src/modules/agent-missing-intent.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,wBAAgB,wBAAwB,IAAI,cAAc,CAoCzD"}