@llui/compiler 0.4.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"lint-modules.d.ts","sourceRoot":"","sources":["../src/lint-modules.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AA2CjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,EAAE,CA4CpD"}
1
+ {"version":3,"file":"lint-modules.d.ts","sourceRoot":"","sources":["../src/lint-modules.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AA6CjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,EAAE,CA8CpD"}
@@ -51,6 +51,8 @@ import { staticOnModule } from './modules/static-on.js';
51
51
  import { noListRenderInSampleModule } from './modules/no-list-render-in-sample.js';
52
52
  import { noSampleInAccessorModule } from './modules/no-sample-in-accessor.js';
53
53
  import { noSampleInReactivePositionModule } from './modules/no-sample-in-reactive-position.js';
54
+ import { noSampleInEventHandlerModule } from './modules/no-sample-in-event-handler.js';
55
+ import { noRepeatedItemCurrentModule } from './modules/no-repeated-item-current.js';
54
56
  import { agentEmitsDriftModule } from './modules/agent-emits-drift.js';
55
57
  import { agentMsgResolvableModule } from './modules/agent-msg-resolvable.js';
56
58
  /**
@@ -101,6 +103,8 @@ export function createLintModules() {
101
103
  noListRenderInSampleModule(),
102
104
  noSampleInAccessorModule(),
103
105
  noSampleInReactivePositionModule(),
106
+ noSampleInEventHandlerModule(),
107
+ noRepeatedItemCurrentModule(),
104
108
  agentEmitsDriftModule(),
105
109
  agentMsgResolvableModule(),
106
110
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"lint-modules.js","sourceRoot":"","sources":["../src/lint-modules.ts"],"names":[],"mappings":"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","sourcesContent":["// Always-on lint modules.\n//\n// Every entry here is a zero-arg `CompilerModule` factory whose output\n// is registered unconditionally on every `transformLlui` invocation.\n// The function is the single source of truth — `transform.ts`'s active-\n// module list spreads it, and `scripts/generate-rule-docs.ts` calls it\n// to enumerate diagnostic IDs for the rule reference. Adding or removing\n// a rule in one place propagates everywhere.\n//\n// Note: this does NOT include modules with per-file options (e.g.\n// `maskLegendModule({ fieldBits, fieldBitsHi })`, `coreSynthesisModule`,\n// etc.) — those stay constructed inline in `transform.ts` because they\n// take per-file context. The `compilerStampModule` is unconditional but\n// it's an instance-not-factory; appended separately by callers.\n\nimport type { CompilerModule } from './module.js'\nimport { bitmaskOverflowModule } from './modules/bitmask-overflow.js'\nimport { asyncUpdateModule } from './modules/async-update.js'\nimport { mapOnStateArrayModule } from './modules/map-on-state-array.js'\nimport { nestedSendInUpdateModule } from './modules/nested-send-in-update.js'\nimport { directStateInViewModule } from './modules/direct-state-in-view.js'\nimport { imperativeDomInViewModule } from './modules/imperative-dom-in-view.js'\nimport { accessorSideEffectModule } from './modules/accessor-side-effect.js'\nimport { stateMutationModule } from './modules/state-mutation.js'\nimport { effectWithoutHandlerModule } from './modules/effect-without-handler.js'\nimport { exhaustiveEffectHandlingModule } from './modules/exhaustive-effect-handling.js'\nimport { noEagerItemAccessorModule } from './modules/no-eager-item-accessor.js'\nimport { pureUpdateFunctionModule } from './modules/pure-update-function.js'\nimport { exhaustiveUpdateModule } from './modules/exhaustive-update.js'\nimport { noLetReactiveAccessorModule } from './modules/no-let-reactive-accessor.js'\nimport { eachClosureViolationModule } from './modules/each-closure-violation.js'\nimport { stringEffectCallbackModule } from './modules/string-effect-callback.js'\nimport { agentMissingIntentModule } from './modules/agent-missing-intent.js'\nimport { agentWarningOnConfirmModule } from './modules/agent-warning-on-confirm.js'\nimport { agentExampleOnPayloadModule } from './modules/agent-example-on-payload.js'\nimport { agentExclusiveAnnotationsModule } from './modules/agent-exclusive-annotations.js'\nimport { agentOptionalFieldUndocumentedModule } from './modules/agent-optional-field-undocumented.js'\nimport { agentTagsendTranslatorMissingModule } from './modules/agent-tagsend-translator-missing.js'\nimport { agentNonextractableHandlerModule } from './modules/agent-nonextractable-handler.js'\nimport { subappRequiresReasonModule } from './modules/subapp-requires-reason.js'\nimport { emptyPropsModule } from './modules/empty-props.js'\nimport { forgottenSpreadModule } from './modules/forgotten-spread.js'\nimport { accessibilityModule } from './modules/accessibility.js'\nimport { viewBagImportModule } from './modules/view-bag-import.js'\nimport { controlledInputModule } from './modules/controlled-input.js'\nimport { missingMemoModule } from './modules/missing-memo.js'\nimport { namespaceImportModule } from './modules/namespace-import.js'\nimport { noBarrelImportWhenSubpathExistsModule } from './modules/no-barrel-import-when-subpath-exists.js'\nimport { formBoilerplateModule } from './modules/form-boilerplate.js'\nimport { spreadInChildrenModule } from './modules/spread-in-children.js'\nimport { staticItemsModule } from './modules/static-items.js'\nimport { staticOnModule } from './modules/static-on.js'\nimport { noListRenderInSampleModule } from './modules/no-list-render-in-sample.js'\nimport { noSampleInAccessorModule } from './modules/no-sample-in-accessor.js'\nimport { noSampleInReactivePositionModule } from './modules/no-sample-in-reactive-position.js'\nimport { agentEmitsDriftModule } from './modules/agent-emits-drift.js'\nimport { agentMsgResolvableModule } from './modules/agent-msg-resolvable.js'\n\n/**\n * Construct fresh instances of every always-on lint module.\n *\n * Returns a new array per call. Modules are stateful within a single\n * `ModuleRegistry.run()` (slot accumulators), so reusing instances\n * across files would leak state — always call this once per file.\n */\nexport function createLintModules(): CompilerModule[] {\n return [\n bitmaskOverflowModule(),\n asyncUpdateModule(),\n mapOnStateArrayModule(),\n nestedSendInUpdateModule(),\n directStateInViewModule(),\n imperativeDomInViewModule(),\n accessorSideEffectModule(),\n stateMutationModule(),\n effectWithoutHandlerModule(),\n exhaustiveEffectHandlingModule(),\n noEagerItemAccessorModule(),\n pureUpdateFunctionModule(),\n exhaustiveUpdateModule(),\n noLetReactiveAccessorModule(),\n eachClosureViolationModule(),\n stringEffectCallbackModule(),\n agentMissingIntentModule(),\n agentWarningOnConfirmModule(),\n agentExampleOnPayloadModule(),\n agentExclusiveAnnotationsModule(),\n agentOptionalFieldUndocumentedModule(),\n agentTagsendTranslatorMissingModule(),\n agentNonextractableHandlerModule(),\n subappRequiresReasonModule(),\n emptyPropsModule(),\n forgottenSpreadModule(),\n accessibilityModule(),\n viewBagImportModule(),\n controlledInputModule(),\n missingMemoModule(),\n namespaceImportModule(),\n noBarrelImportWhenSubpathExistsModule(),\n formBoilerplateModule(),\n spreadInChildrenModule(),\n staticItemsModule(),\n staticOnModule(),\n noListRenderInSampleModule(),\n noSampleInAccessorModule(),\n noSampleInReactivePositionModule(),\n agentEmitsDriftModule(),\n agentMsgResolvableModule(),\n ]\n}\n"]}
1
+ {"version":3,"file":"lint-modules.js","sourceRoot":"","sources":["../src/lint-modules.ts"],"names":[],"mappings":"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","sourcesContent":["// Always-on lint modules.\n//\n// Every entry here is a zero-arg `CompilerModule` factory whose output\n// is registered unconditionally on every `transformLlui` invocation.\n// The function is the single source of truth — `transform.ts`'s active-\n// module list spreads it, and `scripts/generate-rule-docs.ts` calls it\n// to enumerate diagnostic IDs for the rule reference. Adding or removing\n// a rule in one place propagates everywhere.\n//\n// Note: this does NOT include modules with per-file options (e.g.\n// `maskLegendModule({ fieldBits, fieldBitsHi })`, `coreSynthesisModule`,\n// etc.) — those stay constructed inline in `transform.ts` because they\n// take per-file context. The `compilerStampModule` is unconditional but\n// it's an instance-not-factory; appended separately by callers.\n\nimport type { CompilerModule } from './module.js'\nimport { bitmaskOverflowModule } from './modules/bitmask-overflow.js'\nimport { asyncUpdateModule } from './modules/async-update.js'\nimport { mapOnStateArrayModule } from './modules/map-on-state-array.js'\nimport { nestedSendInUpdateModule } from './modules/nested-send-in-update.js'\nimport { directStateInViewModule } from './modules/direct-state-in-view.js'\nimport { imperativeDomInViewModule } from './modules/imperative-dom-in-view.js'\nimport { accessorSideEffectModule } from './modules/accessor-side-effect.js'\nimport { stateMutationModule } from './modules/state-mutation.js'\nimport { effectWithoutHandlerModule } from './modules/effect-without-handler.js'\nimport { exhaustiveEffectHandlingModule } from './modules/exhaustive-effect-handling.js'\nimport { noEagerItemAccessorModule } from './modules/no-eager-item-accessor.js'\nimport { pureUpdateFunctionModule } from './modules/pure-update-function.js'\nimport { exhaustiveUpdateModule } from './modules/exhaustive-update.js'\nimport { noLetReactiveAccessorModule } from './modules/no-let-reactive-accessor.js'\nimport { eachClosureViolationModule } from './modules/each-closure-violation.js'\nimport { stringEffectCallbackModule } from './modules/string-effect-callback.js'\nimport { agentMissingIntentModule } from './modules/agent-missing-intent.js'\nimport { agentWarningOnConfirmModule } from './modules/agent-warning-on-confirm.js'\nimport { agentExampleOnPayloadModule } from './modules/agent-example-on-payload.js'\nimport { agentExclusiveAnnotationsModule } from './modules/agent-exclusive-annotations.js'\nimport { agentOptionalFieldUndocumentedModule } from './modules/agent-optional-field-undocumented.js'\nimport { agentTagsendTranslatorMissingModule } from './modules/agent-tagsend-translator-missing.js'\nimport { agentNonextractableHandlerModule } from './modules/agent-nonextractable-handler.js'\nimport { subappRequiresReasonModule } from './modules/subapp-requires-reason.js'\nimport { emptyPropsModule } from './modules/empty-props.js'\nimport { forgottenSpreadModule } from './modules/forgotten-spread.js'\nimport { accessibilityModule } from './modules/accessibility.js'\nimport { viewBagImportModule } from './modules/view-bag-import.js'\nimport { controlledInputModule } from './modules/controlled-input.js'\nimport { missingMemoModule } from './modules/missing-memo.js'\nimport { namespaceImportModule } from './modules/namespace-import.js'\nimport { noBarrelImportWhenSubpathExistsModule } from './modules/no-barrel-import-when-subpath-exists.js'\nimport { formBoilerplateModule } from './modules/form-boilerplate.js'\nimport { spreadInChildrenModule } from './modules/spread-in-children.js'\nimport { staticItemsModule } from './modules/static-items.js'\nimport { staticOnModule } from './modules/static-on.js'\nimport { noListRenderInSampleModule } from './modules/no-list-render-in-sample.js'\nimport { noSampleInAccessorModule } from './modules/no-sample-in-accessor.js'\nimport { noSampleInReactivePositionModule } from './modules/no-sample-in-reactive-position.js'\nimport { noSampleInEventHandlerModule } from './modules/no-sample-in-event-handler.js'\nimport { noRepeatedItemCurrentModule } from './modules/no-repeated-item-current.js'\nimport { agentEmitsDriftModule } from './modules/agent-emits-drift.js'\nimport { agentMsgResolvableModule } from './modules/agent-msg-resolvable.js'\n\n/**\n * Construct fresh instances of every always-on lint module.\n *\n * Returns a new array per call. Modules are stateful within a single\n * `ModuleRegistry.run()` (slot accumulators), so reusing instances\n * across files would leak state — always call this once per file.\n */\nexport function createLintModules(): CompilerModule[] {\n return [\n bitmaskOverflowModule(),\n asyncUpdateModule(),\n mapOnStateArrayModule(),\n nestedSendInUpdateModule(),\n directStateInViewModule(),\n imperativeDomInViewModule(),\n accessorSideEffectModule(),\n stateMutationModule(),\n effectWithoutHandlerModule(),\n exhaustiveEffectHandlingModule(),\n noEagerItemAccessorModule(),\n pureUpdateFunctionModule(),\n exhaustiveUpdateModule(),\n noLetReactiveAccessorModule(),\n eachClosureViolationModule(),\n stringEffectCallbackModule(),\n agentMissingIntentModule(),\n agentWarningOnConfirmModule(),\n agentExampleOnPayloadModule(),\n agentExclusiveAnnotationsModule(),\n agentOptionalFieldUndocumentedModule(),\n agentTagsendTranslatorMissingModule(),\n agentNonextractableHandlerModule(),\n subappRequiresReasonModule(),\n emptyPropsModule(),\n forgottenSpreadModule(),\n accessibilityModule(),\n viewBagImportModule(),\n controlledInputModule(),\n missingMemoModule(),\n namespaceImportModule(),\n noBarrelImportWhenSubpathExistsModule(),\n formBoilerplateModule(),\n spreadInChildrenModule(),\n staticItemsModule(),\n staticOnModule(),\n noListRenderInSampleModule(),\n noSampleInAccessorModule(),\n noSampleInReactivePositionModule(),\n noSampleInEventHandlerModule(),\n noRepeatedItemCurrentModule(),\n agentEmitsDriftModule(),\n agentMsgResolvableModule(),\n ]\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function noRepeatedItemCurrentModule(): CompilerModule;
3
+ //# sourceMappingURL=no-repeated-item-current.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-repeated-item-current.d.ts","sourceRoot":"","sources":["../../src/modules/no-repeated-item-current.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAoGlD,wBAAgB,2BAA2B,IAAI,cAAc,CA4C5D"}
@@ -0,0 +1,164 @@
1
+ // `no-repeated-item-current` — warns when an `each.render` callback's
2
+ // accessor body calls `item.current()` more than once with a property
3
+ // chain after each call (e.g. `item.current().facts[K]` repeated
4
+ // inside the same text/show.when accessor).
5
+ //
6
+ // Two reasons the pattern is dangerous:
7
+ //
8
+ // 1. **Bitmask trap.** `item.current().X` hides the read from the
9
+ // compiler's static analyzer — the accessor falls back to
10
+ // FULL_MASK and fires on every state change instead of only when
11
+ // `X` changes.
12
+ // 2. **Reconcile-race undefined.** Repeated `.current()` calls
13
+ // across a single accessor body can observe intermediate state
14
+ // during a structural transition. The chained property access
15
+ // then throws `Cannot read properties of undefined (reading
16
+ // 'X')`. The dungeonlogs 2026-05-20 report named exactly this
17
+ // class of bug.
18
+ //
19
+ // The fix is one of:
20
+ // - destructure once: `const e = item.current(); use e.X, e.Y`
21
+ // - project to a row type in `items` so each cell becomes a simple
22
+ // field read (`item.X` shorthand)
23
+ //
24
+ // Severity: warn. A single `item.current()` call is fine (sometimes
25
+ // necessary, e.g. guarding for primitive T's `current` accessor); the
26
+ // warning fires only when the same accessor body calls it 2+ times.
27
+ import ts from 'typescript';
28
+ import { rangeFromOffsets } from '../diagnostic.js';
29
+ /** True when `node` is `item.current()` — bare `item` identifier root. */
30
+ function isItemCurrentCall(node) {
31
+ if (!ts.isCallExpression(node))
32
+ return false;
33
+ if (node.arguments.length !== 0)
34
+ return false;
35
+ if (!ts.isPropertyAccessExpression(node.expression))
36
+ return false;
37
+ const obj = node.expression.expression;
38
+ if (!ts.isIdentifier(obj) || obj.text !== 'item')
39
+ return false;
40
+ const name = node.expression.name;
41
+ return ts.isIdentifier(name) && name.text === 'current';
42
+ }
43
+ /**
44
+ * Find direct-children `item.current()` calls of `body` that are
45
+ * followed by a property access (e.g. `item.current().X`). Skip nested
46
+ * function bodies — a `.current()` inside an inner arrow runs in a
47
+ * different scope.
48
+ */
49
+ function findChainedItemCurrents(body) {
50
+ const out = [];
51
+ const walk = (n) => {
52
+ if (isItemCurrentCall(n)) {
53
+ // We only flag chained access — `item.current()` alone is fine
54
+ // (used as a return value, passed to a helper, etc.).
55
+ const parent = n.parent;
56
+ if (parent && ts.isPropertyAccessExpression(parent) && parent.expression === n) {
57
+ out.push(n);
58
+ }
59
+ else if (parent && ts.isElementAccessExpression(parent) && parent.expression === n) {
60
+ out.push(n);
61
+ }
62
+ return;
63
+ }
64
+ if ((ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) &&
65
+ n !== body) {
66
+ return;
67
+ }
68
+ ts.forEachChild(n, walk);
69
+ };
70
+ walk(body);
71
+ return out;
72
+ }
73
+ /**
74
+ * Walk a function body and report when any inner accessor (arrow arg
75
+ * to `text` / `unsafeHtml` / `h.show({ when })` / similar) calls
76
+ * `item.current().X` more than once.
77
+ */
78
+ function checkRenderBody(body, sf, report) {
79
+ const visit = (n) => {
80
+ if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {
81
+ const calls = findChainedItemCurrents(n.body);
82
+ if (calls.length >= 2) {
83
+ // Report the SECOND call — the first one is fine on its own;
84
+ // the warning is "you're doing this repeatedly."
85
+ report(calls[1]);
86
+ }
87
+ }
88
+ ts.forEachChild(n, visit);
89
+ };
90
+ visit(body);
91
+ void sf;
92
+ }
93
+ /**
94
+ * Detect an `each(...)` or `h.each(...)` call. Returns the render
95
+ * function's body if present.
96
+ */
97
+ function eachRenderBody(call) {
98
+ let isEach = false;
99
+ if (ts.isIdentifier(call.expression) && call.expression.text === 'each')
100
+ isEach = true;
101
+ else if (ts.isPropertyAccessExpression(call.expression) &&
102
+ ts.isIdentifier(call.expression.name) &&
103
+ call.expression.name.text === 'each') {
104
+ isEach = true;
105
+ }
106
+ if (!isEach)
107
+ return undefined;
108
+ const arg = call.arguments[0];
109
+ if (!arg || !ts.isObjectLiteralExpression(arg))
110
+ return undefined;
111
+ for (const prop of arg.properties) {
112
+ if (ts.isPropertyAssignment(prop) &&
113
+ ts.isIdentifier(prop.name) &&
114
+ prop.name.text === 'render' &&
115
+ (ts.isArrowFunction(prop.initializer) || ts.isFunctionExpression(prop.initializer))) {
116
+ return prop.initializer.body;
117
+ }
118
+ }
119
+ return undefined;
120
+ }
121
+ export function noRepeatedItemCurrentModule() {
122
+ return {
123
+ name: 'no-repeated-item-current',
124
+ compilerVersion: '^0.3.0',
125
+ diagnostics: [
126
+ {
127
+ id: 'llui/no-repeated-item-current',
128
+ description: 'Repeated `item.current().X` calls inside the same accessor — bitmask falls back to FULL_MASK and chained access can throw during reconcile races. Destructure once or project to a row type.',
129
+ },
130
+ ],
131
+ visitors: {
132
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
133
+ const visited = node;
134
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
135
+ const walk = (n) => {
136
+ if (ts.isCallExpression(n)) {
137
+ const body = eachRenderBody(n);
138
+ if (body) {
139
+ checkRenderBody(body, sf, (offender) => {
140
+ ctx.reportDiagnostic({
141
+ id: 'llui/no-repeated-item-current',
142
+ severity: 'warning',
143
+ category: 'reactivity',
144
+ message: `Repeated \`item.current()\` calls inside an each.render accessor. The compiler can't trace through ` +
145
+ `\`item.current().X\` so the binding falls back to FULL_MASK (fires on every state change), and ` +
146
+ `chained access can throw \`Cannot read properties of undefined\` during structural reconciles. ` +
147
+ `Either destructure once at the top — \`const e = item.current(); /* use e.X, e.Y */\` — or project ` +
148
+ `to a row type in \`items\` so each cell becomes a simple field read (\`item.X\` shorthand).`,
149
+ location: {
150
+ file: sf.fileName,
151
+ range: rangeFromOffsets(sf.text, offender.getStart(sf), offender.getEnd()),
152
+ },
153
+ });
154
+ });
155
+ }
156
+ }
157
+ ts.forEachChild(n, walk);
158
+ };
159
+ walk(sf);
160
+ },
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=no-repeated-item-current.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-repeated-item-current.js","sourceRoot":"","sources":["../../src/modules/no-repeated-item-current.ts"],"names":[],"mappings":"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","sourcesContent":["// `no-repeated-item-current` — warns when an `each.render` callback's\n// accessor body calls `item.current()` more than once with a property\n// chain after each call (e.g. `item.current().facts[K]` repeated\n// inside the same text/show.when accessor).\n//\n// Two reasons the pattern is dangerous:\n//\n// 1. **Bitmask trap.** `item.current().X` hides the read from the\n// compiler's static analyzer — the accessor falls back to\n// FULL_MASK and fires on every state change instead of only when\n// `X` changes.\n// 2. **Reconcile-race undefined.** Repeated `.current()` calls\n// across a single accessor body can observe intermediate state\n// during a structural transition. The chained property access\n// then throws `Cannot read properties of undefined (reading\n// 'X')`. The dungeonlogs 2026-05-20 report named exactly this\n// class of bug.\n//\n// The fix is one of:\n// - destructure once: `const e = item.current(); use e.X, e.Y`\n// - project to a row type in `items` so each cell becomes a simple\n// field read (`item.X` shorthand)\n//\n// Severity: warn. A single `item.current()` call is fine (sometimes\n// necessary, e.g. guarding for primitive T's `current` accessor); the\n// warning fires only when the same accessor body calls it 2+ times.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\n\n/** True when `node` is `item.current()` — bare `item` identifier root. */\nfunction isItemCurrentCall(node: ts.Node): node is ts.CallExpression {\n if (!ts.isCallExpression(node)) return false\n if (node.arguments.length !== 0) return false\n if (!ts.isPropertyAccessExpression(node.expression)) return false\n const obj = node.expression.expression\n if (!ts.isIdentifier(obj) || obj.text !== 'item') return false\n const name = node.expression.name\n return ts.isIdentifier(name) && name.text === 'current'\n}\n\n/**\n * Find direct-children `item.current()` calls of `body` that are\n * followed by a property access (e.g. `item.current().X`). Skip nested\n * function bodies — a `.current()` inside an inner arrow runs in a\n * different scope.\n */\nfunction findChainedItemCurrents(body: ts.Node): ts.CallExpression[] {\n const out: ts.CallExpression[] = []\n const walk = (n: ts.Node): void => {\n if (isItemCurrentCall(n)) {\n // We only flag chained access — `item.current()` alone is fine\n // (used as a return value, passed to a helper, etc.).\n const parent = n.parent\n if (parent && ts.isPropertyAccessExpression(parent) && parent.expression === n) {\n out.push(n)\n } else if (parent && ts.isElementAccessExpression(parent) && parent.expression === n) {\n out.push(n)\n }\n return\n }\n if (\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) &&\n n !== body\n ) {\n return\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return out\n}\n\n/**\n * Walk a function body and report when any inner accessor (arrow arg\n * to `text` / `unsafeHtml` / `h.show({ when })` / similar) calls\n * `item.current().X` more than once.\n */\nfunction checkRenderBody(\n body: ts.Node,\n sf: ts.SourceFile,\n report: (offender: ts.CallExpression) => void,\n): void {\n const visit = (n: ts.Node): void => {\n if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {\n const calls = findChainedItemCurrents(n.body)\n if (calls.length >= 2) {\n // Report the SECOND call — the first one is fine on its own;\n // the warning is \"you're doing this repeatedly.\"\n report(calls[1]!)\n }\n }\n ts.forEachChild(n, visit)\n }\n visit(body)\n void sf\n}\n\n/**\n * Detect an `each(...)` or `h.each(...)` call. Returns the render\n * function's body if present.\n */\nfunction eachRenderBody(call: ts.CallExpression): ts.Node | undefined {\n let isEach = false\n if (ts.isIdentifier(call.expression) && call.expression.text === 'each') isEach = true\n else if (\n ts.isPropertyAccessExpression(call.expression) &&\n ts.isIdentifier(call.expression.name) &&\n call.expression.name.text === 'each'\n ) {\n isEach = true\n }\n if (!isEach) return 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 === 'render' &&\n (ts.isArrowFunction(prop.initializer) || ts.isFunctionExpression(prop.initializer))\n ) {\n return prop.initializer.body\n }\n }\n return undefined\n}\n\nexport function noRepeatedItemCurrentModule(): CompilerModule {\n return {\n name: 'no-repeated-item-current',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/no-repeated-item-current',\n description:\n 'Repeated `item.current().X` calls inside the same accessor — bitmask falls back to FULL_MASK and chained access can throw during reconcile races. Destructure once or project to a row type.',\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 const body = eachRenderBody(n)\n if (body) {\n checkRenderBody(body, sf, (offender) => {\n ctx.reportDiagnostic({\n id: 'llui/no-repeated-item-current',\n severity: 'warning',\n category: 'reactivity',\n message:\n `Repeated \\`item.current()\\` calls inside an each.render accessor. The compiler can't trace through ` +\n `\\`item.current().X\\` so the binding falls back to FULL_MASK (fires on every state change), and ` +\n `chained access can throw \\`Cannot read properties of undefined\\` during structural reconciles. ` +\n `Either destructure once at the top — \\`const e = item.current(); /* use e.X, e.Y */\\` — or project ` +\n `to a row type in \\`items\\` so each cell becomes a simple field read (\\`item.X\\` shorthand).`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, offender.getStart(sf), offender.getEnd()),\n },\n })\n })\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n },\n },\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { CompilerModule } from '../module.js';
2
+ export declare function noSampleInEventHandlerModule(): CompilerModule;
3
+ //# sourceMappingURL=no-sample-in-event-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-sample-in-event-handler.d.ts","sourceRoot":"","sources":["../../src/modules/no-sample-in-event-handler.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA0ClD,wBAAgB,4BAA4B,IAAI,cAAc,CAgD7D"}
@@ -0,0 +1,101 @@
1
+ // `no-sample-in-event-handler` — errors when `sample()` / `h.sample()`
2
+ // appears inside an event-handler property (`onClick`, `onInput`,
3
+ // `onSubmit`, …). Event handlers run AFTER mount, with no active
4
+ // render context, so the runtime `sample()` throws `[LLui] sample()
5
+ // can only be called inside a component's view() function`.
6
+ //
7
+ // Catching at compile time turns "open the dev console, click the
8
+ // button, see the error" into a build failure on the offending file.
9
+ // Aligned with the framework's "compile-time errors not lint warnings"
10
+ // philosophy.
11
+ //
12
+ // The right pattern is to capture at render time:
13
+ // const id = h.sample((s) => s.id)
14
+ // button({ onClick: () => send({ type: 'select', id }) })
15
+ // or to use the mount handle: `handle.getState()` inside the handler.
16
+ import ts from 'typescript';
17
+ import { rangeFromOffsets } from '../diagnostic.js';
18
+ const EVENT_HANDLER_KEY_RE = /^on[A-Z]/;
19
+ function isSampleCall(n) {
20
+ if (!ts.isCallExpression(n))
21
+ return false;
22
+ if (ts.isIdentifier(n.expression) && n.expression.text === 'sample')
23
+ return true;
24
+ if (ts.isPropertyAccessExpression(n.expression) &&
25
+ ts.isIdentifier(n.expression.name) &&
26
+ n.expression.name.text === 'sample') {
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+ function findFirstSampleInside(body) {
32
+ let found;
33
+ const walk = (n) => {
34
+ if (found)
35
+ return;
36
+ if (isSampleCall(n)) {
37
+ found = n;
38
+ return;
39
+ }
40
+ // DON'T descend into nested functions — sample() inside an inner
41
+ // function (e.g. a setTimeout body or another arrow that captures
42
+ // the event handler's closure) runs at a different time and has
43
+ // its own context check at runtime. Only the directly-synchronous
44
+ // sample call in the handler body is the trap this rule targets.
45
+ if ((ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) &&
46
+ n !== body) {
47
+ return;
48
+ }
49
+ ts.forEachChild(n, walk);
50
+ };
51
+ walk(body);
52
+ return found;
53
+ }
54
+ export function noSampleInEventHandlerModule() {
55
+ return {
56
+ name: 'no-sample-in-event-handler',
57
+ compilerVersion: '^0.3.0',
58
+ diagnostics: [
59
+ {
60
+ id: 'llui/no-sample-in-event-handler',
61
+ description: '`sample()` inside an event handler — handlers run with no render context; throws at runtime. Capture at render time instead.',
62
+ },
63
+ ],
64
+ visitors: {
65
+ [ts.SyntaxKind.SourceFile]: (ctx, node) => {
66
+ const visited = node;
67
+ const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true);
68
+ const walk = (n) => {
69
+ if (ts.isPropertyAssignment(n)) {
70
+ const key = n.name;
71
+ if (ts.isIdentifier(key) && EVENT_HANDLER_KEY_RE.test(key.text)) {
72
+ const value = n.initializer;
73
+ if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {
74
+ const offender = findFirstSampleInside(value.body);
75
+ if (offender) {
76
+ ctx.reportDiagnostic({
77
+ id: 'llui/no-sample-in-event-handler',
78
+ severity: 'error',
79
+ category: 'reactivity',
80
+ message: `\`sample()\` is being called inside the \`${key.text}\` handler. Handlers run AFTER mount with no active render context, so this throws at runtime ` +
81
+ `(\`[LLui] sample() can only be called inside a component's view() function\`). Capture the value at render time instead: ` +
82
+ `\`const id = h.sample(s => s.id); button({ ${key.text}: () => send({ ..., id }) })\`. ` +
83
+ `If you need the LATEST state at click time (rare), use the mount handle: ` +
84
+ `\`handle.getState()\` inside the handler.`,
85
+ location: {
86
+ file: sf.fileName,
87
+ range: rangeFromOffsets(sf.text, offender.getStart(sf), offender.getEnd()),
88
+ },
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ ts.forEachChild(n, walk);
95
+ };
96
+ walk(sf);
97
+ },
98
+ },
99
+ };
100
+ }
101
+ //# sourceMappingURL=no-sample-in-event-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-sample-in-event-handler.js","sourceRoot":"","sources":["../../src/modules/no-sample-in-event-handler.ts"],"names":[],"mappings":"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","sourcesContent":["// `no-sample-in-event-handler` — errors when `sample()` / `h.sample()`\n// appears inside an event-handler property (`onClick`, `onInput`,\n// `onSubmit`, …). Event handlers run AFTER mount, with no active\n// render context, so the runtime `sample()` throws `[LLui] sample()\n// can only be called inside a component's view() function`.\n//\n// Catching at compile time turns \"open the dev console, click the\n// button, see the error\" into a build failure on the offending file.\n// Aligned with the framework's \"compile-time errors not lint warnings\"\n// philosophy.\n//\n// The right pattern is to capture at render time:\n// const id = h.sample((s) => s.id)\n// button({ onClick: () => send({ type: 'select', id }) })\n// or to use the mount handle: `handle.getState()` inside the handler.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\n\nconst EVENT_HANDLER_KEY_RE = /^on[A-Z]/\n\nfunction isSampleCall(n: ts.Node): boolean {\n if (!ts.isCallExpression(n)) return false\n if (ts.isIdentifier(n.expression) && n.expression.text === 'sample') return true\n if (\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.name) &&\n n.expression.name.text === 'sample'\n ) {\n return true\n }\n return false\n}\n\nfunction findFirstSampleInside(body: ts.Node): ts.CallExpression | undefined {\n let found: ts.CallExpression | undefined\n const walk = (n: ts.Node): void => {\n if (found) return\n if (isSampleCall(n)) {\n found = n as ts.CallExpression\n return\n }\n // DON'T descend into nested functions — sample() inside an inner\n // function (e.g. a setTimeout body or another arrow that captures\n // the event handler's closure) runs at a different time and has\n // its own context check at runtime. Only the directly-synchronous\n // sample call in the handler body is the trap this rule targets.\n if (\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) &&\n n !== body\n ) {\n return\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n return found\n}\n\nexport function noSampleInEventHandlerModule(): CompilerModule {\n return {\n name: 'no-sample-in-event-handler',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/no-sample-in-event-handler',\n description:\n '`sample()` inside an event handler — handlers run with no render context; throws at runtime. Capture at render time instead.',\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n const visited = node as ts.SourceFile\n const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n const walk = (n: ts.Node): void => {\n if (ts.isPropertyAssignment(n)) {\n const key = n.name\n if (ts.isIdentifier(key) && EVENT_HANDLER_KEY_RE.test(key.text)) {\n const value = n.initializer\n if (ts.isArrowFunction(value) || ts.isFunctionExpression(value)) {\n const offender = findFirstSampleInside(value.body)\n if (offender) {\n ctx.reportDiagnostic({\n id: 'llui/no-sample-in-event-handler',\n severity: 'error',\n category: 'reactivity',\n message:\n `\\`sample()\\` is being called inside the \\`${key.text}\\` handler. Handlers run AFTER mount with no active render context, so this throws at runtime ` +\n `(\\`[LLui] sample() can only be called inside a component's view() function\\`). Capture the value at render time instead: ` +\n `\\`const id = h.sample(s => s.id); button({ ${key.text}: () => send({ ..., id }) })\\`. ` +\n `If you need the LATEST state at click time (rare), use the mount handle: ` +\n `\\`handle.getState()\\` inside the handler.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, offender.getStart(sf), offender.getEnd()),\n },\n })\n }\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n },\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/compiler",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {