@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,144 @@
1
+ // `spread-in-children` — errors when an element-helper's children array
2
+ // contains a spread of a dynamic source (`div([...someList()])`).
3
+ // Disables the compiler's template-clone optimization. Locally-bounded
4
+ // spreads (`const xs = [a, b]; div([...xs])`) are exempt. Migrated from
5
+ // `@llui/eslint-plugin/src/rules/spread-in-children.ts`.
6
+ import ts from 'typescript';
7
+ import { rangeFromOffsets } from '../diagnostic.js';
8
+ import { ELEMENT_HELPERS } from './_element-helpers.js';
9
+ const ARRAY_ITERATION_METHODS = new Set([
10
+ 'map',
11
+ 'filter',
12
+ 'flatMap',
13
+ 'slice',
14
+ 'concat',
15
+ 'reverse',
16
+ 'sort',
17
+ ]);
18
+ /** Walk top-level statements and resolve `const NAME = init` to the init expression. */
19
+ function resolveTopLevelConstInit(sf, name) {
20
+ for (const stmt of sf.statements) {
21
+ if (!ts.isVariableStatement(stmt))
22
+ continue;
23
+ if (!(stmt.declarationList.flags & ts.NodeFlags.Const))
24
+ continue;
25
+ for (const d of stmt.declarationList.declarations) {
26
+ if (ts.isIdentifier(d.name) && d.name.text === name)
27
+ return d.initializer ?? null;
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ function isBoundedArrayReceiver(sf, recv) {
33
+ if (!ts.isIdentifier(recv))
34
+ return false;
35
+ const init = resolveTopLevelConstInit(sf, recv.text);
36
+ if (!init)
37
+ return false;
38
+ if (ts.isArrayLiteralExpression(init))
39
+ return true;
40
+ if (ts.isAsExpression(init)) {
41
+ return ts.isArrayLiteralExpression(init.expression);
42
+ }
43
+ return false;
44
+ }
45
+ function isBoundedInitializer(sf, init) {
46
+ if (ts.isArrayLiteralExpression(init))
47
+ return true;
48
+ if (ts.isAsExpression(init))
49
+ return isBoundedInitializer(sf, init.expression);
50
+ if (ts.isCallExpression(init)) {
51
+ if (ts.isIdentifier(init.expression))
52
+ return true;
53
+ if (ts.isPropertyAccessExpression(init.expression) && ts.isIdentifier(init.expression.name)) {
54
+ const method = init.expression.name.text;
55
+ if (!ARRAY_ITERATION_METHODS.has(method))
56
+ return true;
57
+ return isBoundedArrayReceiver(sf, init.expression.expression);
58
+ }
59
+ }
60
+ return false;
61
+ }
62
+ function isBoundedSpreadSource(sf, expr) {
63
+ // Strip parens + `as` casts before classifying — `...(cond ? a : b)`
64
+ // wraps the conditional in a ParenthesizedExpression; `...x as never`
65
+ // wraps in an AsExpression.
66
+ let e = expr;
67
+ while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e))
68
+ e = e.expression;
69
+ if (ts.isIdentifier(e)) {
70
+ const init = resolveTopLevelConstInit(sf, e.text);
71
+ if (!init)
72
+ return false;
73
+ return isBoundedInitializer(sf, init);
74
+ }
75
+ // Inline array literal — `[...[a, b]]` is degenerate but bounded.
76
+ if (ts.isArrayLiteralExpression(e))
77
+ return true;
78
+ // Ternary `cond ? [literal] : []` is a conditional-render pattern;
79
+ // both branches must be bounded for the whole spread to be bounded.
80
+ if (ts.isConditionalExpression(e)) {
81
+ return isBoundedSpreadSource(sf, e.whenTrue) && isBoundedSpreadSource(sf, e.whenFalse);
82
+ }
83
+ if (ts.isCallExpression(e)) {
84
+ if (ts.isPropertyAccessExpression(e.expression) && ts.isIdentifier(e.expression.name)) {
85
+ const method = e.expression.name.text;
86
+ if (!ARRAY_ITERATION_METHODS.has(method))
87
+ return true;
88
+ return isBoundedArrayReceiver(sf, e.expression.expression);
89
+ }
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+ export function spreadInChildrenModule() {
95
+ return {
96
+ name: 'spread-in-children',
97
+ compilerVersion: '^0.3.0',
98
+ diagnostics: [
99
+ {
100
+ id: 'llui/spread-in-children',
101
+ description: 'Spread of a dynamic array into element children — disables template-clone optimization.',
102
+ },
103
+ ],
104
+ visitors: {
105
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
106
+ const visited = node;
107
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
108
+ const walk = (n) => {
109
+ if (ts.isCallExpression(n) &&
110
+ ts.isIdentifier(n.expression) &&
111
+ ELEMENT_HELPERS.has(n.expression.text)) {
112
+ for (const arg of n.arguments) {
113
+ if (!ts.isArrayLiteralExpression(arg))
114
+ continue;
115
+ for (const el of arg.elements) {
116
+ if (!ts.isSpreadElement(el))
117
+ continue;
118
+ if (isBoundedSpreadSource(sf, el.expression))
119
+ continue;
120
+ ctx.reportDiagnostic({
121
+ id: 'llui/spread-in-children',
122
+ severity: 'error',
123
+ category: 'perf',
124
+ message: `Spread of a dynamic array in children of \`${n.expression.text}()\` disables ` +
125
+ `the compiler's template-clone optimization (the child count is no longer ` +
126
+ `statically known). For dynamic child counts, use \`each({ items, key, render })\` ` +
127
+ `instead — it gets per-row scope, key-based reconciliation, and reactive bindings.`,
128
+ location: {
129
+ file: sf.fileName,
130
+ range: rangeFromOffsets(sf.text, el.getStart(sf), el.getEnd()),
131
+ },
132
+ });
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ ts.forEachChild(n, walk);
138
+ };
139
+ walk(sf);
140
+ },
141
+ },
142
+ };
143
+ }
144
+ //# sourceMappingURL=spread-in-children.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spread-in-children.js","sourceRoot":"","sources":["../../src/modules/spread-in-children.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,kEAAkE;AAClE,uEAAuE;AACvE,wEAAwE;AACxE,yDAAyD;AAEzD,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IACtC,KAAK;IACL,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,SAAS;IACT,MAAM;CACP,CAAC,CAAA;AAEF,wFAAwF;AACxF,SAAS,wBAAwB,CAAC,EAAiB,EAAE,IAAY;IAC/D,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC3C,IAAI,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,SAAQ;QAChE,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC;YAClD,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO,CAAC,CAAC,WAAW,IAAI,IAAI,CAAA;QACnF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,sBAAsB,CAAC,EAAiB,EAAE,IAAmB;IACpE,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IACxC,MAAM,IAAI,GAAG,wBAAwB,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClD,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,oBAAoB,CAAC,EAAiB,EAAE,IAAmB;IAClE,IAAI,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClD,IAAI,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC;QAAE,OAAO,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAA;QACjD,IAAI,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5F,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAA;YACxC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAA;YACrD,OAAO,sBAAsB,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,qBAAqB,CAAC,EAAiB,EAAE,IAAmB;IACnE,qEAAqE;IACrE,sEAAsE;IACtE,4BAA4B;IAC5B,IAAI,CAAC,GAAkB,IAAI,CAAA;IAC3B,OAAO,EAAE,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,UAAU,CAAA;IAChF,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,wBAAwB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QACvB,OAAO,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IACvC,CAAC;IACD,kEAAkE;IAClE,IAAI,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,mEAAmE;IACnE,oEAAoE;IACpE,IAAI,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;QAClC,OAAO,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAA;IACxF,CAAC;IACD,IAAI,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,IAAI,EAAE,CAAC,0BAA0B,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtF,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAA;YACrC,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAA;YACrD,OAAO,sBAAsB,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;QAC5D,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,yBAAyB;gBAC7B,WAAW,EACT,yFAAyF;aAC5F;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,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;4BAC9B,IAAI,CAAC,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC;gCAAE,SAAQ;4BAC/C,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;gCAC9B,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;oCAAE,SAAQ;gCACrC,IAAI,qBAAqB,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC;oCAAE,SAAQ;gCACtD,GAAG,CAAC,gBAAgB,CAAC;oCACnB,EAAE,EAAE,yBAAyB;oCAC7B,QAAQ,EAAE,OAAO;oCACjB,QAAQ,EAAE,MAAM;oCAChB,OAAO,EACL,8CAA8C,CAAC,CAAC,UAAU,CAAC,IAAI,gBAAgB;wCAC/E,2EAA2E;wCAC3E,oFAAoF;wCACpF,mFAAmF;oCACrF,QAAQ,EAAE;wCACR,IAAI,EAAE,EAAE,CAAC,QAAQ;wCACjB,KAAK,EAAE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC;qCAC/D;iCACF,CAAC,CAAA;gCACF,MAAK;4BACP,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":["// `spread-in-children` — errors when an element-helper's children array\n// contains a spread of a dynamic source (`div([...someList()])`).\n// Disables the compiler's template-clone optimization. Locally-bounded\n// spreads (`const xs = [a, b]; div([...xs])`) are exempt. Migrated from\n// `@llui/eslint-plugin/src/rules/spread-in-children.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { ELEMENT_HELPERS } from './_element-helpers.js'\n\nconst ARRAY_ITERATION_METHODS = new Set([\n 'map',\n 'filter',\n 'flatMap',\n 'slice',\n 'concat',\n 'reverse',\n 'sort',\n])\n\n/** Walk top-level statements and resolve `const NAME = init` to the init expression. */\nfunction resolveTopLevelConstInit(sf: ts.SourceFile, name: string): ts.Expression | null {\n for (const stmt of sf.statements) {\n if (!ts.isVariableStatement(stmt)) continue\n if (!(stmt.declarationList.flags & ts.NodeFlags.Const)) continue\n for (const d of stmt.declarationList.declarations) {\n if (ts.isIdentifier(d.name) && d.name.text === name) return d.initializer ?? null\n }\n }\n return null\n}\n\nfunction isBoundedArrayReceiver(sf: ts.SourceFile, recv: ts.Expression): boolean {\n if (!ts.isIdentifier(recv)) return false\n const init = resolveTopLevelConstInit(sf, recv.text)\n if (!init) return false\n if (ts.isArrayLiteralExpression(init)) return true\n if (ts.isAsExpression(init)) {\n return ts.isArrayLiteralExpression(init.expression)\n }\n return false\n}\n\nfunction isBoundedInitializer(sf: ts.SourceFile, init: ts.Expression): boolean {\n if (ts.isArrayLiteralExpression(init)) return true\n if (ts.isAsExpression(init)) return isBoundedInitializer(sf, init.expression)\n if (ts.isCallExpression(init)) {\n if (ts.isIdentifier(init.expression)) return true\n if (ts.isPropertyAccessExpression(init.expression) && ts.isIdentifier(init.expression.name)) {\n const method = init.expression.name.text\n if (!ARRAY_ITERATION_METHODS.has(method)) return true\n return isBoundedArrayReceiver(sf, init.expression.expression)\n }\n }\n return false\n}\n\nfunction isBoundedSpreadSource(sf: ts.SourceFile, expr: ts.Expression): boolean {\n // Strip parens + `as` casts before classifying — `...(cond ? a : b)`\n // wraps the conditional in a ParenthesizedExpression; `...x as never`\n // wraps in an AsExpression.\n let e: ts.Expression = expr\n while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e)) e = e.expression\n if (ts.isIdentifier(e)) {\n const init = resolveTopLevelConstInit(sf, e.text)\n if (!init) return false\n return isBoundedInitializer(sf, init)\n }\n // Inline array literal — `[...[a, b]]` is degenerate but bounded.\n if (ts.isArrayLiteralExpression(e)) return true\n // Ternary `cond ? [literal] : []` is a conditional-render pattern;\n // both branches must be bounded for the whole spread to be bounded.\n if (ts.isConditionalExpression(e)) {\n return isBoundedSpreadSource(sf, e.whenTrue) && isBoundedSpreadSource(sf, e.whenFalse)\n }\n if (ts.isCallExpression(e)) {\n if (ts.isPropertyAccessExpression(e.expression) && ts.isIdentifier(e.expression.name)) {\n const method = e.expression.name.text\n if (!ARRAY_ITERATION_METHODS.has(method)) return true\n return isBoundedArrayReceiver(sf, e.expression.expression)\n }\n return true\n }\n return false\n}\n\nexport function spreadInChildrenModule(): CompilerModule {\n return {\n name: 'spread-in-children',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/spread-in-children',\n description:\n 'Spread of a dynamic array into element children — disables template-clone optimization.',\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 for (const arg of n.arguments) {\n if (!ts.isArrayLiteralExpression(arg)) continue\n for (const el of arg.elements) {\n if (!ts.isSpreadElement(el)) continue\n if (isBoundedSpreadSource(sf, el.expression)) continue\n ctx.reportDiagnostic({\n id: 'llui/spread-in-children',\n severity: 'error',\n category: 'perf',\n message:\n `Spread of a dynamic array in children of \\`${n.expression.text}()\\` disables ` +\n `the compiler's template-clone optimization (the child count is no longer ` +\n `statically known). For dynamic child counts, use \\`each({ items, key, render })\\` ` +\n `instead — it gets per-row scope, key-based reconciliation, and reactive bindings.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, el.getStart(sf), el.getEnd()),\n },\n })\n break\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 stateMutationModule(): CompilerModule;
3
+ //# sourceMappingURL=state-mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-mutation.d.ts","sourceRoot":"","sources":["../../src/modules/state-mutation.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAqClD,wBAAgB,mBAAmB,IAAI,cAAc,CA8GpD"}
@@ -0,0 +1,138 @@
1
+ // `state-mutation` — errors on direct mutation of the state parameter
2
+ // inside a component's update function. State is immutable; the
3
+ // runtime compares old/new state by reference to compute the dirty
4
+ // mask. Direct mutation breaks that invariant and silently disables
5
+ // bitmask gating. Migrated from
6
+ // `@llui/eslint-plugin/src/rules/state-mutation.ts`.
7
+ import ts from 'typescript';
8
+ import { rangeFromOffsets } from '../diagnostic.js';
9
+ import { findComponentCalls } from './_shared.js';
10
+ const MUTATING_METHODS = new Set([
11
+ 'push',
12
+ 'pop',
13
+ 'shift',
14
+ 'unshift',
15
+ 'splice',
16
+ 'sort',
17
+ 'reverse',
18
+ 'fill',
19
+ ]);
20
+ function findUpdateProperty(call) {
21
+ const arg = call.arguments[0];
22
+ if (!arg || !ts.isObjectLiteralExpression(arg))
23
+ return undefined;
24
+ for (const prop of arg.properties) {
25
+ if (ts.isPropertyAssignment(prop) &&
26
+ ts.isIdentifier(prop.name) &&
27
+ prop.name.text === 'update') {
28
+ return prop;
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ /** True when `expr` is a member access rooted at the state binding name. */
34
+ function isStateMemberAccess(expr, stateName) {
35
+ if (!ts.isPropertyAccessExpression(expr) && !ts.isElementAccessExpression(expr))
36
+ return false;
37
+ const root = expr.expression;
38
+ if (ts.isIdentifier(root) && root.text === stateName)
39
+ return true;
40
+ return isStateMemberAccess(root, stateName);
41
+ }
42
+ export function stateMutationModule() {
43
+ return {
44
+ name: 'state-mutation',
45
+ compilerVersion: '^0.3.0',
46
+ diagnostics: [
47
+ {
48
+ id: 'llui/state-mutation',
49
+ description: 'Direct mutation of state in update() — state is immutable, return `{ ...state, field: value }`.',
50
+ },
51
+ ],
52
+ visitors: {
53
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
54
+ const visited = node;
55
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
56
+ for (const call of findComponentCalls(sf)) {
57
+ const updateProp = findUpdateProperty(call);
58
+ if (!updateProp)
59
+ continue;
60
+ const fn = updateProp.initializer;
61
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
62
+ continue;
63
+ const stateParam = fn.parameters[0];
64
+ if (!stateParam || !ts.isIdentifier(stateParam.name))
65
+ continue;
66
+ const stateName = stateParam.name.text;
67
+ const report = (n, msg) => {
68
+ ctx.reportDiagnostic({
69
+ id: 'llui/state-mutation',
70
+ severity: 'error',
71
+ category: 'reactivity',
72
+ message: msg,
73
+ location: {
74
+ file: sf.fileName,
75
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
76
+ },
77
+ });
78
+ };
79
+ const walk = (n) => {
80
+ // Don't descend into nested function expressions — those are
81
+ // deferred callbacks, not the synchronous update body.
82
+ if ((ts.isArrowFunction(n) ||
83
+ ts.isFunctionExpression(n) ||
84
+ ts.isFunctionDeclaration(n)) &&
85
+ n !== fn) {
86
+ return;
87
+ }
88
+ // Assignment: state.x = ... / state.x += ...
89
+ if (ts.isBinaryExpression(n)) {
90
+ const op = n.operatorToken.kind;
91
+ const isAssign = op === ts.SyntaxKind.EqualsToken ||
92
+ op === ts.SyntaxKind.PlusEqualsToken ||
93
+ op === ts.SyntaxKind.MinusEqualsToken ||
94
+ op === ts.SyntaxKind.AsteriskEqualsToken ||
95
+ op === ts.SyntaxKind.SlashEqualsToken ||
96
+ op === ts.SyntaxKind.PercentEqualsToken ||
97
+ op === ts.SyntaxKind.AmpersandEqualsToken ||
98
+ op === ts.SyntaxKind.BarEqualsToken ||
99
+ op === ts.SyntaxKind.CaretEqualsToken ||
100
+ op === ts.SyntaxKind.LessThanLessThanEqualsToken ||
101
+ op === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
102
+ op === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken;
103
+ if (isAssign && isStateMemberAccess(n.left, stateName)) {
104
+ if (op === ts.SyntaxKind.EqualsToken) {
105
+ report(n, `Direct mutation of \`${stateName}\` in update() — state is immutable. Return a new state object: \`{ ...${stateName}, field: newValue }\`.`);
106
+ }
107
+ else {
108
+ report(n, `Compound assignment on \`${stateName}\` in update() — state is immutable. Compute the new value and return it via spread.`);
109
+ }
110
+ }
111
+ }
112
+ // ++state.x / --state.x / state.x++
113
+ if (ts.isPostfixUnaryExpression(n) || ts.isPrefixUnaryExpression(n)) {
114
+ const op = n.operator;
115
+ if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {
116
+ if (isStateMemberAccess(n.operand, stateName)) {
117
+ report(n, `Increment/decrement on \`${stateName}\` in update() — state is immutable. Compute the new value and return it via spread.`);
118
+ }
119
+ }
120
+ }
121
+ // state.arr.push(…) etc.
122
+ if (ts.isCallExpression(n) &&
123
+ ts.isPropertyAccessExpression(n.expression) &&
124
+ ts.isIdentifier(n.expression.name) &&
125
+ MUTATING_METHODS.has(n.expression.name.text) &&
126
+ isStateMemberAccess(n.expression.expression, stateName)) {
127
+ report(n, `Mutating method \`.${n.expression.name.text}()\` on a state property in update() — use the immutable form (e.g. \`[...arr, x]\` instead of \`arr.push(x)\`).`);
128
+ }
129
+ ts.forEachChild(n, walk);
130
+ };
131
+ if (fn.body)
132
+ walk(fn.body);
133
+ }
134
+ },
135
+ },
136
+ };
137
+ }
138
+ //# sourceMappingURL=state-mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-mutation.js","sourceRoot":"","sources":["../../src/modules/state-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["// `state-mutation` — errors on direct mutation of the state parameter\n// inside a component's update function. State is immutable; the\n// runtime compares old/new state by reference to compute the dirty\n// mask. Direct mutation breaks that invariant and silently disables\n// bitmask gating. Migrated from\n// `@llui/eslint-plugin/src/rules/state-mutation.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nconst MUTATING_METHODS = new Set([\n 'push',\n 'pop',\n 'shift',\n 'unshift',\n 'splice',\n 'sort',\n 'reverse',\n 'fill',\n])\n\nfunction findUpdateProperty(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 (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'update'\n ) {\n return prop\n }\n }\n return undefined\n}\n\n/** True when `expr` is a member access rooted at the state binding name. */\nfunction isStateMemberAccess(expr: ts.Node, stateName: string): boolean {\n if (!ts.isPropertyAccessExpression(expr) && !ts.isElementAccessExpression(expr)) return false\n const root = expr.expression\n if (ts.isIdentifier(root) && root.text === stateName) return true\n return isStateMemberAccess(root, stateName)\n}\n\nexport function stateMutationModule(): CompilerModule {\n return {\n name: 'state-mutation',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/state-mutation',\n description:\n 'Direct mutation of state in update() — state is immutable, return `{ ...state, field: value }`.',\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 updateProp = findUpdateProperty(call)\n if (!updateProp) continue\n const fn = updateProp.initializer\n if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) continue\n const stateParam = fn.parameters[0]\n if (!stateParam || !ts.isIdentifier(stateParam.name)) continue\n const stateName = stateParam.name.text\n\n const report = (n: ts.Node, msg: string): void => {\n ctx.reportDiagnostic({\n id: 'llui/state-mutation',\n severity: 'error',\n category: 'reactivity',\n message: msg,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n\n const walk = (n: ts.Node): void => {\n // Don't descend into nested function expressions — those are\n // deferred callbacks, not the synchronous update body.\n if (\n (ts.isArrowFunction(n) ||\n ts.isFunctionExpression(n) ||\n ts.isFunctionDeclaration(n)) &&\n n !== fn\n ) {\n return\n }\n // Assignment: state.x = ... / state.x += ...\n if (ts.isBinaryExpression(n)) {\n const op = n.operatorToken.kind\n const isAssign =\n op === ts.SyntaxKind.EqualsToken ||\n op === ts.SyntaxKind.PlusEqualsToken ||\n op === ts.SyntaxKind.MinusEqualsToken ||\n op === ts.SyntaxKind.AsteriskEqualsToken ||\n op === ts.SyntaxKind.SlashEqualsToken ||\n op === ts.SyntaxKind.PercentEqualsToken ||\n op === ts.SyntaxKind.AmpersandEqualsToken ||\n op === ts.SyntaxKind.BarEqualsToken ||\n op === ts.SyntaxKind.CaretEqualsToken ||\n op === ts.SyntaxKind.LessThanLessThanEqualsToken ||\n op === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||\n op === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken\n if (isAssign && isStateMemberAccess(n.left, stateName)) {\n if (op === ts.SyntaxKind.EqualsToken) {\n report(\n n,\n `Direct mutation of \\`${stateName}\\` in update() — state is immutable. Return a new state object: \\`{ ...${stateName}, field: newValue }\\`.`,\n )\n } else {\n report(\n n,\n `Compound assignment on \\`${stateName}\\` in update() — state is immutable. Compute the new value and return it via spread.`,\n )\n }\n }\n }\n // ++state.x / --state.x / state.x++\n if (ts.isPostfixUnaryExpression(n) || ts.isPrefixUnaryExpression(n)) {\n const op = n.operator\n if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken) {\n if (isStateMemberAccess(n.operand, stateName)) {\n report(\n n,\n `Increment/decrement on \\`${stateName}\\` in update() — state is immutable. Compute the new value and return it via spread.`,\n )\n }\n }\n }\n // state.arr.push(…) etc.\n if (\n ts.isCallExpression(n) &&\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.name) &&\n MUTATING_METHODS.has(n.expression.name.text) &&\n isStateMemberAccess(n.expression.expression, stateName)\n ) {\n report(\n n,\n `Mutating method \\`.${n.expression.name.text}()\\` on a state property in update() — use the immutable form (e.g. \\`[...arr, x]\\` instead of \\`arr.push(x)\\`).`,\n )\n }\n ts.forEachChild(n, walk)\n }\n if (fn.body) walk(fn.body)\n }\n },\n },\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ import { type StateSchema } from '../state-schema.js';
3
+ export interface StateSchemaModuleOptions {
4
+ /** Pre-computed schema (cross-file aware) — null when extraction failed. */
5
+ stateSchema: StateSchema | null;
6
+ }
7
+ export declare function stateSchemaModule(opts: StateSchemaModuleOptions): CompilerModule;
8
+ //# sourceMappingURL=state-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-schema.d.ts","sourceRoot":"","sources":["../../src/modules/state-schema.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAwB,MAAM,cAAc,CAAA;AACxE,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIzE,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,WAAW,EAAE,WAAW,GAAG,IAAI,CAAA;CAChC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,cAAc,CA0ChF"}
@@ -0,0 +1,55 @@
1
+ // `state-schema` — emits `__stateSchema` per `component()` call and
2
+ // populates the schema-hash inputs slot. Factory module: takes the
3
+ // pre-computed StateSchema (from `extractStateSchema` upstream, with
4
+ // cross-file resolution if available) and emits its literal form
5
+ // targeted at each component() call site.
6
+ //
7
+ // Migrated from the inline `injectStateSchema` in transform.ts. The
8
+ // monolith's `extractStateSchema(...)` call lives in transformLlui's
9
+ // setup section and gets passed into the factory; the module consumes
10
+ // it without re-running the extractor. This keeps the extraction
11
+ // authoritative at the umbrella level (where pre-extracted cross-file
12
+ // data + type-source overrides are already wired) and frees the module
13
+ // from re-implementing input resolution.
14
+ import { stateTypeToLiteral } from '../state-schema.js';
15
+ import { SCHEMA_HASH_INPUTS_SLOT } from './schema-hash.js';
16
+ import { findComponentCalls } from './_shared.js';
17
+ export function stateSchemaModule(opts) {
18
+ return {
19
+ name: 'state-schema',
20
+ compilerVersion: '^0.3.0',
21
+ diagnostics: [],
22
+ // Targets captured in `emit` — see `_shared.ts`.
23
+ visitors: {},
24
+ emit(ctx, analysis) {
25
+ if (!opts.stateSchema)
26
+ return [];
27
+ const calls = findComponentCalls(analysis.sourceFile);
28
+ if (calls.length === 0)
29
+ return [];
30
+ // Populate the schema-hash inputs slot. The schema-hash module
31
+ // reads this on its own emit pass and computes the hash. Module
32
+ // ordering is observable per v2c §2.1: when `stateSchemaModule`
33
+ // appears in the active module list before `schemaHashModule`,
34
+ // the inputs slot is populated when schema-hash's emit runs.
35
+ const hashInputs = analysis.perModule.get(SCHEMA_HASH_INPUTS_SLOT);
36
+ if (hashInputs) {
37
+ hashInputs.stateSchema = opts.stateSchema;
38
+ }
39
+ else {
40
+ analysis.perModule.set(SCHEMA_HASH_INPUTS_SLOT, {
41
+ msgSchema: null,
42
+ stateSchema: opts.stateSchema,
43
+ msgAnnotations: null,
44
+ });
45
+ }
46
+ return calls.map((call) => ({
47
+ module: 'state-schema',
48
+ field: '__stateSchema',
49
+ value: stateTypeToLiteral({ kind: 'object', fields: opts.stateSchema.fields }, ctx.factory),
50
+ target: call,
51
+ }));
52
+ },
53
+ };
54
+ }
55
+ //# sourceMappingURL=state-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-schema.js","sourceRoot":"","sources":["../../src/modules/state-schema.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mEAAmE;AACnE,qEAAqE;AACrE,iEAAiE;AACjE,0CAA0C;AAC1C,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,sEAAsE;AACtE,iEAAiE;AACjE,sEAAsE;AACtE,uEAAuE;AACvE,yCAAyC;AAGzC,OAAO,EAAE,kBAAkB,EAAoB,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,uBAAuB,EAAyB,MAAM,kBAAkB,CAAA;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAOjD,MAAM,UAAU,iBAAiB,CAAC,IAA8B;IAC9D,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE,EAAE;QACf,iDAAiD;QACjD,QAAQ,EAAE,EAAE;QAEZ,IAAI,CAAC,GAAG,EAAE,QAAQ;YAChB,IAAI,CAAC,IAAI,CAAC,WAAW;gBAAE,OAAO,EAAE,CAAA;YAChC,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;YACrD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAA;YAEjC,+DAA+D;YAC/D,gEAAgE;YAChE,gEAAgE;YAChE,+DAA+D;YAC/D,6DAA6D;YAC7D,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,uBAAuB,CAEpD,CAAA;YACb,IAAI,UAAU,EAAE,CAAC;gBACf,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;YAC3C,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,uBAAuB,EAAE;oBAC9C,SAAS,EAAE,IAAI;oBACf,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,cAAc,EAAE,IAAI;iBACD,CAAC,CAAA;YACxB,CAAC;YAED,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1B,MAAM,EAAE,cAAc;gBACtB,KAAK,EAAE,eAAe;gBACtB,KAAK,EAAE,kBAAkB,CACvB,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,WAAY,CAAC,MAAM,EAAE,EACpD,GAAG,CAAC,OAAO,CACZ;gBACD,MAAM,EAAE,IAAI;aACb,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["// `state-schema` — emits `__stateSchema` per `component()` call and\n// populates the schema-hash inputs slot. Factory module: takes the\n// pre-computed StateSchema (from `extractStateSchema` upstream, with\n// cross-file resolution if available) and emits its literal form\n// targeted at each component() call site.\n//\n// Migrated from the inline `injectStateSchema` in transform.ts. The\n// monolith's `extractStateSchema(...)` call lives in transformLlui's\n// setup section and gets passed into the factory; the module consumes\n// it without re-running the extractor. This keeps the extraction\n// authoritative at the umbrella level (where pre-extracted cross-file\n// data + type-source overrides are already wired) and frees the module\n// from re-implementing input resolution.\n\nimport type { CompilerModule, EmissionContribution } from '../module.js'\nimport { stateTypeToLiteral, type StateSchema } from '../state-schema.js'\nimport { SCHEMA_HASH_INPUTS_SLOT, type SchemaHashInputs } from './schema-hash.js'\nimport { findComponentCalls } from './_shared.js'\n\nexport interface StateSchemaModuleOptions {\n /** Pre-computed schema (cross-file aware) — null when extraction failed. */\n stateSchema: StateSchema | null\n}\n\nexport function stateSchemaModule(opts: StateSchemaModuleOptions): CompilerModule {\n return {\n name: 'state-schema',\n compilerVersion: '^0.3.0',\n diagnostics: [],\n // Targets captured in `emit` — see `_shared.ts`.\n visitors: {},\n\n emit(ctx, analysis): EmissionContribution[] {\n if (!opts.stateSchema) return []\n const calls = findComponentCalls(analysis.sourceFile)\n if (calls.length === 0) return []\n\n // Populate the schema-hash inputs slot. The schema-hash module\n // reads this on its own emit pass and computes the hash. Module\n // ordering is observable per v2c §2.1: when `stateSchemaModule`\n // appears in the active module list before `schemaHashModule`,\n // the inputs slot is populated when schema-hash's emit runs.\n const hashInputs = analysis.perModule.get(SCHEMA_HASH_INPUTS_SLOT) as\n | SchemaHashInputs\n | undefined\n if (hashInputs) {\n hashInputs.stateSchema = opts.stateSchema\n } else {\n analysis.perModule.set(SCHEMA_HASH_INPUTS_SLOT, {\n msgSchema: null,\n stateSchema: opts.stateSchema,\n msgAnnotations: null,\n } as SchemaHashInputs)\n }\n\n return calls.map((call) => ({\n module: 'state-schema',\n field: '__stateSchema',\n value: stateTypeToLiteral(\n { kind: 'object', fields: opts.stateSchema!.fields },\n ctx.factory,\n ),\n target: call,\n }))\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function staticItemsModule(): CompilerModule;
3
+ //# sourceMappingURL=static-items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-items.d.ts","sourceRoot":"","sources":["../../src/modules/static-items.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAuClD,wBAAgB,iBAAiB,IAAI,cAAc,CAoFlD"}
@@ -0,0 +1,125 @@
1
+ // `static-items` — errors when `each({ items })` receives a factory that
2
+ // doesn't read state. Without state reads, the items list is computed
3
+ // once at mount and `each` never reconciles — adds/removes/updates are
4
+ // invisible. Migrated from
5
+ // `@llui/eslint-plugin/src/rules/static-items.ts`.
6
+ import ts from 'typescript';
7
+ import { rangeFromOffsets } from '../diagnostic.js';
8
+ function bodyMayRead(body) {
9
+ let found = false;
10
+ const walk = (n) => {
11
+ if (found)
12
+ return;
13
+ if (ts.isCallExpression(n) ||
14
+ ts.isPropertyAccessExpression(n) ||
15
+ ts.isElementAccessExpression(n)) {
16
+ found = true;
17
+ return;
18
+ }
19
+ ts.forEachChild(n, walk);
20
+ };
21
+ walk(body);
22
+ return found;
23
+ }
24
+ function readsParam(body, paramName) {
25
+ let found = false;
26
+ const walk = (n) => {
27
+ if (found)
28
+ return;
29
+ if (ts.isIdentifier(n) && n.text === paramName) {
30
+ const parent = n.parent;
31
+ // Don't count the parameter declaration itself.
32
+ if (parent && ts.isParameter(parent) && parent.name === n) {
33
+ return;
34
+ }
35
+ found = true;
36
+ return;
37
+ }
38
+ ts.forEachChild(n, walk);
39
+ };
40
+ walk(body);
41
+ return found;
42
+ }
43
+ export function staticItemsModule() {
44
+ return {
45
+ name: 'static-items',
46
+ compilerVersion: '^0.3.0',
47
+ diagnostics: [
48
+ {
49
+ id: 'llui/static-items',
50
+ description: '`each({ items })` factory reads no state — list never reconciles.',
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
+ const walk = (n) => {
58
+ if (ts.isCallExpression(n)) {
59
+ let isEach = false;
60
+ if (ts.isIdentifier(n.expression) && n.expression.text === 'each')
61
+ isEach = true;
62
+ else if (ts.isPropertyAccessExpression(n.expression) &&
63
+ ts.isIdentifier(n.expression.name) &&
64
+ n.expression.name.text === 'each')
65
+ isEach = true;
66
+ if (isEach) {
67
+ const opts = n.arguments[0];
68
+ if (opts && ts.isObjectLiteralExpression(opts)) {
69
+ const itemsProp = opts.properties.find((p) => ts.isPropertyAssignment(p) &&
70
+ ts.isIdentifier(p.name) &&
71
+ p.name.text === 'items');
72
+ if (itemsProp) {
73
+ const v = itemsProp.initializer;
74
+ if (ts.isArrowFunction(v) || ts.isFunctionExpression(v)) {
75
+ let isStatic = false;
76
+ if (v.parameters.length === 0) {
77
+ // Zero-param zero-arg `() => Foo` where Foo is a
78
+ // bare identifier is an explicit opt-in to a
79
+ // known-static list (the author named a const
80
+ // and pointed `items` at it). The footgun pattern
81
+ // is `() => [1,2,3]` / `() => {…}` (anonymous
82
+ // literal); the named-const case is a legitimate
83
+ // pattern and shouldn't fire.
84
+ const body = v.body;
85
+ if (body && ts.isIdentifier(body)) {
86
+ isStatic = false;
87
+ }
88
+ else {
89
+ isStatic = body ? !bodyMayRead(body) : true;
90
+ }
91
+ }
92
+ else if (v.parameters.length === 1) {
93
+ const param = v.parameters[0];
94
+ if (ts.isIdentifier(param.name)) {
95
+ isStatic = v.body ? !readsParam(v.body, param.name.text) : true;
96
+ }
97
+ }
98
+ if (isStatic) {
99
+ ctx.reportDiagnostic({
100
+ id: 'llui/static-items',
101
+ severity: 'error',
102
+ category: 'reactivity',
103
+ message: `\`each()\`'s \`items\` factory reads no state — the list is computed ` +
104
+ `once at mount and \`each\` never reconciles. Adds, removes, and updates ` +
105
+ `to items will never appear in the DOM. Reference the state field that ` +
106
+ `holds the list, e.g. \`items: (s) => s.list.items\`.`,
107
+ location: {
108
+ file: sf.fileName,
109
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
110
+ },
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ts.forEachChild(n, walk);
119
+ };
120
+ walk(sf);
121
+ },
122
+ },
123
+ };
124
+ }
125
+ //# sourceMappingURL=static-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-items.js","sourceRoot":"","sources":["../../src/modules/static-items.ts"],"names":[],"mappings":"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","sourcesContent":["// `static-items` — errors when `each({ items })` receives a factory that\n// doesn't read state. Without state reads, the items list is computed\n// once at mount and `each` never reconciles — adds/removes/updates are\n// invisible. Migrated from\n// `@llui/eslint-plugin/src/rules/static-items.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\n\nfunction bodyMayRead(body: ts.Node): boolean {\n let found = false\n const walk = (n: ts.Node): void => {\n if (found) return\n if (\n ts.isCallExpression(n) ||\n ts.isPropertyAccessExpression(n) ||\n ts.isElementAccessExpression(n)\n ) {\n found = true\n return\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return found\n}\n\nfunction readsParam(body: ts.Node, paramName: string): boolean {\n let found = false\n const walk = (n: ts.Node): void => {\n if (found) return\n if (ts.isIdentifier(n) && n.text === paramName) {\n const parent = n.parent\n // Don't count the parameter declaration itself.\n if (parent && ts.isParameter(parent) && parent.name === n) {\n return\n }\n found = true\n return\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return found\n}\n\nexport function staticItemsModule(): CompilerModule {\n return {\n name: 'static-items',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/static-items',\n description: '`each({ items })` factory reads no state — list never reconciles.',\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 (ts.isCallExpression(n)) {\n let isEach = false\n if (ts.isIdentifier(n.expression) && n.expression.text === 'each') isEach = true\n else if (\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.name) &&\n n.expression.name.text === 'each'\n )\n isEach = true\n if (isEach) {\n const opts = n.arguments[0]\n if (opts && ts.isObjectLiteralExpression(opts)) {\n const itemsProp = opts.properties.find(\n (p): p is ts.PropertyAssignment =>\n ts.isPropertyAssignment(p) &&\n ts.isIdentifier(p.name) &&\n p.name.text === 'items',\n )\n if (itemsProp) {\n const v = itemsProp.initializer\n if (ts.isArrowFunction(v) || ts.isFunctionExpression(v)) {\n let isStatic = false\n if (v.parameters.length === 0) {\n // Zero-param zero-arg `() => Foo` where Foo is a\n // bare identifier is an explicit opt-in to a\n // known-static list (the author named a const\n // and pointed `items` at it). The footgun pattern\n // is `() => [1,2,3]` / `() => {…}` (anonymous\n // literal); the named-const case is a legitimate\n // pattern and shouldn't fire.\n const body = v.body\n if (body && ts.isIdentifier(body)) {\n isStatic = false\n } else {\n isStatic = body ? !bodyMayRead(body) : true\n }\n } else if (v.parameters.length === 1) {\n const param = v.parameters[0]!\n if (ts.isIdentifier(param.name)) {\n isStatic = v.body ? !readsParam(v.body, param.name.text) : true\n }\n }\n if (isStatic) {\n ctx.reportDiagnostic({\n id: 'llui/static-items',\n severity: 'error',\n category: 'reactivity',\n message:\n `\\`each()\\`'s \\`items\\` factory reads no state — the list is computed ` +\n `once at mount and \\`each\\` never reconciles. Adds, removes, and updates ` +\n `to items will never appear in the DOM. Reference the state field that ` +\n `holds the list, e.g. \\`items: (s) => s.list.items\\`.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\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 staticOnModule(): CompilerModule;
3
+ //# sourceMappingURL=static-on.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-on.d.ts","sourceRoot":"","sources":["../../src/modules/static-on.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAoClD,wBAAgB,cAAc,IAAI,cAAc,CAgE/C"}