@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,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function formBoilerplateModule(): CompilerModule;
3
+ //# sourceMappingURL=form-boilerplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-boilerplate.d.ts","sourceRoot":"","sources":["../../src/modules/form-boilerplate.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAoClD,wBAAgB,qBAAqB,IAAI,cAAc,CAwDtD"}
@@ -0,0 +1,101 @@
1
+ // `form-boilerplate` — errors when a file's `type Msg = ...` union has
2
+ // 3+ variants with identical shape (a `value:` field of the same type,
3
+ // plus a `set*`/`update*`/`change*` discriminant prefix). Suggests a
4
+ // generic field-update pattern instead. Migrated from
5
+ // `@llui/eslint-plugin/src/rules/form-boilerplate.ts`.
6
+ import ts from 'typescript';
7
+ import { rangeFromOffsets } from '../diagnostic.js';
8
+ function collectMsgVariantShapes(typeNode, sf) {
9
+ const out = [];
10
+ if (ts.isUnionTypeNode(typeNode)) {
11
+ for (const m of typeNode.types)
12
+ out.push(...collectMsgVariantShapes(m, sf));
13
+ return out;
14
+ }
15
+ if (!ts.isTypeLiteralNode(typeNode))
16
+ return out;
17
+ let typeName = '';
18
+ const fields = [];
19
+ for (const m of typeNode.members) {
20
+ if (!ts.isPropertySignature(m) || !m.name || !ts.isIdentifier(m.name))
21
+ continue;
22
+ const fieldName = m.name.text;
23
+ const fieldType = m.type ? m.type.getText(sf) : 'unknown';
24
+ if (fieldName === 'type') {
25
+ if (m.type && ts.isLiteralTypeNode(m.type)) {
26
+ const lit = m.type.literal;
27
+ if (ts.isStringLiteral(lit))
28
+ typeName = lit.text;
29
+ }
30
+ }
31
+ else {
32
+ fields.push(`${fieldName}:${fieldType}`);
33
+ }
34
+ }
35
+ if (typeName && fields.length > 0) {
36
+ fields.sort();
37
+ out.push({ typeName, shape: fields.join(',') });
38
+ }
39
+ return out;
40
+ }
41
+ export function formBoilerplateModule() {
42
+ return {
43
+ name: 'form-boilerplate',
44
+ compilerVersion: '^0.3.0',
45
+ diagnostics: [
46
+ {
47
+ id: 'llui/form-boilerplate',
48
+ description: 'Msg union has 3+ set*/update*/change* variants with identical shape — use a generic field-update message.',
49
+ },
50
+ ],
51
+ visitors: {
52
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
53
+ const visited = node;
54
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
55
+ for (const stmt of sf.statements) {
56
+ if (!ts.isTypeAliasDeclaration(stmt))
57
+ continue;
58
+ if (stmt.name.text !== 'Msg')
59
+ continue;
60
+ const variants = collectMsgVariantShapes(stmt.type, sf);
61
+ if (variants.length < 3)
62
+ continue;
63
+ const shapeGroups = new Map();
64
+ for (const v of variants) {
65
+ const g = shapeGroups.get(v.shape) ?? [];
66
+ g.push(v.typeName);
67
+ shapeGroups.set(v.shape, g);
68
+ }
69
+ for (const [shape, group] of shapeGroups) {
70
+ if (group.length < 3)
71
+ continue;
72
+ const hasValueField = shape.split(',').some((f) => f.startsWith('value:'));
73
+ if (!hasValueField)
74
+ continue;
75
+ const prefixPattern = /^(set|update|change)[A-Z]/;
76
+ if (!group.every((name) => prefixPattern.test(name)))
77
+ continue;
78
+ const groupStr = group
79
+ .slice(0, 3)
80
+ .map((g) => `'${g}'`)
81
+ .join(', ') + (group.length > 3 ? ', ...' : '');
82
+ ctx.reportDiagnostic({
83
+ id: 'llui/form-boilerplate',
84
+ severity: 'error',
85
+ category: 'style',
86
+ message: `Msg union has ${group.length} variants with identical shapes (${groupStr}). ` +
87
+ `Consider a generic field-update pattern: a single \`{ type: 'fieldChanged', ` +
88
+ `field: 'fieldName', value: T }\` variant, with the discriminant on \`field\`. ` +
89
+ `Reduces switch-case boilerplate and makes the update() function shorter.`,
90
+ location: {
91
+ file: sf.fileName,
92
+ range: rangeFromOffsets(sf.text, stmt.getStart(sf), stmt.getEnd()),
93
+ },
94
+ });
95
+ }
96
+ }
97
+ },
98
+ },
99
+ };
100
+ }
101
+ //# sourceMappingURL=form-boilerplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-boilerplate.js","sourceRoot":"","sources":["../../src/modules/form-boilerplate.ts"],"names":[],"mappings":"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","sourcesContent":["// `form-boilerplate` — errors when a file's `type Msg = ...` union has\n// 3+ variants with identical shape (a `value:` field of the same type,\n// plus a `set*`/`update*`/`change*` discriminant prefix). Suggests a\n// generic field-update pattern instead. Migrated from\n// `@llui/eslint-plugin/src/rules/form-boilerplate.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\n\ninterface VariantShape {\n typeName: string\n shape: string\n}\n\nfunction collectMsgVariantShapes(typeNode: ts.TypeNode, sf: ts.SourceFile): VariantShape[] {\n const out: VariantShape[] = []\n if (ts.isUnionTypeNode(typeNode)) {\n for (const m of typeNode.types) out.push(...collectMsgVariantShapes(m, sf))\n return out\n }\n if (!ts.isTypeLiteralNode(typeNode)) return out\n let typeName = ''\n const fields: string[] = []\n for (const m of typeNode.members) {\n if (!ts.isPropertySignature(m) || !m.name || !ts.isIdentifier(m.name)) continue\n const fieldName = m.name.text\n const fieldType = m.type ? m.type.getText(sf) : 'unknown'\n if (fieldName === 'type') {\n if (m.type && ts.isLiteralTypeNode(m.type)) {\n const lit = m.type.literal\n if (ts.isStringLiteral(lit)) typeName = lit.text\n }\n } else {\n fields.push(`${fieldName}:${fieldType}`)\n }\n }\n if (typeName && fields.length > 0) {\n fields.sort()\n out.push({ typeName, shape: fields.join(',') })\n }\n return out\n}\n\nexport function formBoilerplateModule(): CompilerModule {\n return {\n name: 'form-boilerplate',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/form-boilerplate',\n description:\n 'Msg union has 3+ set*/update*/change* variants with identical shape — use a generic field-update message.',\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 stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== 'Msg') continue\n const variants = collectMsgVariantShapes(stmt.type, sf)\n if (variants.length < 3) continue\n const shapeGroups = new Map<string, string[]>()\n for (const v of variants) {\n const g = shapeGroups.get(v.shape) ?? []\n g.push(v.typeName)\n shapeGroups.set(v.shape, g)\n }\n for (const [shape, group] of shapeGroups) {\n if (group.length < 3) continue\n const hasValueField = shape.split(',').some((f) => f.startsWith('value:'))\n if (!hasValueField) continue\n const prefixPattern = /^(set|update|change)[A-Z]/\n if (!group.every((name) => prefixPattern.test(name))) continue\n const groupStr =\n group\n .slice(0, 3)\n .map((g) => `'${g}'`)\n .join(', ') + (group.length > 3 ? ', ...' : '')\n ctx.reportDiagnostic({\n id: 'llui/form-boilerplate',\n severity: 'error',\n category: 'style',\n message:\n `Msg union has ${group.length} variants with identical shapes (${groupStr}). ` +\n `Consider a generic field-update pattern: a single \\`{ type: 'fieldChanged', ` +\n `field: 'fieldName', value: T }\\` variant, with the discriminant on \\`field\\`. ` +\n `Reduces switch-case boilerplate and makes the update() function shorter.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, stmt.getStart(sf), stmt.getEnd()),\n },\n })\n }\n }\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function imperativeDomInViewModule(): CompilerModule;
3
+ //# sourceMappingURL=imperative-dom-in-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imperative-dom-in-view.d.ts","sourceRoot":"","sources":["../../src/modules/imperative-dom-in-view.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA2DlD,wBAAgB,yBAAyB,IAAI,cAAc,CA6D1D"}
@@ -0,0 +1,123 @@
1
+ // `imperative-dom-in-view` — errors on `document.querySelector` and
2
+ // friends inside view() unless wrapped in `onMount()` or a deferred
3
+ // callback (event handler, setTimeout, addEventListener, promise
4
+ // chain). Imperative DOM reads in view() execute eagerly at
5
+ // view-build time and miss every subsequent reactive update — they
6
+ // silently break the framework's reactive model. Migrated from
7
+ // `@llui/eslint-plugin/src/rules/imperative-dom-in-view.ts`.
8
+ import ts from 'typescript';
9
+ import { rangeFromOffsets } from '../diagnostic.js';
10
+ import { findComponentCalls } from './_shared.js';
11
+ const IMPERATIVE_DOM_METHODS = new Set([
12
+ 'querySelector',
13
+ 'querySelectorAll',
14
+ 'getElementById',
15
+ 'getElementsByClassName',
16
+ 'getElementsByTagName',
17
+ ]);
18
+ const DEFERRED_TIMER_CALLEES = new Set([
19
+ 'setTimeout',
20
+ 'setInterval',
21
+ 'queueMicrotask',
22
+ 'requestAnimationFrame',
23
+ 'requestIdleCallback',
24
+ ]);
25
+ const DEFERRED_METHOD_CALLEES = new Set(['addEventListener', 'then', 'catch', 'finally']);
26
+ function findViewProperty(call) {
27
+ const arg = call.arguments[0];
28
+ if (!arg || !ts.isObjectLiteralExpression(arg))
29
+ return undefined;
30
+ for (const prop of arg.properties) {
31
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'view') {
32
+ return prop;
33
+ }
34
+ }
35
+ return undefined;
36
+ }
37
+ /**
38
+ * True when `fn`'s immediate parent context is a deferred boundary —
39
+ * an event handler, a timer callback, an `addEventListener` / promise
40
+ * `.then` argument. Inside any of those, imperative DOM is fine
41
+ * (it runs after the view has mounted).
42
+ */
43
+ function isDeferredCallback(fn) {
44
+ const parent = fn.parent;
45
+ if (!parent)
46
+ return false;
47
+ if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
48
+ if (/^on[A-Z]/.test(parent.name.text))
49
+ return true;
50
+ }
51
+ if (ts.isCallExpression(parent)) {
52
+ if (ts.isIdentifier(parent.expression) && DEFERRED_TIMER_CALLEES.has(parent.expression.text)) {
53
+ return true;
54
+ }
55
+ if (ts.isPropertyAccessExpression(parent.expression) &&
56
+ ts.isIdentifier(parent.expression.name) &&
57
+ DEFERRED_METHOD_CALLEES.has(parent.expression.name.text)) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ export function imperativeDomInViewModule() {
64
+ return {
65
+ name: 'imperative-dom-in-view',
66
+ compilerVersion: '^0.3.0',
67
+ diagnostics: [
68
+ {
69
+ id: 'llui/imperative-dom-in-view',
70
+ description: 'Imperative DOM access (document.querySelector, getElementById, etc.) in view() — use LLui primitives or onMount.',
71
+ },
72
+ ],
73
+ visitors: {
74
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
75
+ const visited = node;
76
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
77
+ for (const call of findComponentCalls(sf)) {
78
+ const viewProp = findViewProperty(call);
79
+ if (!viewProp)
80
+ continue;
81
+ const fn = viewProp.initializer;
82
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
83
+ continue;
84
+ const walk = (n, inOnMount, inDeferred) => {
85
+ let nextOnMount = inOnMount;
86
+ let nextDeferred = inDeferred;
87
+ if (ts.isCallExpression(n) &&
88
+ ts.isIdentifier(n.expression) &&
89
+ n.expression.text === 'onMount') {
90
+ nextOnMount = true;
91
+ }
92
+ if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {
93
+ if (isDeferredCallback(n))
94
+ nextDeferred = true;
95
+ }
96
+ if (ts.isPropertyAccessExpression(n) &&
97
+ ts.isIdentifier(n.expression) &&
98
+ n.expression.text === 'document' &&
99
+ ts.isIdentifier(n.name) &&
100
+ IMPERATIVE_DOM_METHODS.has(n.name.text) &&
101
+ !nextOnMount &&
102
+ !nextDeferred) {
103
+ ctx.reportDiagnostic({
104
+ id: 'llui/imperative-dom-in-view',
105
+ severity: 'error',
106
+ category: 'reactivity',
107
+ message: `Imperative DOM access (\`document.${n.name.text}\`) in view() runs once at view-build time and misses every subsequent update — the result is not reactive. Use LLui primitives (\`text\`, \`show\`, \`branch\`, \`each\`) for reactive rendering, or wrap in \`onMount(() => { … })\` if you genuinely need imperative DOM on mount.`,
108
+ location: {
109
+ file: sf.fileName,
110
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
111
+ },
112
+ });
113
+ }
114
+ ts.forEachChild(n, (c) => walk(c, nextOnMount, nextDeferred));
115
+ };
116
+ if (fn.body)
117
+ walk(fn.body, false, false);
118
+ }
119
+ },
120
+ },
121
+ };
122
+ }
123
+ //# sourceMappingURL=imperative-dom-in-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imperative-dom-in-view.js","sourceRoot":"","sources":["../../src/modules/imperative-dom-in-view.ts"],"names":[],"mappings":"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","sourcesContent":["// `imperative-dom-in-view` — errors on `document.querySelector` and\n// friends inside view() unless wrapped in `onMount()` or a deferred\n// callback (event handler, setTimeout, addEventListener, promise\n// chain). Imperative DOM reads in view() execute eagerly at\n// view-build time and miss every subsequent reactive update — they\n// silently break the framework's reactive model. Migrated from\n// `@llui/eslint-plugin/src/rules/imperative-dom-in-view.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { findComponentCalls } from './_shared.js'\n\nconst IMPERATIVE_DOM_METHODS = new Set([\n 'querySelector',\n 'querySelectorAll',\n 'getElementById',\n 'getElementsByClassName',\n 'getElementsByTagName',\n])\n\nconst DEFERRED_TIMER_CALLEES = new Set([\n 'setTimeout',\n 'setInterval',\n 'queueMicrotask',\n 'requestAnimationFrame',\n 'requestIdleCallback',\n])\n\nconst DEFERRED_METHOD_CALLEES = new Set(['addEventListener', 'then', 'catch', 'finally'])\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`'s immediate parent context is a deferred boundary —\n * an event handler, a timer callback, an `addEventListener` / promise\n * `.then` argument. Inside any of those, imperative DOM is fine\n * (it runs after the view has mounted).\n */\nfunction isDeferredCallback(fn: ts.ArrowFunction | ts.FunctionExpression): boolean {\n const parent = fn.parent\n if (!parent) return false\n if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {\n if (/^on[A-Z]/.test(parent.name.text)) return true\n }\n if (ts.isCallExpression(parent)) {\n if (ts.isIdentifier(parent.expression) && DEFERRED_TIMER_CALLEES.has(parent.expression.text)) {\n return true\n }\n if (\n ts.isPropertyAccessExpression(parent.expression) &&\n ts.isIdentifier(parent.expression.name) &&\n DEFERRED_METHOD_CALLEES.has(parent.expression.name.text)\n ) {\n return true\n }\n }\n return false\n}\n\nexport function imperativeDomInViewModule(): CompilerModule {\n return {\n name: 'imperative-dom-in-view',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/imperative-dom-in-view',\n description:\n 'Imperative DOM access (document.querySelector, getElementById, etc.) in view() — use LLui primitives or onMount.',\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, inOnMount: boolean, inDeferred: boolean): void => {\n let nextOnMount = inOnMount\n let nextDeferred = inDeferred\n if (\n ts.isCallExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === 'onMount'\n ) {\n nextOnMount = true\n }\n if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {\n if (isDeferredCallback(n)) nextDeferred = true\n }\n if (\n ts.isPropertyAccessExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === 'document' &&\n ts.isIdentifier(n.name) &&\n IMPERATIVE_DOM_METHODS.has(n.name.text) &&\n !nextOnMount &&\n !nextDeferred\n ) {\n ctx.reportDiagnostic({\n id: 'llui/imperative-dom-in-view',\n severity: 'error',\n category: 'reactivity',\n message: `Imperative DOM access (\\`document.${n.name.text}\\`) in view() runs once at view-build time and misses every subsequent update — the result is not reactive. Use LLui primitives (\\`text\\`, \\`show\\`, \\`branch\\`, \\`each\\`) for reactive rendering, or wrap in \\`onMount(() => { … })\\` if you genuinely need imperative DOM on mount.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),\n },\n })\n }\n ts.forEachChild(n, (c) => walk(c, nextOnMount, nextDeferred))\n }\n if (fn.body) walk(fn.body, false, false)\n }\n },\n },\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export interface ItemDedupModuleOptions {
3
+ viewHelperNames: Set<string>;
4
+ viewHelperAliases: Map<string, string>;
5
+ }
6
+ export declare function itemDedupModule(options: ItemDedupModuleOptions): CompilerModule;
7
+ //# sourceMappingURL=item-dedup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-dedup.d.ts","sourceRoot":"","sources":["../../src/modules/item-dedup.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlD,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,cAAc,CAgP/E"}
@@ -0,0 +1,204 @@
1
+ // `item-dedup` — deduplicates repeated `item(selector)` calls AND
2
+ // `item.FIELD` property accesses inside an each() render callback.
3
+ //
4
+ // Before: `item((r) => r.id)` and `item.id` mixed across the render
5
+ // body produce a fresh selector closure / Proxy.get trap on every
6
+ // invocation, fragmenting the V8 inline cache. Worse, repeated
7
+ // `item(...)` calls each allocate a new accessor closure.
8
+ //
9
+ // After: every distinct accessor target (keyed by the simple field
10
+ // name when extractable, else by the printed expression text) is
11
+ // lifted into a single `const __sN = (r) => r.FIELD` selector plus
12
+ // a single `const __aN = acc(__sN)` accessor at the top of the
13
+ // render body. Every occurrence then resolves to a plain identifier
14
+ // reference (`__aN`), and the runtime evaluates them via the
15
+ // non-Proxy `acc()` helper.
16
+ //
17
+ // Fires top-down (`transformCallEnter`) on `each()` so subsequent
18
+ // element-rewrite passes see the hoisted form — important because
19
+ // element rewrites inline accessor argument references, and the
20
+ // hoisted `__aN` is a stable identifier they can compile correctly.
21
+ //
22
+ // Gated on the `each` helper (alias-aware via `isHelperCall`). The
23
+ // module exits early when fewer than 2 occurrences exist; no point
24
+ // hoisting a single access.
25
+ import ts from 'typescript';
26
+ import { isHelperCall } from '../transform.js';
27
+ export function itemDedupModule(options) {
28
+ const { viewHelperNames, viewHelperAliases } = options;
29
+ // Printer is per-module — created lazily on first match, then reused
30
+ // across calls. The selector-expression key uses the printer's output
31
+ // as a fallback when `extractSimpleField` can't reduce to a plain
32
+ // field name.
33
+ let printer = null;
34
+ return {
35
+ name: 'item-dedup',
36
+ compilerVersion: '^0.3.0',
37
+ diagnostics: [],
38
+ visitors: {},
39
+ transformCallEnter(ctx, node) {
40
+ if (!isHelperCall(node.expression, 'each', viewHelperNames, viewHelperAliases))
41
+ return null;
42
+ const f = ctx.factory;
43
+ const sf = ctx.analysis.sourceFile;
44
+ if (!printer)
45
+ printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
46
+ const arg = node.arguments[0];
47
+ if (!arg || !ts.isObjectLiteralExpression(arg))
48
+ return null;
49
+ let renderProp = null;
50
+ for (const prop of arg.properties) {
51
+ if (ts.isPropertyAssignment(prop) &&
52
+ ts.isIdentifier(prop.name) &&
53
+ prop.name.text === 'render') {
54
+ renderProp = prop;
55
+ break;
56
+ }
57
+ }
58
+ if (!renderProp)
59
+ return null;
60
+ const renderFn = renderProp.initializer;
61
+ if (!ts.isArrowFunction(renderFn) && !ts.isFunctionExpression(renderFn))
62
+ return null;
63
+ const renderParam = renderFn.parameters[0];
64
+ if (!renderParam)
65
+ return null;
66
+ let itemName = null;
67
+ if (ts.isIdentifier(renderParam.name)) {
68
+ itemName = renderParam.name.text;
69
+ }
70
+ else if (ts.isObjectBindingPattern(renderParam.name)) {
71
+ for (const el of renderParam.name.elements) {
72
+ if (ts.isBindingElement(el) && ts.isIdentifier(el.name) && el.name.text === 'item') {
73
+ itemName = 'item';
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ if (!itemName)
79
+ return null;
80
+ const occurrences = [];
81
+ function extractSimpleField(sel) {
82
+ if (sel.parameters.length !== 1)
83
+ return null;
84
+ const paramName = sel.parameters[0].name;
85
+ if (!ts.isIdentifier(paramName))
86
+ return null;
87
+ const body = ts.isArrowFunction(sel) ? sel.body : null;
88
+ if (!body)
89
+ return null;
90
+ const expr = ts.isBlock(body) ? null : body;
91
+ if (!expr || !ts.isPropertyAccessExpression(expr))
92
+ return null;
93
+ if (!ts.isIdentifier(expr.expression) || expr.expression.text !== paramName.text)
94
+ return null;
95
+ if (!ts.isIdentifier(expr.name))
96
+ return null;
97
+ return expr.name.text;
98
+ }
99
+ function collectItemCalls(n) {
100
+ if (ts.isCallExpression(n) &&
101
+ ts.isIdentifier(n.expression) &&
102
+ n.expression.text === itemName &&
103
+ n.arguments.length === 1) {
104
+ const sel = n.arguments[0];
105
+ if (ts.isArrowFunction(sel) || ts.isFunctionExpression(sel)) {
106
+ const field = extractSimpleField(sel);
107
+ const key = field !== null
108
+ ? `field:${field}`
109
+ : `expr:${printer.printNode(ts.EmitHint.Expression, sel, sf)}`;
110
+ occurrences.push({ kind: 'call', node: n, selector: sel, key });
111
+ }
112
+ }
113
+ else if (ts.isPropertyAccessExpression(n) &&
114
+ ts.isIdentifier(n.expression) &&
115
+ n.expression.text === itemName &&
116
+ ts.isIdentifier(n.name)) {
117
+ const field = n.name.text;
118
+ occurrences.push({ kind: 'access', node: n, field, key: `field:${field}` });
119
+ }
120
+ ts.forEachChild(n, collectItemCalls);
121
+ }
122
+ collectItemCalls(renderFn.body);
123
+ if (occurrences.length < 2)
124
+ return null;
125
+ const groups = new Map();
126
+ for (const occ of occurrences) {
127
+ const existing = groups.get(occ.key);
128
+ if (existing)
129
+ existing.push(occ);
130
+ else
131
+ groups.set(occ.key, [occ]);
132
+ }
133
+ const allGroups = [...groups.entries()];
134
+ if (allGroups.length === 0)
135
+ return null;
136
+ const hoistedStmts = [];
137
+ const replacements = new Map();
138
+ let sIdx = 0;
139
+ for (const [key, occs] of allGroups) {
140
+ const selVar = `__s${sIdx}`;
141
+ const accVar = `__a${sIdx}`;
142
+ sIdx++;
143
+ let selector;
144
+ const callOccurrence = occs.find((o) => o.kind === 'call');
145
+ if (callOccurrence && callOccurrence.kind === 'call') {
146
+ selector = callOccurrence.selector;
147
+ }
148
+ else {
149
+ const firstAccess = occs[0];
150
+ if (firstAccess.kind !== 'access')
151
+ throw new Error('unreachable');
152
+ selector = f.createArrowFunction(undefined, undefined, [f.createParameterDeclaration(undefined, undefined, 't')], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createPropertyAccessExpression(f.createIdentifier('t'), firstAccess.field));
153
+ }
154
+ hoistedStmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration(selVar, undefined, undefined, selector)], ts.NodeFlags.Const)));
155
+ hoistedStmts.push(f.createVariableStatement(undefined, f.createVariableDeclarationList([
156
+ f.createVariableDeclaration(accVar, undefined, undefined, f.createCallExpression(f.createIdentifier('acc'), undefined, [
157
+ f.createIdentifier(selVar),
158
+ ])),
159
+ ], ts.NodeFlags.Const)));
160
+ void key;
161
+ for (const occ of occs) {
162
+ replacements.set(occ.node, f.createIdentifier(accVar));
163
+ }
164
+ }
165
+ function replaceVisitor(n) {
166
+ if (replacements.has(n))
167
+ return replacements.get(n);
168
+ return ts.visitEachChild(n, replaceVisitor, undefined);
169
+ }
170
+ const newBody = ts.visitNode(renderFn.body, replaceVisitor);
171
+ let finalBody;
172
+ if (ts.isBlock(newBody)) {
173
+ finalBody = f.createBlock([...hoistedStmts, ...newBody.statements], true);
174
+ }
175
+ else {
176
+ finalBody = f.createBlock([...hoistedStmts, f.createReturnStatement(newBody)], true);
177
+ }
178
+ const newParameters = renderFn.parameters.map((p, idx) => {
179
+ if (idx !== 0)
180
+ return p;
181
+ if (!ts.isObjectBindingPattern(p.name))
182
+ return p;
183
+ const hasAcc = p.name.elements.some((el) => ts.isBindingElement(el) && ts.isIdentifier(el.name) && el.name.text === 'acc');
184
+ if (hasAcc)
185
+ return p;
186
+ const newBinding = f.createObjectBindingPattern([
187
+ ...p.name.elements,
188
+ f.createBindingElement(undefined, undefined, f.createIdentifier('acc')),
189
+ ]);
190
+ return f.createParameterDeclaration(p.modifiers, p.dotDotDotToken, newBinding, p.questionToken, p.type, p.initializer);
191
+ });
192
+ const newRenderFn = ts.isArrowFunction(renderFn)
193
+ ? f.createArrowFunction(renderFn.modifiers, renderFn.typeParameters, newParameters, renderFn.type, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), finalBody)
194
+ : f.createFunctionExpression(renderFn.modifiers, renderFn.asteriskToken, renderFn.name, renderFn.typeParameters, newParameters, renderFn.type, finalBody);
195
+ const newProps = arg.properties.map((p) => p === renderProp ? f.createPropertyAssignment('render', newRenderFn) : p);
196
+ const newArg = f.createObjectLiteralExpression(newProps, true);
197
+ return f.createCallExpression(node.expression, node.typeArguments, [
198
+ newArg,
199
+ ...node.arguments.slice(1),
200
+ ]);
201
+ },
202
+ };
203
+ }
204
+ //# sourceMappingURL=item-dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-dedup.js","sourceRoot":"","sources":["../../src/modules/item-dedup.ts"],"names":[],"mappings":"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","sourcesContent":["// `item-dedup` — deduplicates repeated `item(selector)` calls AND\n// `item.FIELD` property accesses inside an each() render callback.\n//\n// Before: `item((r) => r.id)` and `item.id` mixed across the render\n// body produce a fresh selector closure / Proxy.get trap on every\n// invocation, fragmenting the V8 inline cache. Worse, repeated\n// `item(...)` calls each allocate a new accessor closure.\n//\n// After: every distinct accessor target (keyed by the simple field\n// name when extractable, else by the printed expression text) is\n// lifted into a single `const __sN = (r) => r.FIELD` selector plus\n// a single `const __aN = acc(__sN)` accessor at the top of the\n// render body. Every occurrence then resolves to a plain identifier\n// reference (`__aN`), and the runtime evaluates them via the\n// non-Proxy `acc()` helper.\n//\n// Fires top-down (`transformCallEnter`) on `each()` so subsequent\n// element-rewrite passes see the hoisted form — important because\n// element rewrites inline accessor argument references, and the\n// hoisted `__aN` is a stable identifier they can compile correctly.\n//\n// Gated on the `each` helper (alias-aware via `isHelperCall`). The\n// module exits early when fewer than 2 occurrences exist; no point\n// hoisting a single access.\n\nimport ts from 'typescript'\nimport type { CompilerModule } from '../module.js'\nimport { isHelperCall } from '../transform.js'\n\nexport interface ItemDedupModuleOptions {\n viewHelperNames: Set<string>\n viewHelperAliases: Map<string, string>\n}\n\nexport function itemDedupModule(options: ItemDedupModuleOptions): CompilerModule {\n const { viewHelperNames, viewHelperAliases } = options\n // Printer is per-module — created lazily on first match, then reused\n // across calls. The selector-expression key uses the printer's output\n // as a fallback when `extractSimpleField` can't reduce to a plain\n // field name.\n let printer: ts.Printer | null = null\n return {\n name: 'item-dedup',\n compilerVersion: '^0.3.0',\n diagnostics: [],\n visitors: {},\n\n transformCallEnter(ctx, node) {\n if (!isHelperCall(node.expression, 'each', viewHelperNames, viewHelperAliases)) return null\n const f = ctx.factory\n const sf = ctx.analysis.sourceFile\n if (!printer) printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })\n\n const arg = node.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return null\n\n let renderProp: ts.PropertyAssignment | null = null\n for (const prop of arg.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'render'\n ) {\n renderProp = prop\n break\n }\n }\n if (!renderProp) return null\n\n const renderFn = renderProp.initializer\n if (!ts.isArrowFunction(renderFn) && !ts.isFunctionExpression(renderFn)) return null\n\n const renderParam = renderFn.parameters[0]\n if (!renderParam) return null\n\n let itemName: string | null = null\n if (ts.isIdentifier(renderParam.name)) {\n itemName = renderParam.name.text\n } else if (ts.isObjectBindingPattern(renderParam.name)) {\n for (const el of renderParam.name.elements) {\n if (ts.isBindingElement(el) && ts.isIdentifier(el.name) && el.name.text === 'item') {\n itemName = 'item'\n break\n }\n }\n }\n if (!itemName) return null\n\n type Occurrence =\n | { kind: 'call'; node: ts.CallExpression; selector: ts.Expression; key: string }\n | { kind: 'access'; node: ts.PropertyAccessExpression; field: string; key: string }\n const occurrences: Occurrence[] = []\n\n function extractSimpleField(sel: ts.ArrowFunction | ts.FunctionExpression): string | null {\n if (sel.parameters.length !== 1) return null\n const paramName = sel.parameters[0]!.name\n if (!ts.isIdentifier(paramName)) return null\n const body = ts.isArrowFunction(sel) ? sel.body : null\n if (!body) return null\n const expr = ts.isBlock(body) ? null : body\n if (!expr || !ts.isPropertyAccessExpression(expr)) return null\n if (!ts.isIdentifier(expr.expression) || expr.expression.text !== paramName.text)\n return null\n if (!ts.isIdentifier(expr.name)) return null\n return expr.name.text\n }\n\n function collectItemCalls(n: ts.Node): void {\n if (\n ts.isCallExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === itemName &&\n n.arguments.length === 1\n ) {\n const sel = n.arguments[0]!\n if (ts.isArrowFunction(sel) || ts.isFunctionExpression(sel)) {\n const field = extractSimpleField(sel)\n const key =\n field !== null\n ? `field:${field}`\n : `expr:${printer!.printNode(ts.EmitHint.Expression, sel, sf)}`\n occurrences.push({ kind: 'call', node: n, selector: sel, key })\n }\n } else if (\n ts.isPropertyAccessExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === itemName &&\n ts.isIdentifier(n.name)\n ) {\n const field = n.name.text\n occurrences.push({ kind: 'access', node: n, field, key: `field:${field}` })\n }\n ts.forEachChild(n, collectItemCalls)\n }\n collectItemCalls(renderFn.body)\n\n if (occurrences.length < 2) return null\n\n const groups = new Map<string, Occurrence[]>()\n for (const occ of occurrences) {\n const existing = groups.get(occ.key)\n if (existing) existing.push(occ)\n else groups.set(occ.key, [occ])\n }\n\n const allGroups = [...groups.entries()]\n if (allGroups.length === 0) return null\n\n const hoistedStmts: ts.Statement[] = []\n const replacements = new Map<ts.Node, ts.Identifier>()\n let sIdx = 0\n\n for (const [key, occs] of allGroups) {\n const selVar = `__s${sIdx}`\n const accVar = `__a${sIdx}`\n sIdx++\n\n let selector: ts.Expression\n const callOccurrence = occs.find((o) => o.kind === 'call')\n if (callOccurrence && callOccurrence.kind === 'call') {\n selector = callOccurrence.selector\n } else {\n const firstAccess = occs[0]!\n if (firstAccess.kind !== 'access') throw new Error('unreachable')\n selector = f.createArrowFunction(\n undefined,\n undefined,\n [f.createParameterDeclaration(undefined, undefined, 't')],\n undefined,\n f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),\n f.createPropertyAccessExpression(f.createIdentifier('t'), firstAccess.field),\n )\n }\n\n hoistedStmts.push(\n f.createVariableStatement(\n undefined,\n f.createVariableDeclarationList(\n [f.createVariableDeclaration(selVar, undefined, undefined, selector)],\n ts.NodeFlags.Const,\n ),\n ),\n )\n hoistedStmts.push(\n f.createVariableStatement(\n undefined,\n f.createVariableDeclarationList(\n [\n f.createVariableDeclaration(\n accVar,\n undefined,\n undefined,\n f.createCallExpression(f.createIdentifier('acc'), undefined, [\n f.createIdentifier(selVar),\n ]),\n ),\n ],\n ts.NodeFlags.Const,\n ),\n ),\n )\n\n void key\n for (const occ of occs) {\n replacements.set(occ.node, f.createIdentifier(accVar))\n }\n }\n\n function replaceVisitor(n: ts.Node): ts.Node {\n if (replacements.has(n)) return replacements.get(n)!\n return ts.visitEachChild(n, replaceVisitor, undefined!)\n }\n const newBody = ts.visitNode(renderFn.body, replaceVisitor)!\n\n let finalBody: ts.ConciseBody\n if (ts.isBlock(newBody)) {\n finalBody = f.createBlock([...hoistedStmts, ...(newBody as ts.Block).statements], true)\n } else {\n finalBody = f.createBlock(\n [...hoistedStmts, f.createReturnStatement(newBody as ts.Expression)],\n true,\n )\n }\n\n const newParameters = renderFn.parameters.map((p, idx) => {\n if (idx !== 0) return p\n if (!ts.isObjectBindingPattern(p.name)) return p\n const hasAcc = p.name.elements.some(\n (el) => ts.isBindingElement(el) && ts.isIdentifier(el.name) && el.name.text === 'acc',\n )\n if (hasAcc) return p\n const newBinding = f.createObjectBindingPattern([\n ...p.name.elements,\n f.createBindingElement(undefined, undefined, f.createIdentifier('acc')),\n ])\n return f.createParameterDeclaration(\n p.modifiers,\n p.dotDotDotToken,\n newBinding,\n p.questionToken,\n p.type,\n p.initializer,\n )\n })\n\n const newRenderFn = ts.isArrowFunction(renderFn)\n ? f.createArrowFunction(\n renderFn.modifiers,\n renderFn.typeParameters,\n newParameters,\n renderFn.type,\n f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),\n finalBody,\n )\n : f.createFunctionExpression(\n renderFn.modifiers,\n renderFn.asteriskToken,\n renderFn.name,\n renderFn.typeParameters,\n newParameters,\n renderFn.type,\n finalBody as ts.Block,\n )\n\n const newProps = arg.properties.map((p) =>\n p === renderProp ? f.createPropertyAssignment('render', newRenderFn) : p,\n )\n const newArg = f.createObjectLiteralExpression(newProps, true)\n return f.createCallExpression(node.expression, node.typeArguments, [\n newArg,\n ...node.arguments.slice(1),\n ])\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function mapOnStateArrayModule(): CompilerModule;
3
+ //# sourceMappingURL=map-on-state-array.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-on-state-array.d.ts","sourceRoot":"","sources":["../../src/modules/map-on-state-array.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAgClD,wBAAgB,qBAAqB,IAAI,cAAc,CAgDtD"}
@@ -0,0 +1,84 @@
1
+ // `map-on-state-array` — errors when a state-derived array is iterated
2
+ // with `.map()` inside a view function. The reactive primitive is
3
+ // `each(...)`; `.map()` produces a static list with no per-row scope,
4
+ // no key-based reconciliation, and no precise mask gating. Migrated
5
+ // from `@llui/eslint-plugin/src/rules/map-on-state-array.ts`.
6
+ import ts from 'typescript';
7
+ import { rangeFromOffsets } from '../diagnostic.js';
8
+ import { findComponentCalls } from './_shared.js';
9
+ function findViewProperty(call) {
10
+ const arg = call.arguments[0];
11
+ if (!arg || !ts.isObjectLiteralExpression(arg))
12
+ return undefined;
13
+ for (const prop of arg.properties) {
14
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'view') {
15
+ return prop;
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+ /**
21
+ * True when `expr` resolves to a state-like reference. We recognize
22
+ * the conventional names — `state`, `s`, `_state` — and chained
23
+ * property accesses rooted at one of them (e.g. `s.items.filtered`).
24
+ * Type-aware resolution would be more precise; the conventions are
25
+ * stable enough across the codebase that name matching catches the
26
+ * intended cases without a checker.
27
+ */
28
+ function isStateReference(expr) {
29
+ if (ts.isIdentifier(expr)) {
30
+ return expr.text === 'state' || expr.text === 's' || expr.text === '_state';
31
+ }
32
+ if (ts.isPropertyAccessExpression(expr)) {
33
+ return isStateReference(expr.expression);
34
+ }
35
+ return false;
36
+ }
37
+ export function mapOnStateArrayModule() {
38
+ return {
39
+ name: 'map-on-state-array',
40
+ compilerVersion: '^0.3.0',
41
+ diagnostics: [
42
+ {
43
+ id: 'llui/map-on-state-array',
44
+ description: 'Array .map() on a state-derived value in view(). Use each() for reactive lists.',
45
+ },
46
+ ],
47
+ visitors: {
48
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
49
+ const visited = node;
50
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
51
+ for (const call of findComponentCalls(sf)) {
52
+ const viewProp = findViewProperty(call);
53
+ if (!viewProp)
54
+ continue;
55
+ const fn = viewProp.initializer;
56
+ if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))
57
+ continue;
58
+ const walk = (n) => {
59
+ if (ts.isCallExpression(n) &&
60
+ ts.isPropertyAccessExpression(n.expression) &&
61
+ ts.isIdentifier(n.expression.name) &&
62
+ n.expression.name.text === 'map' &&
63
+ isStateReference(n.expression.expression)) {
64
+ ctx.reportDiagnostic({
65
+ id: 'llui/map-on-state-array',
66
+ severity: 'error',
67
+ category: 'reactivity',
68
+ message: 'Array `.map()` on a state-derived value inside view(). `.map()` produces a static list — use `each({ items, key, render })` for reactive lists with per-row scope and key-based reconciliation.',
69
+ location: {
70
+ file: sf.fileName,
71
+ range: rangeFromOffsets(sf.text, n.getStart(sf), n.getEnd()),
72
+ },
73
+ });
74
+ }
75
+ ts.forEachChild(n, walk);
76
+ };
77
+ if (fn.body)
78
+ walk(fn.body);
79
+ }
80
+ },
81
+ },
82
+ };
83
+ }
84
+ //# sourceMappingURL=map-on-state-array.js.map