@llui/compiler 0.3.0 → 0.3.2
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.
- package/dist/collect-deps.d.ts +12 -0
- package/dist/collect-deps.d.ts.map +1 -1
- package/dist/collect-deps.js +6 -1
- package/dist/collect-deps.js.map +1 -1
- package/dist/cross-file-walker.d.ts +10 -0
- package/dist/cross-file-walker.d.ts.map +1 -1
- package/dist/cross-file-walker.js +81 -24
- package/dist/cross-file-walker.js.map +1 -1
- package/dist/lint-modules.d.ts +10 -0
- package/dist/lint-modules.d.ts.map +1 -0
- package/dist/lint-modules.js +108 -0
- package/dist/lint-modules.js.map +1 -0
- package/dist/module.d.ts +33 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +2 -1
- package/dist/module.js.map +1 -1
- package/dist/modules/_msg-variants.d.ts +10 -0
- package/dist/modules/_msg-variants.d.ts.map +1 -1
- package/dist/modules/_msg-variants.js +18 -1
- package/dist/modules/_msg-variants.js.map +1 -1
- package/dist/modules/agent-emits-drift.d.ts.map +1 -1
- package/dist/modules/agent-emits-drift.js +36 -6
- package/dist/modules/agent-emits-drift.js.map +1 -1
- package/dist/modules/core-synthesis.js +15 -123
- package/dist/modules/core-synthesis.js.map +1 -1
- package/dist/modules/element-rewrite.d.ts +6 -0
- package/dist/modules/element-rewrite.d.ts.map +1 -1
- package/dist/modules/element-rewrite.js +54 -11
- package/dist/modules/element-rewrite.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +191 -96
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Always-on lint modules.
|
|
2
|
+
//
|
|
3
|
+
// Every entry here is a zero-arg `CompilerModule` factory whose output
|
|
4
|
+
// is registered unconditionally on every `transformLlui` invocation.
|
|
5
|
+
// The function is the single source of truth — `transform.ts`'s active-
|
|
6
|
+
// module list spreads it, and `scripts/generate-rule-docs.ts` calls it
|
|
7
|
+
// to enumerate diagnostic IDs for the rule reference. Adding or removing
|
|
8
|
+
// a rule in one place propagates everywhere.
|
|
9
|
+
//
|
|
10
|
+
// Note: this does NOT include modules with per-file options (e.g.
|
|
11
|
+
// `maskLegendModule({ fieldBits, fieldBitsHi })`, `coreSynthesisModule`,
|
|
12
|
+
// etc.) — those stay constructed inline in `transform.ts` because they
|
|
13
|
+
// take per-file context. The `compilerStampModule` is unconditional but
|
|
14
|
+
// it's an instance-not-factory; appended separately by callers.
|
|
15
|
+
import { bitmaskOverflowModule } from './modules/bitmask-overflow.js';
|
|
16
|
+
import { asyncUpdateModule } from './modules/async-update.js';
|
|
17
|
+
import { mapOnStateArrayModule } from './modules/map-on-state-array.js';
|
|
18
|
+
import { nestedSendInUpdateModule } from './modules/nested-send-in-update.js';
|
|
19
|
+
import { directStateInViewModule } from './modules/direct-state-in-view.js';
|
|
20
|
+
import { imperativeDomInViewModule } from './modules/imperative-dom-in-view.js';
|
|
21
|
+
import { accessorSideEffectModule } from './modules/accessor-side-effect.js';
|
|
22
|
+
import { stateMutationModule } from './modules/state-mutation.js';
|
|
23
|
+
import { effectWithoutHandlerModule } from './modules/effect-without-handler.js';
|
|
24
|
+
import { exhaustiveEffectHandlingModule } from './modules/exhaustive-effect-handling.js';
|
|
25
|
+
import { noEagerItemAccessorModule } from './modules/no-eager-item-accessor.js';
|
|
26
|
+
import { pureUpdateFunctionModule } from './modules/pure-update-function.js';
|
|
27
|
+
import { exhaustiveUpdateModule } from './modules/exhaustive-update.js';
|
|
28
|
+
import { noLetReactiveAccessorModule } from './modules/no-let-reactive-accessor.js';
|
|
29
|
+
import { eachClosureViolationModule } from './modules/each-closure-violation.js';
|
|
30
|
+
import { stringEffectCallbackModule } from './modules/string-effect-callback.js';
|
|
31
|
+
import { agentMissingIntentModule } from './modules/agent-missing-intent.js';
|
|
32
|
+
import { agentWarningOnConfirmModule } from './modules/agent-warning-on-confirm.js';
|
|
33
|
+
import { agentExampleOnPayloadModule } from './modules/agent-example-on-payload.js';
|
|
34
|
+
import { agentExclusiveAnnotationsModule } from './modules/agent-exclusive-annotations.js';
|
|
35
|
+
import { agentOptionalFieldUndocumentedModule } from './modules/agent-optional-field-undocumented.js';
|
|
36
|
+
import { agentTagsendTranslatorMissingModule } from './modules/agent-tagsend-translator-missing.js';
|
|
37
|
+
import { agentNonextractableHandlerModule } from './modules/agent-nonextractable-handler.js';
|
|
38
|
+
import { subappRequiresReasonModule } from './modules/subapp-requires-reason.js';
|
|
39
|
+
import { emptyPropsModule } from './modules/empty-props.js';
|
|
40
|
+
import { forgottenSpreadModule } from './modules/forgotten-spread.js';
|
|
41
|
+
import { accessibilityModule } from './modules/accessibility.js';
|
|
42
|
+
import { viewBagImportModule } from './modules/view-bag-import.js';
|
|
43
|
+
import { controlledInputModule } from './modules/controlled-input.js';
|
|
44
|
+
import { missingMemoModule } from './modules/missing-memo.js';
|
|
45
|
+
import { namespaceImportModule } from './modules/namespace-import.js';
|
|
46
|
+
import { noBarrelImportWhenSubpathExistsModule } from './modules/no-barrel-import-when-subpath-exists.js';
|
|
47
|
+
import { formBoilerplateModule } from './modules/form-boilerplate.js';
|
|
48
|
+
import { spreadInChildrenModule } from './modules/spread-in-children.js';
|
|
49
|
+
import { staticItemsModule } from './modules/static-items.js';
|
|
50
|
+
import { staticOnModule } from './modules/static-on.js';
|
|
51
|
+
import { noListRenderInSampleModule } from './modules/no-list-render-in-sample.js';
|
|
52
|
+
import { noSampleInAccessorModule } from './modules/no-sample-in-accessor.js';
|
|
53
|
+
import { noSampleInReactivePositionModule } from './modules/no-sample-in-reactive-position.js';
|
|
54
|
+
import { agentEmitsDriftModule } from './modules/agent-emits-drift.js';
|
|
55
|
+
import { agentMsgResolvableModule } from './modules/agent-msg-resolvable.js';
|
|
56
|
+
/**
|
|
57
|
+
* Construct fresh instances of every always-on lint module.
|
|
58
|
+
*
|
|
59
|
+
* Returns a new array per call. Modules are stateful within a single
|
|
60
|
+
* `ModuleRegistry.run()` (slot accumulators), so reusing instances
|
|
61
|
+
* across files would leak state — always call this once per file.
|
|
62
|
+
*/
|
|
63
|
+
export function createLintModules() {
|
|
64
|
+
return [
|
|
65
|
+
bitmaskOverflowModule(),
|
|
66
|
+
asyncUpdateModule(),
|
|
67
|
+
mapOnStateArrayModule(),
|
|
68
|
+
nestedSendInUpdateModule(),
|
|
69
|
+
directStateInViewModule(),
|
|
70
|
+
imperativeDomInViewModule(),
|
|
71
|
+
accessorSideEffectModule(),
|
|
72
|
+
stateMutationModule(),
|
|
73
|
+
effectWithoutHandlerModule(),
|
|
74
|
+
exhaustiveEffectHandlingModule(),
|
|
75
|
+
noEagerItemAccessorModule(),
|
|
76
|
+
pureUpdateFunctionModule(),
|
|
77
|
+
exhaustiveUpdateModule(),
|
|
78
|
+
noLetReactiveAccessorModule(),
|
|
79
|
+
eachClosureViolationModule(),
|
|
80
|
+
stringEffectCallbackModule(),
|
|
81
|
+
agentMissingIntentModule(),
|
|
82
|
+
agentWarningOnConfirmModule(),
|
|
83
|
+
agentExampleOnPayloadModule(),
|
|
84
|
+
agentExclusiveAnnotationsModule(),
|
|
85
|
+
agentOptionalFieldUndocumentedModule(),
|
|
86
|
+
agentTagsendTranslatorMissingModule(),
|
|
87
|
+
agentNonextractableHandlerModule(),
|
|
88
|
+
subappRequiresReasonModule(),
|
|
89
|
+
emptyPropsModule(),
|
|
90
|
+
forgottenSpreadModule(),
|
|
91
|
+
accessibilityModule(),
|
|
92
|
+
viewBagImportModule(),
|
|
93
|
+
controlledInputModule(),
|
|
94
|
+
missingMemoModule(),
|
|
95
|
+
namespaceImportModule(),
|
|
96
|
+
noBarrelImportWhenSubpathExistsModule(),
|
|
97
|
+
formBoilerplateModule(),
|
|
98
|
+
spreadInChildrenModule(),
|
|
99
|
+
staticItemsModule(),
|
|
100
|
+
staticOnModule(),
|
|
101
|
+
noListRenderInSampleModule(),
|
|
102
|
+
noSampleInAccessorModule(),
|
|
103
|
+
noSampleInReactivePositionModule(),
|
|
104
|
+
agentEmitsDriftModule(),
|
|
105
|
+
agentMsgResolvableModule(),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=lint-modules.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/module.d.ts
CHANGED
|
@@ -26,6 +26,32 @@ export interface FileAnalysis {
|
|
|
26
26
|
* Compiler-API checker, the project root, sibling-module findings if
|
|
27
27
|
* dependencies allow).
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Resolved external type sources for the file under analysis. Same
|
|
31
|
+
* shape as `transform.ts`'s `ExternalTypeSources`; declared here as a
|
|
32
|
+
* structural minimum so the module registry doesn't import from the
|
|
33
|
+
* umbrella. The host adapter (vite-plugin) supplies the values via
|
|
34
|
+
* its async cross-file resolver (`findTypeSource`).
|
|
35
|
+
*
|
|
36
|
+
* Always undefined for test-only `transformLlui(source, fileName)`
|
|
37
|
+
* invocations and for lint adapters without import resolution. Modules
|
|
38
|
+
* that consume this should fall back to file-local behaviour when
|
|
39
|
+
* absent.
|
|
40
|
+
*/
|
|
41
|
+
export interface ModuleExternalTypes {
|
|
42
|
+
state?: {
|
|
43
|
+
source: string;
|
|
44
|
+
typeName: string;
|
|
45
|
+
};
|
|
46
|
+
msg?: {
|
|
47
|
+
source: string;
|
|
48
|
+
typeName: string;
|
|
49
|
+
};
|
|
50
|
+
effect?: {
|
|
51
|
+
source: string;
|
|
52
|
+
typeName: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
29
55
|
export interface AnalysisContext {
|
|
30
56
|
sourceFile: ts.SourceFile;
|
|
31
57
|
/** TS TypeChecker, when the host adapter has built a Program. May be undefined for AST-only paths. */
|
|
@@ -39,6 +65,12 @@ export interface AnalysisContext {
|
|
|
39
65
|
getSlot<T>(moduleName: string, init: () => T): T;
|
|
40
66
|
/** Record a diagnostic. The diagnostic's `id` should match one declared in `DiagnosticDefinition[]`. */
|
|
41
67
|
reportDiagnostic(d: Diagnostic): void;
|
|
68
|
+
/**
|
|
69
|
+
* External type sources from the host adapter's cross-file resolver.
|
|
70
|
+
* Undefined when the host doesn't supply them (test path, lint-only
|
|
71
|
+
* adapters without import resolution).
|
|
72
|
+
*/
|
|
73
|
+
externalTypes?: ModuleExternalTypes;
|
|
42
74
|
}
|
|
43
75
|
export interface EmissionContribution {
|
|
44
76
|
/** Module emitting this contribution — used for conflict reporting. */
|
|
@@ -213,7 +245,7 @@ export declare class ModuleRegistry {
|
|
|
213
245
|
* 4. Emission: each module's `emit?` fires; the registry merges
|
|
214
246
|
* contributions, detecting (field, target) conflicts.
|
|
215
247
|
*/
|
|
216
|
-
run(sourceFile: ts.SourceFile, checker?: ts.TypeChecker): RegistryRunResult;
|
|
248
|
+
run(sourceFile: ts.SourceFile, checker?: ts.TypeChecker, externalTypes?: ModuleExternalTypes): RegistryRunResult;
|
|
217
249
|
/** Module names in declaration order. Adapters surface this for debug logs / config diagnostics. */
|
|
218
250
|
listModules(): string[];
|
|
219
251
|
/** All diagnostic definitions across active modules. Used by adapters to enumerate stable IDs. */
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAIjD,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAA;IACV,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAA;IACzB,4DAA4D;IAC5D,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,2CAA2C;IAC3C,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,EAAE,CAAC,UAAU,CAAA;IACzB,sGAAsG;IACtG,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,SAAS,CAAA;IACnC;;;;;OAKG;IACH,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;IAChD,wGAAwG;IACxG,gBAAgB,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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"}
|
package/dist/module.js
CHANGED
|
@@ -79,7 +79,7 @@ export class ModuleRegistry {
|
|
|
79
79
|
* 4. Emission: each module's `emit?` fires; the registry merges
|
|
80
80
|
* contributions, detecting (field, target) conflicts.
|
|
81
81
|
*/
|
|
82
|
-
run(sourceFile, checker) {
|
|
82
|
+
run(sourceFile, checker, externalTypes) {
|
|
83
83
|
const analysis = {
|
|
84
84
|
sourceFile,
|
|
85
85
|
perModule: new Map(),
|
|
@@ -112,6 +112,7 @@ export class ModuleRegistry {
|
|
|
112
112
|
reportDiagnostic: (d) => {
|
|
113
113
|
analysis.diagnostics.push(d);
|
|
114
114
|
},
|
|
115
|
+
externalTypes,
|
|
115
116
|
};
|
|
116
117
|
// Phase 2: single-pass visitor walk over the (possibly
|
|
117
118
|
// pre-transformed) SourceFile.
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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","sourcesContent":["// CompilerModule + ModuleRegistry — v2c §2 visitor-registry primitive.\n//\n// Modules accumulate findings during a single AST walk per file and\n// contribute emissions after the walk completes. The walker visits each\n// node once; every module registered for that node's SyntaxKind sees it.\n//\n// This file defines the interfaces + the registry. The actual modules\n// (`compiler-core`, `compiler-agent`, `compiler-ssr`, `compiler-devtools`)\n// will consume them; for v2c-partial only the primitive lands, and one\n// proof-of-concept module exercises it.\n//\n// Design contract (v2c.md §2.1):\n// - Modules NEVER walk the AST themselves — only the registry walks.\n// This keeps the cost O(nodes), not O(modules × nodes).\n// - Visitor order for a given SyntaxKind is the declaration order in\n// `llui.config.ts`'s `modules: [...]` array. Observable to module\n// authors. Alphabetical-by-name was rejected (couples correctness\n// to package names).\n// - Emission conflicts (two modules writing to the same field) are\n// a hard error, not a silent overwrite. Each module owns disjoint\n// output fields.\n// - `runtimeImports` arrays merge by union (deduplicated). Multiple\n// modules requesting the same runtime helper collapse to one\n// import.\n\nimport ts from 'typescript'\nimport type { Diagnostic } from './diagnostic.js'\n\n// ── Module interface ────────────────────────────────────────────────\n\nexport interface DiagnosticDefinition {\n /** Stable id, e.g. `llui/opaque-view-call`. Per v2c §3 §8.2. */\n id: string\n /** One-line description; useful for adapter UIs that don't render the message. */\n description: string\n}\n\n/**\n * Per-file analysis output. Modules accumulate findings here during\n * visitor dispatch; emit consumes it. The shape is intentionally\n * open-ended — modules name their own slots and the umbrella's\n * orchestrator never inspects them, only forwards.\n */\nexport interface FileAnalysis {\n /** Source file the analysis ran over. */\n sourceFile: ts.SourceFile\n /** Per-module accumulator buckets, keyed by module name. */\n perModule: Map<string, unknown>\n /** Diagnostics emitted during the walk. */\n diagnostics: Diagnostic[]\n}\n\n/**\n * Context passed to every visitor invocation. Modules use it to record\n * findings, emit diagnostics, and consult shared state (the TS\n * Compiler-API checker, the project root, sibling-module findings if\n * dependencies allow).\n */\nexport interface AnalysisContext {\n sourceFile: ts.SourceFile\n /** TS TypeChecker, when the host adapter has built a Program. May be undefined for AST-only paths. */\n checker: ts.TypeChecker | undefined\n /**\n * Get the named module's accumulator slot (creating it lazily). The\n * slot is whatever shape the module wrote; type-safe access is the\n * module author's responsibility — typically via a typed `get<T>()`\n * wrapper exported alongside the module.\n */\n getSlot<T>(moduleName: string, init: () => T): T\n /** Record a diagnostic. The diagnostic's `id` should match one declared in `DiagnosticDefinition[]`. */\n reportDiagnostic(d: Diagnostic): void\n}\n\nexport interface EmissionContribution {\n /** Module emitting this contribution — used for conflict reporting. */\n module: string\n /** Field name on the `ComponentDef` object literal (e.g. `__msgSchema`). */\n field: string\n /** AST expression to assign. The umbrella merges into the component()'s config arg. */\n value: ts.Expression\n /**\n * Optional per-call target. When set, this contribution applies only\n * to the named `component()` call expression; the umbrella's\n * emission-merger writes the field into that call's config-arg\n * object literal. When omitted, the contribution is *file-global*:\n * the merger writes the field into every `component()` call in the\n * file (the common case — `__msgSchema`, `__prefixes`, `__schemaHash`\n * are file-shape-derived).\n *\n * Per-call target is needed for `__componentMeta` (file + line vary\n * per call site) and any other field whose value depends on the\n * specific `component()` call location.\n *\n * Conflict-detection runs per-(field, target) tuple — two modules\n * may both contribute `__custom` if they target *different* call\n * expressions; same target on the same field is still an error.\n */\n target?: ts.CallExpression\n}\n\nexport interface EmissionContext {\n sourceFile: ts.SourceFile\n factory: ts.NodeFactory\n}\n\n/**\n * A compiler module declares:\n * - identification (name, compilerVersion semver against the umbrella);\n * - the diagnostics it can emit (stable IDs);\n * - per-`SyntaxKind` visitor handlers (the walker dispatches each AST\n * node once; every module with a handler for its kind sees it);\n * - optionally, an `emit` function that contributes ComponentDef fields\n * after the walk completes;\n * - optionally, `runtimeImports` declaring which `@llui/dom` symbols\n * its emissions reference.\n */\nexport interface CompilerModule {\n name: string\n /** Semver range against the compiler API. v2c §5. */\n compilerVersion: string\n /** Modules this one depends on. The registry verifies presence at activation. */\n dependsOn?: string[]\n diagnostics: DiagnosticDefinition[]\n /**\n * Optional AST pre-transform. Called once per file BEFORE the\n * visitor walk and emission phase. Returns a (possibly rewritten)\n * SourceFile; the result is threaded through subsequent modules'\n * pre-transforms (in declaration order) and then becomes the file\n * the visitor walks. Use for AST mutations the visitor model can't\n * cleanly express — adjacent statement insertion, wrapping arrow\n * expressions, etc. The agent's connect-pattern pass and the\n * universal handler-tagger are the canonical examples (MODULE-MAPPING.md\n * binding-descriptors entry).\n *\n * Most modules do NOT need this. Visitor + emit is the preferred\n * shape because it composes deterministically across modules without\n * threading a mutable SourceFile through each one. preTransform\n * exists for the cases where AST mutation is unavoidable.\n *\n * The §2.1 \"walker runs once per file\" invariant is preserved: the\n * VISITOR walk runs once. preTransform passes are additional, but\n * they're typically cheap (targeted call-site rewrites, not deep\n * recursive walks) and execute before the single visitor walk.\n */\n preTransform?(ctx: PreTransformContext, sf: ts.SourceFile): ts.SourceFile\n visitors: {\n [K in ts.SyntaxKind]?: (ctx: AnalysisContext, node: ts.Node) => void\n }\n /**\n * Optional per-call AST rewrite, BOTTOM-UP (after children visited).\n * Called once per `CallExpression` during the post-visitor transform\n * phase, AFTER analysis has accumulated findings in\n * `analysis.perModule` AND after `ts.visitEachChild` has recursively\n * rewritten the node's children. Returns either:\n * - `null` — node unchanged; chain continues with the next module's\n * transformCall (if any).\n * - a new `ts.CallExpression` — node replaced; subsequent modules'\n * transformCall hooks see the new node (composes in declaration\n * order, just like preTransform).\n *\n * Use for rewrites that depend on the rewritten children — e.g.\n * row-factory emission inspects the render body for an already-emitted\n * `elTemplate(...)` call, so element rewrites that produce\n * `elTemplate` MUST have fired first. Module authors should treat\n * transformCall as a pure function of its inputs (the node + analysis\n * findings).\n */\n transformCall?(ctx: TransformCallContext, node: ts.CallExpression): ts.CallExpression | null\n /**\n * Optional per-call AST rewrite, TOP-DOWN (before children visited).\n * Mirrors `transformCall` but fires BEFORE `ts.visitEachChild`\n * recurses into the call's children. Use when the rewrite must happen\n * before the children are visited — most commonly when the rewrite\n * changes the call's argument shape and the children's visitor would\n * misinterpret the original shape. Memo-wrapping the `items:`\n * accessor of an `each()` call is the canonical example: the wrapped\n * accessor is what subsequent passes (item-selector dedup, mask\n * injection) read.\n *\n * Both `transformCallEnter` and `transformCall` may be declared by\n * the same module; enter fires top-down before recursion, transformCall\n * fires bottom-up after. Ordering within each direction is declaration\n * order across modules; the two directions never interleave for a\n * given node.\n */\n transformCallEnter?(ctx: TransformCallContext, node: ts.CallExpression): ts.CallExpression | null\n /** Called once per file after the visitor pass completes. Returns this module's emission contributions. */\n emit?(ctx: EmissionContext, analysis: FileAnalysis): EmissionContribution[]\n /** Runtime symbol names this module's emissions reference (from `@llui/dom`). */\n runtimeImports?: string[]\n}\n\nexport interface PreTransformContext {\n factory: ts.NodeFactory\n /**\n * Shared per-file findings accumulator. preTransform passes that\n * need to communicate with their own emit step (e.g. \"this file\n * needed scope-variant registrations\") use this slot map. The same\n * `analysis.perModule` map is later passed to visitors and emit.\n */\n analysis: FileAnalysis\n}\n\n/**\n * Context passed to every `transformCall` invocation. Carries the\n * factory for building new AST nodes and a read-only view of analysis\n * findings (visitors have already completed and populated\n * `analysis.perModule` by the time transformCall fires).\n */\nexport interface TransformCallContext {\n factory: ts.NodeFactory\n /** Read-only access to visitor-phase findings. */\n analysis: FileAnalysis\n}\n\n// ── Registry ────────────────────────────────────────────────────────\n\nexport interface RegistryRunResult {\n analysis: FileAnalysis\n emissions: EmissionContribution[]\n /** Union of runtime imports from every active module. */\n runtimeImports: string[]\n}\n\n/**\n * The visitor registry. Built once per compiler boot from the user's\n * `llui.config.ts` `modules: [...]` array; the umbrella's per-file\n * pipeline calls `run(sourceFile, checker)` to drive a complete pass.\n */\nexport class ModuleRegistry {\n private readonly modules: ReadonlyArray<CompilerModule>\n /** Pre-indexed by SyntaxKind for O(1) dispatch. */\n private readonly visitorsByKind: Map<ts.SyntaxKind, Array<CompilerModule>>\n\n constructor(modules: ReadonlyArray<CompilerModule>) {\n this.modules = modules\n this.verifyDependencies()\n this.visitorsByKind = this.buildVisitorIndex()\n }\n\n private verifyDependencies(): void {\n const present = new Set(this.modules.map((m) => m.name))\n for (const m of this.modules) {\n for (const dep of m.dependsOn ?? []) {\n if (!present.has(dep)) {\n throw new Error(\n `[llui] module \"${m.name}\" depends on \"${dep}\", which is not in the active module list. ` +\n `Add ${dep}() to your llui.config.ts modules array (must appear before \"${m.name}\"). ` +\n `See docs/proposals/v2-compiler/v2c.md §2.4.`,\n )\n }\n }\n }\n }\n\n private buildVisitorIndex(): Map<ts.SyntaxKind, Array<CompilerModule>> {\n const index = new Map<ts.SyntaxKind, Array<CompilerModule>>()\n for (const m of this.modules) {\n for (const kindStr of Object.keys(m.visitors)) {\n const kind = Number(kindStr) as ts.SyntaxKind\n if (!index.has(kind)) index.set(kind, [])\n index.get(kind)!.push(m)\n }\n }\n return index\n }\n\n /**\n * Run a full analysis + emission pass over `sourceFile`. Phases:\n * 1. Pre-transform: each module's `preTransform?` fires in\n * declaration order; the (possibly rewritten) SourceFile flows\n * through subsequent passes.\n * 2. Visitor walk: a single AST walk dispatches each node to every\n * module's matching SyntaxKind handler. Read-only — visitors\n * accumulate findings in `analysis.perModule` but cannot rewrite.\n * 3. Transform: a `ts.transform`-style walk dispatches each\n * `CallExpression` to every module's `transformCallEnter?`\n * (top-down, before children recursion) and `transformCall?`\n * (bottom-up, after children recursion) hooks in declaration\n * order; each hook's return value (if non-null) feeds the next.\n * Composes call-site rewrites without each module paying a\n * whole-file walk cost.\n * 4. Emission: each module's `emit?` fires; the registry merges\n * contributions, detecting (field, target) conflicts.\n */\n run(sourceFile: ts.SourceFile, checker?: ts.TypeChecker): RegistryRunResult {\n const analysis: FileAnalysis = {\n sourceFile,\n perModule: new Map(),\n diagnostics: [],\n }\n\n // Phase 1: pre-transform passes. Threaded SourceFile flows through\n // each module's preTransform in declaration order. Modules without\n // a preTransform pass through.\n let currentSf = sourceFile\n for (const m of this.modules) {\n if (!m.preTransform) continue\n currentSf = m.preTransform(\n {\n factory: ts.factory,\n analysis,\n },\n currentSf,\n )\n }\n analysis.sourceFile = currentSf\n const ctx: AnalysisContext = {\n sourceFile: currentSf,\n checker,\n getSlot: <T>(name: string, init: () => T): T => {\n let slot = analysis.perModule.get(name) as T | undefined\n if (slot === undefined) {\n slot = init()\n analysis.perModule.set(name, slot)\n }\n return slot\n },\n reportDiagnostic: (d) => {\n analysis.diagnostics.push(d)\n },\n }\n\n // Phase 2: single-pass visitor walk over the (possibly\n // pre-transformed) SourceFile.\n const walk = (node: ts.Node): void => {\n const handlers = this.visitorsByKind.get(node.kind)\n if (handlers) {\n for (const m of handlers) {\n const handler = m.visitors[node.kind]\n if (handler) handler(ctx, node)\n }\n }\n ts.forEachChild(node, walk)\n }\n walk(currentSf)\n\n // Phase 2b: per-CallExpression transform. Modules with a\n // `transformCallEnter` (top-down) or `transformCall` (bottom-up)\n // hook get one chance to rewrite each call site per direction;\n // chained in declaration order. The phase is skipped entirely\n // when no module declares either hook (zero overhead for the\n // common case of metadata-only modules).\n //\n // Within a single CallExpression visit:\n // 1. transformCallEnter fires (declaration order) — rewrites\n // the node BEFORE children are recursed; subsequent\n // transformCallEnter hooks see the rewritten node.\n // 2. ts.visitEachChild recurses into the (possibly enter-rewritten)\n // node, visiting children.\n // 3. transformCall fires (declaration order) — rewrites the\n // now-children-rewritten node; subsequent transformCall hooks\n // see the result.\n //\n // The two directions never interleave for a given node: all enters\n // run, then all children visit, then all exits run.\n const enterModules = this.modules.filter((m) => m.transformCallEnter)\n const exitModules = this.modules.filter((m) => m.transformCall)\n if (enterModules.length > 0 || exitModules.length > 0) {\n const transformCtx: TransformCallContext = {\n factory: ts.factory,\n analysis,\n }\n const visit: ts.Visitor = (node) => {\n // Top-down (enter) — fires BEFORE children recursion. Chain\n // composes in declaration order; each enter hook sees the\n // output of the previous one.\n let current = node\n if (ts.isCallExpression(current)) {\n for (const m of enterModules) {\n const replaced = m.transformCallEnter!(transformCtx, current as ts.CallExpression)\n if (replaced) current = replaced\n }\n }\n // Recurse children of the (possibly enter-rewritten) node.\n const visited = ts.visitEachChild(current, visit, undefined!)\n // Bottom-up (exit) — fires AFTER children recursion. Chain\n // composes in declaration order; each exit hook sees the\n // output of the previous one.\n if (ts.isCallExpression(visited)) {\n let result = visited as ts.CallExpression\n for (const m of exitModules) {\n const replaced = m.transformCall!(transformCtx, result)\n if (replaced) result = replaced\n }\n return result\n }\n return visited\n }\n currentSf = ts.visitNode(currentSf, visit) as ts.SourceFile\n analysis.sourceFile = currentSf\n }\n\n // Phase 3: emission. Each module contributes after analysis\n // completes. Conflicts on (field, target) tuples are hard errors\n // per §2.1.\n const emissionCtx: EmissionContext = {\n sourceFile: currentSf,\n factory: ts.factory,\n }\n const emissions: EmissionContribution[] = []\n // Conflict detection keyed by `(field, target)` — two modules may\n // contribute distinct per-target emissions to the same field name\n // (e.g. component-meta for two different `component()` calls in\n // one file), but two emissions with the same target on the same\n // field is the hard error from §2.1.\n //\n // Targets are compared by object identity (not by `pos`/`end`) so\n // synthetic nodes (factory-created with pos=-1) compare correctly.\n // File-global emissions (target=undefined) share one bucket.\n const globalOwnerByField = new Map<string, string>()\n const targetOwnerByField = new Map<ts.CallExpression, Map<string, string>>()\n for (const m of this.modules) {\n if (!m.emit) continue\n const contributions = m.emit(emissionCtx, analysis)\n for (const c of contributions) {\n let owners: Map<string, string>\n if (c.target) {\n let existing = targetOwnerByField.get(c.target)\n if (!existing) {\n existing = new Map()\n targetOwnerByField.set(c.target, existing)\n }\n owners = existing\n } else {\n owners = globalOwnerByField\n }\n const other = owners.get(c.field)\n if (other !== undefined) {\n throw new Error(\n `[llui/module-emission-conflict] Modules \"${other}\" and \"${c.module}\" both ` +\n `contribute to ComponentDef field \"${c.field}\"${c.target ? ' for the same component() call site' : ''}. ` +\n `This is a hard error — each (field, target) pair must be owned by exactly one ` +\n `module. Either deduplicate, or move one emission to a distinct field. See ` +\n `docs/proposals/v2-compiler/v2c.md §2.1.`,\n )\n }\n owners.set(c.field, c.module)\n emissions.push(c)\n }\n }\n\n // Union runtime imports.\n const runtimeImports = new Set<string>()\n for (const m of this.modules) {\n for (const imp of m.runtimeImports ?? []) runtimeImports.add(imp)\n }\n\n return {\n analysis,\n emissions,\n runtimeImports: [...runtimeImports].sort(),\n }\n }\n\n /** Module names in declaration order. Adapters surface this for debug logs / config diagnostics. */\n listModules(): string[] {\n return this.modules.map((m) => m.name)\n }\n\n /** All diagnostic definitions across active modules. Used by adapters to enumerate stable IDs. */\n listDiagnostics(): DiagnosticDefinition[] {\n return this.modules.flatMap((m) => m.diagnostics)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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","sourcesContent":["// CompilerModule + ModuleRegistry — v2c §2 visitor-registry primitive.\n//\n// Modules accumulate findings during a single AST walk per file and\n// contribute emissions after the walk completes. The walker visits each\n// node once; every module registered for that node's SyntaxKind sees it.\n//\n// This file defines the interfaces + the registry. The actual modules\n// (`compiler-core`, `compiler-agent`, `compiler-ssr`, `compiler-devtools`)\n// will consume them; for v2c-partial only the primitive lands, and one\n// proof-of-concept module exercises it.\n//\n// Design contract (v2c.md §2.1):\n// - Modules NEVER walk the AST themselves — only the registry walks.\n// This keeps the cost O(nodes), not O(modules × nodes).\n// - Visitor order for a given SyntaxKind is the declaration order in\n// `llui.config.ts`'s `modules: [...]` array. Observable to module\n// authors. Alphabetical-by-name was rejected (couples correctness\n// to package names).\n// - Emission conflicts (two modules writing to the same field) are\n// a hard error, not a silent overwrite. Each module owns disjoint\n// output fields.\n// - `runtimeImports` arrays merge by union (deduplicated). Multiple\n// modules requesting the same runtime helper collapse to one\n// import.\n\nimport ts from 'typescript'\nimport type { Diagnostic } from './diagnostic.js'\n\n// ── Module interface ────────────────────────────────────────────────\n\nexport interface DiagnosticDefinition {\n /** Stable id, e.g. `llui/opaque-view-call`. Per v2c §3 §8.2. */\n id: string\n /** One-line description; useful for adapter UIs that don't render the message. */\n description: string\n}\n\n/**\n * Per-file analysis output. Modules accumulate findings here during\n * visitor dispatch; emit consumes it. The shape is intentionally\n * open-ended — modules name their own slots and the umbrella's\n * orchestrator never inspects them, only forwards.\n */\nexport interface FileAnalysis {\n /** Source file the analysis ran over. */\n sourceFile: ts.SourceFile\n /** Per-module accumulator buckets, keyed by module name. */\n perModule: Map<string, unknown>\n /** Diagnostics emitted during the walk. */\n diagnostics: Diagnostic[]\n}\n\n/**\n * Context passed to every visitor invocation. Modules use it to record\n * findings, emit diagnostics, and consult shared state (the TS\n * Compiler-API checker, the project root, sibling-module findings if\n * dependencies allow).\n */\n/**\n * Resolved external type sources for the file under analysis. Same\n * shape as `transform.ts`'s `ExternalTypeSources`; declared here as a\n * structural minimum so the module registry doesn't import from the\n * umbrella. The host adapter (vite-plugin) supplies the values via\n * its async cross-file resolver (`findTypeSource`).\n *\n * Always undefined for test-only `transformLlui(source, fileName)`\n * invocations and for lint adapters without import resolution. Modules\n * that consume this should fall back to file-local behaviour when\n * absent.\n */\nexport interface ModuleExternalTypes {\n state?: { source: string; typeName: string }\n msg?: { source: string; typeName: string }\n effect?: { source: string; typeName: string }\n}\n\nexport interface AnalysisContext {\n sourceFile: ts.SourceFile\n /** TS TypeChecker, when the host adapter has built a Program. May be undefined for AST-only paths. */\n checker: ts.TypeChecker | undefined\n /**\n * Get the named module's accumulator slot (creating it lazily). The\n * slot is whatever shape the module wrote; type-safe access is the\n * module author's responsibility — typically via a typed `get<T>()`\n * wrapper exported alongside the module.\n */\n getSlot<T>(moduleName: string, init: () => T): T\n /** Record a diagnostic. The diagnostic's `id` should match one declared in `DiagnosticDefinition[]`. */\n reportDiagnostic(d: Diagnostic): void\n /**\n * External type sources from the host adapter's cross-file resolver.\n * Undefined when the host doesn't supply them (test path, lint-only\n * adapters without import resolution).\n */\n externalTypes?: ModuleExternalTypes\n}\n\nexport interface EmissionContribution {\n /** Module emitting this contribution — used for conflict reporting. */\n module: string\n /** Field name on the `ComponentDef` object literal (e.g. `__msgSchema`). */\n field: string\n /** AST expression to assign. The umbrella merges into the component()'s config arg. */\n value: ts.Expression\n /**\n * Optional per-call target. When set, this contribution applies only\n * to the named `component()` call expression; the umbrella's\n * emission-merger writes the field into that call's config-arg\n * object literal. When omitted, the contribution is *file-global*:\n * the merger writes the field into every `component()` call in the\n * file (the common case — `__msgSchema`, `__prefixes`, `__schemaHash`\n * are file-shape-derived).\n *\n * Per-call target is needed for `__componentMeta` (file + line vary\n * per call site) and any other field whose value depends on the\n * specific `component()` call location.\n *\n * Conflict-detection runs per-(field, target) tuple — two modules\n * may both contribute `__custom` if they target *different* call\n * expressions; same target on the same field is still an error.\n */\n target?: ts.CallExpression\n}\n\nexport interface EmissionContext {\n sourceFile: ts.SourceFile\n factory: ts.NodeFactory\n}\n\n/**\n * A compiler module declares:\n * - identification (name, compilerVersion semver against the umbrella);\n * - the diagnostics it can emit (stable IDs);\n * - per-`SyntaxKind` visitor handlers (the walker dispatches each AST\n * node once; every module with a handler for its kind sees it);\n * - optionally, an `emit` function that contributes ComponentDef fields\n * after the walk completes;\n * - optionally, `runtimeImports` declaring which `@llui/dom` symbols\n * its emissions reference.\n */\nexport interface CompilerModule {\n name: string\n /** Semver range against the compiler API. v2c §5. */\n compilerVersion: string\n /** Modules this one depends on. The registry verifies presence at activation. */\n dependsOn?: string[]\n diagnostics: DiagnosticDefinition[]\n /**\n * Optional AST pre-transform. Called once per file BEFORE the\n * visitor walk and emission phase. Returns a (possibly rewritten)\n * SourceFile; the result is threaded through subsequent modules'\n * pre-transforms (in declaration order) and then becomes the file\n * the visitor walks. Use for AST mutations the visitor model can't\n * cleanly express — adjacent statement insertion, wrapping arrow\n * expressions, etc. The agent's connect-pattern pass and the\n * universal handler-tagger are the canonical examples (MODULE-MAPPING.md\n * binding-descriptors entry).\n *\n * Most modules do NOT need this. Visitor + emit is the preferred\n * shape because it composes deterministically across modules without\n * threading a mutable SourceFile through each one. preTransform\n * exists for the cases where AST mutation is unavoidable.\n *\n * The §2.1 \"walker runs once per file\" invariant is preserved: the\n * VISITOR walk runs once. preTransform passes are additional, but\n * they're typically cheap (targeted call-site rewrites, not deep\n * recursive walks) and execute before the single visitor walk.\n */\n preTransform?(ctx: PreTransformContext, sf: ts.SourceFile): ts.SourceFile\n visitors: {\n [K in ts.SyntaxKind]?: (ctx: AnalysisContext, node: ts.Node) => void\n }\n /**\n * Optional per-call AST rewrite, BOTTOM-UP (after children visited).\n * Called once per `CallExpression` during the post-visitor transform\n * phase, AFTER analysis has accumulated findings in\n * `analysis.perModule` AND after `ts.visitEachChild` has recursively\n * rewritten the node's children. Returns either:\n * - `null` — node unchanged; chain continues with the next module's\n * transformCall (if any).\n * - a new `ts.CallExpression` — node replaced; subsequent modules'\n * transformCall hooks see the new node (composes in declaration\n * order, just like preTransform).\n *\n * Use for rewrites that depend on the rewritten children — e.g.\n * row-factory emission inspects the render body for an already-emitted\n * `elTemplate(...)` call, so element rewrites that produce\n * `elTemplate` MUST have fired first. Module authors should treat\n * transformCall as a pure function of its inputs (the node + analysis\n * findings).\n */\n transformCall?(ctx: TransformCallContext, node: ts.CallExpression): ts.CallExpression | null\n /**\n * Optional per-call AST rewrite, TOP-DOWN (before children visited).\n * Mirrors `transformCall` but fires BEFORE `ts.visitEachChild`\n * recurses into the call's children. Use when the rewrite must happen\n * before the children are visited — most commonly when the rewrite\n * changes the call's argument shape and the children's visitor would\n * misinterpret the original shape. Memo-wrapping the `items:`\n * accessor of an `each()` call is the canonical example: the wrapped\n * accessor is what subsequent passes (item-selector dedup, mask\n * injection) read.\n *\n * Both `transformCallEnter` and `transformCall` may be declared by\n * the same module; enter fires top-down before recursion, transformCall\n * fires bottom-up after. Ordering within each direction is declaration\n * order across modules; the two directions never interleave for a\n * given node.\n */\n transformCallEnter?(ctx: TransformCallContext, node: ts.CallExpression): ts.CallExpression | null\n /** Called once per file after the visitor pass completes. Returns this module's emission contributions. */\n emit?(ctx: EmissionContext, analysis: FileAnalysis): EmissionContribution[]\n /** Runtime symbol names this module's emissions reference (from `@llui/dom`). */\n runtimeImports?: string[]\n}\n\nexport interface PreTransformContext {\n factory: ts.NodeFactory\n /**\n * Shared per-file findings accumulator. preTransform passes that\n * need to communicate with their own emit step (e.g. \"this file\n * needed scope-variant registrations\") use this slot map. The same\n * `analysis.perModule` map is later passed to visitors and emit.\n */\n analysis: FileAnalysis\n}\n\n/**\n * Context passed to every `transformCall` invocation. Carries the\n * factory for building new AST nodes and a read-only view of analysis\n * findings (visitors have already completed and populated\n * `analysis.perModule` by the time transformCall fires).\n */\nexport interface TransformCallContext {\n factory: ts.NodeFactory\n /** Read-only access to visitor-phase findings. */\n analysis: FileAnalysis\n}\n\n// ── Registry ────────────────────────────────────────────────────────\n\nexport interface RegistryRunResult {\n analysis: FileAnalysis\n emissions: EmissionContribution[]\n /** Union of runtime imports from every active module. */\n runtimeImports: string[]\n}\n\n/**\n * The visitor registry. Built once per compiler boot from the user's\n * `llui.config.ts` `modules: [...]` array; the umbrella's per-file\n * pipeline calls `run(sourceFile, checker)` to drive a complete pass.\n */\nexport class ModuleRegistry {\n private readonly modules: ReadonlyArray<CompilerModule>\n /** Pre-indexed by SyntaxKind for O(1) dispatch. */\n private readonly visitorsByKind: Map<ts.SyntaxKind, Array<CompilerModule>>\n\n constructor(modules: ReadonlyArray<CompilerModule>) {\n this.modules = modules\n this.verifyDependencies()\n this.visitorsByKind = this.buildVisitorIndex()\n }\n\n private verifyDependencies(): void {\n const present = new Set(this.modules.map((m) => m.name))\n for (const m of this.modules) {\n for (const dep of m.dependsOn ?? []) {\n if (!present.has(dep)) {\n throw new Error(\n `[llui] module \"${m.name}\" depends on \"${dep}\", which is not in the active module list. ` +\n `Add ${dep}() to your llui.config.ts modules array (must appear before \"${m.name}\"). ` +\n `See docs/proposals/v2-compiler/v2c.md §2.4.`,\n )\n }\n }\n }\n }\n\n private buildVisitorIndex(): Map<ts.SyntaxKind, Array<CompilerModule>> {\n const index = new Map<ts.SyntaxKind, Array<CompilerModule>>()\n for (const m of this.modules) {\n for (const kindStr of Object.keys(m.visitors)) {\n const kind = Number(kindStr) as ts.SyntaxKind\n if (!index.has(kind)) index.set(kind, [])\n index.get(kind)!.push(m)\n }\n }\n return index\n }\n\n /**\n * Run a full analysis + emission pass over `sourceFile`. Phases:\n * 1. Pre-transform: each module's `preTransform?` fires in\n * declaration order; the (possibly rewritten) SourceFile flows\n * through subsequent passes.\n * 2. Visitor walk: a single AST walk dispatches each node to every\n * module's matching SyntaxKind handler. Read-only — visitors\n * accumulate findings in `analysis.perModule` but cannot rewrite.\n * 3. Transform: a `ts.transform`-style walk dispatches each\n * `CallExpression` to every module's `transformCallEnter?`\n * (top-down, before children recursion) and `transformCall?`\n * (bottom-up, after children recursion) hooks in declaration\n * order; each hook's return value (if non-null) feeds the next.\n * Composes call-site rewrites without each module paying a\n * whole-file walk cost.\n * 4. Emission: each module's `emit?` fires; the registry merges\n * contributions, detecting (field, target) conflicts.\n */\n run(\n sourceFile: ts.SourceFile,\n checker?: ts.TypeChecker,\n externalTypes?: ModuleExternalTypes,\n ): RegistryRunResult {\n const analysis: FileAnalysis = {\n sourceFile,\n perModule: new Map(),\n diagnostics: [],\n }\n\n // Phase 1: pre-transform passes. Threaded SourceFile flows through\n // each module's preTransform in declaration order. Modules without\n // a preTransform pass through.\n let currentSf = sourceFile\n for (const m of this.modules) {\n if (!m.preTransform) continue\n currentSf = m.preTransform(\n {\n factory: ts.factory,\n analysis,\n },\n currentSf,\n )\n }\n analysis.sourceFile = currentSf\n const ctx: AnalysisContext = {\n sourceFile: currentSf,\n checker,\n getSlot: <T>(name: string, init: () => T): T => {\n let slot = analysis.perModule.get(name) as T | undefined\n if (slot === undefined) {\n slot = init()\n analysis.perModule.set(name, slot)\n }\n return slot\n },\n reportDiagnostic: (d) => {\n analysis.diagnostics.push(d)\n },\n externalTypes,\n }\n\n // Phase 2: single-pass visitor walk over the (possibly\n // pre-transformed) SourceFile.\n const walk = (node: ts.Node): void => {\n const handlers = this.visitorsByKind.get(node.kind)\n if (handlers) {\n for (const m of handlers) {\n const handler = m.visitors[node.kind]\n if (handler) handler(ctx, node)\n }\n }\n ts.forEachChild(node, walk)\n }\n walk(currentSf)\n\n // Phase 2b: per-CallExpression transform. Modules with a\n // `transformCallEnter` (top-down) or `transformCall` (bottom-up)\n // hook get one chance to rewrite each call site per direction;\n // chained in declaration order. The phase is skipped entirely\n // when no module declares either hook (zero overhead for the\n // common case of metadata-only modules).\n //\n // Within a single CallExpression visit:\n // 1. transformCallEnter fires (declaration order) — rewrites\n // the node BEFORE children are recursed; subsequent\n // transformCallEnter hooks see the rewritten node.\n // 2. ts.visitEachChild recurses into the (possibly enter-rewritten)\n // node, visiting children.\n // 3. transformCall fires (declaration order) — rewrites the\n // now-children-rewritten node; subsequent transformCall hooks\n // see the result.\n //\n // The two directions never interleave for a given node: all enters\n // run, then all children visit, then all exits run.\n const enterModules = this.modules.filter((m) => m.transformCallEnter)\n const exitModules = this.modules.filter((m) => m.transformCall)\n if (enterModules.length > 0 || exitModules.length > 0) {\n const transformCtx: TransformCallContext = {\n factory: ts.factory,\n analysis,\n }\n const visit: ts.Visitor = (node) => {\n // Top-down (enter) — fires BEFORE children recursion. Chain\n // composes in declaration order; each enter hook sees the\n // output of the previous one.\n let current = node\n if (ts.isCallExpression(current)) {\n for (const m of enterModules) {\n const replaced = m.transformCallEnter!(transformCtx, current as ts.CallExpression)\n if (replaced) current = replaced\n }\n }\n // Recurse children of the (possibly enter-rewritten) node.\n const visited = ts.visitEachChild(current, visit, undefined!)\n // Bottom-up (exit) — fires AFTER children recursion. Chain\n // composes in declaration order; each exit hook sees the\n // output of the previous one.\n if (ts.isCallExpression(visited)) {\n let result = visited as ts.CallExpression\n for (const m of exitModules) {\n const replaced = m.transformCall!(transformCtx, result)\n if (replaced) result = replaced\n }\n return result\n }\n return visited\n }\n currentSf = ts.visitNode(currentSf, visit) as ts.SourceFile\n analysis.sourceFile = currentSf\n }\n\n // Phase 3: emission. Each module contributes after analysis\n // completes. Conflicts on (field, target) tuples are hard errors\n // per §2.1.\n const emissionCtx: EmissionContext = {\n sourceFile: currentSf,\n factory: ts.factory,\n }\n const emissions: EmissionContribution[] = []\n // Conflict detection keyed by `(field, target)` — two modules may\n // contribute distinct per-target emissions to the same field name\n // (e.g. component-meta for two different `component()` calls in\n // one file), but two emissions with the same target on the same\n // field is the hard error from §2.1.\n //\n // Targets are compared by object identity (not by `pos`/`end`) so\n // synthetic nodes (factory-created with pos=-1) compare correctly.\n // File-global emissions (target=undefined) share one bucket.\n const globalOwnerByField = new Map<string, string>()\n const targetOwnerByField = new Map<ts.CallExpression, Map<string, string>>()\n for (const m of this.modules) {\n if (!m.emit) continue\n const contributions = m.emit(emissionCtx, analysis)\n for (const c of contributions) {\n let owners: Map<string, string>\n if (c.target) {\n let existing = targetOwnerByField.get(c.target)\n if (!existing) {\n existing = new Map()\n targetOwnerByField.set(c.target, existing)\n }\n owners = existing\n } else {\n owners = globalOwnerByField\n }\n const other = owners.get(c.field)\n if (other !== undefined) {\n throw new Error(\n `[llui/module-emission-conflict] Modules \"${other}\" and \"${c.module}\" both ` +\n `contribute to ComponentDef field \"${c.field}\"${c.target ? ' for the same component() call site' : ''}. ` +\n `This is a hard error — each (field, target) pair must be owned by exactly one ` +\n `module. Either deduplicate, or move one emission to a distinct field. See ` +\n `docs/proposals/v2-compiler/v2c.md §2.1.`,\n )\n }\n owners.set(c.field, c.module)\n emissions.push(c)\n }\n }\n\n // Union runtime imports.\n const runtimeImports = new Set<string>()\n for (const m of this.modules) {\n for (const imp of m.runtimeImports ?? []) runtimeImports.add(imp)\n }\n\n return {\n analysis,\n emissions,\n runtimeImports: [...runtimeImports].sort(),\n }\n }\n\n /** Module names in declaration order. Adapters surface this for debug logs / config diagnostics. */\n listModules(): string[] {\n return this.modules.map((m) => m.name)\n }\n\n /** All diagnostic definitions across active modules. Used by adapters to enumerate stable IDs. */\n listDiagnostics(): DiagnosticDefinition[] {\n return this.modules.flatMap((m) => m.diagnostics)\n }\n}\n"]}
|
|
@@ -5,6 +5,16 @@ export interface MsgVariant {
|
|
|
5
5
|
leadingCommentText: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function forEachMsgVariant(sf: ts.SourceFile, callback: (v: MsgVariant) => void): void;
|
|
8
|
+
/**
|
|
9
|
+
* Iterate variants of a SPECIFIC named Msg alias in an arbitrary
|
|
10
|
+
* source file. Used by lint modules that have a cross-file Msg source
|
|
11
|
+
* (typically the file declaring an imported `type Msg`) and need to
|
|
12
|
+
* iterate its variants in addition to whatever the file-local pass
|
|
13
|
+
* found. The alias name is required because the importing file may
|
|
14
|
+
* rename the alias; pass the alias name as it appears in the
|
|
15
|
+
* declaring file.
|
|
16
|
+
*/
|
|
17
|
+
export declare function forEachMsgVariantInExternalSource(externalSource: string, externalFileName: string, aliasName: string, callback: (v: MsgVariant) => void): void;
|
|
8
18
|
/** Variant has at least one property beyond `type`. */
|
|
9
19
|
export declare function variantHasPayload(node: ts.TypeLiteralNode): boolean;
|
|
10
20
|
//# sourceMappingURL=_msg-variants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_msg-variants.d.ts","sourceRoot":"","sources":["../../src/modules/_msg-variants.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,EAAE,CAAC,eAAe,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAoCD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"_msg-variants.d.ts","sourceRoot":"","sources":["../../src/modules/_msg-variants.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,EAAE,CAAC,eAAe,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAoCD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAG5F;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,cAAc,EAAE,MAAM,EACtB,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,GAChC,IAAI,CAIN;AA+BD,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,GAAG,OAAO,CAQnE"}
|
|
@@ -55,11 +55,28 @@ function collectComponentMsgArgNames(sf) {
|
|
|
55
55
|
}
|
|
56
56
|
export function forEachMsgVariant(sf, callback) {
|
|
57
57
|
const componentMsgNames = collectComponentMsgArgNames(sf);
|
|
58
|
+
forEachMsgVariantIn(sf, componentMsgNames, callback);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Iterate variants of a SPECIFIC named Msg alias in an arbitrary
|
|
62
|
+
* source file. Used by lint modules that have a cross-file Msg source
|
|
63
|
+
* (typically the file declaring an imported `type Msg`) and need to
|
|
64
|
+
* iterate its variants in addition to whatever the file-local pass
|
|
65
|
+
* found. The alias name is required because the importing file may
|
|
66
|
+
* rename the alias; pass the alias name as it appears in the
|
|
67
|
+
* declaring file.
|
|
68
|
+
*/
|
|
69
|
+
export function forEachMsgVariantInExternalSource(externalSource, externalFileName, aliasName, callback) {
|
|
70
|
+
const sf = ts.createSourceFile(externalFileName, externalSource, ts.ScriptTarget.Latest, true);
|
|
71
|
+
// Tight match: ONLY iterate the alias whose name was passed in.
|
|
72
|
+
forEachMsgVariantIn(sf, new Set([aliasName]), callback);
|
|
73
|
+
}
|
|
74
|
+
function forEachMsgVariantIn(sf, matchAliasNames, callback) {
|
|
58
75
|
for (const stmt of sf.statements) {
|
|
59
76
|
if (!ts.isTypeAliasDeclaration(stmt))
|
|
60
77
|
continue;
|
|
61
78
|
const aliasName = stmt.name.text;
|
|
62
|
-
if (aliasName !== 'Msg' && !
|
|
79
|
+
if (aliasName !== 'Msg' && !matchAliasNames.has(aliasName))
|
|
63
80
|
continue;
|
|
64
81
|
const ann = stmt.type;
|
|
65
82
|
if (!ts.isUnionTypeNode(ann))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_msg-variants.js","sourceRoot":"","sources":["../../src/modules/_msg-variants.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,iEAAiE;AACjE,uEAAuE;AACvE,2BAA2B;AAC3B,EAAE;AACF,yCAAyC;AACzC,wEAAwE;AACxE,4DAA4D;AAC5D,EAAE;AACF,sEAAsE;AACtE,qEAAqE;AACrE,iDAAiD;AACjD,EAAE;AACF,kCAAkC;AAClC,sEAAsE;AACtE,wDAAwD;AACxD,kEAAkE;AAClE,mEAAmE;AACnE,qEAAqE;AACrE,oDAAoD;AAEpD,OAAO,EAAE,MAAM,YAAY,CAAA;AAQ3B,SAAS,uBAAuB,CAAC,OAA2B;IAC1D,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAAE,SAAQ;QACxC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,SAAQ;QAC3E,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAA;QAClB,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC;YAAE,SAAQ;QAChD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAA;QACvB,IAAI,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,IAAI,CAAA;IAC9C,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,wFAAwF;AACxF,SAAS,2BAA2B,CAAC,EAAiB;IACpD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;QAChC,IACE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACtB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;YAC7B,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW;YACjC,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,EAC3B,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAE,CAAA;YAClC,IAAI,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAC/B,CAAC;QACH,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAC1B,CAAC,CAAA;IACD,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAiB,EAAE,QAAiC;IACpF,MAAM,iBAAiB,GAAG,2BAA2B,CAAC,EAAE,CAAC,CAAA;IACzD,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;QAChC,IAAI,SAAS,KAAK,KAAK,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"_msg-variants.js","sourceRoot":"","sources":["../../src/modules/_msg-variants.ts"],"names":[],"mappings":"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","sourcesContent":["// Shared helper for agent-protocol rules that iterate Msg union\n// variants and inspect leading JSDoc-style comments on each. The\n// compiler doesn't run typed-lint, so \"is this a Msg union?\" detection\n// uses simpler heuristics:\n//\n// 1. Type alias literally named `Msg`.\n// 2. Any type-alias whose name appears as the SECOND generic argument\n// of a `component<S, Msg, E>()` call in the same file.\n//\n// File-local only. Cross-file Msg detection would need the cross-file\n// resolver; for the v1 of these rules we accept the file-local scope\n// (matches the original Vite-plugin diagnostic).\n//\n// Each variant callback receives:\n// - `variant`: the discriminant string (the `type: '...'` literal).\n// - `node`: the TypeLiteral AST node for the variant.\n// - `leadingCommentText`: the source slice between the previous\n// variant's end (or the alias start) and this variant's start.\n// Includes JSDoc blocks, `|` tokens, and whitespace; regex-match\n// against it the same way the ESLint rules did.\n\nimport ts from 'typescript'\n\nexport interface MsgVariant {\n variant: string\n node: ts.TypeLiteralNode\n leadingCommentText: string\n}\n\nfunction readDiscriminantLiteral(typeLit: ts.TypeLiteralNode): string | null {\n for (const m of typeLit.members) {\n if (!ts.isPropertySignature(m)) continue\n if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type') continue\n const ann = m.type\n if (!ann || !ts.isLiteralTypeNode(ann)) continue\n const lit = ann.literal\n if (ts.isStringLiteral(lit)) return lit.text\n }\n return null\n}\n\n/** Names of type aliases that appear as the 2nd generic arg of `component<S,M,E>()`. */\nfunction collectComponentMsgArgNames(sf: ts.SourceFile): Set<string> {\n const out = new Set<string>()\n const walk = (n: ts.Node): void => {\n if (\n ts.isCallExpression(n) &&\n ts.isIdentifier(n.expression) &&\n n.expression.text === 'component' &&\n n.typeArguments &&\n n.typeArguments.length >= 2\n ) {\n const msgArg = n.typeArguments[1]!\n if (ts.isTypeReferenceNode(msgArg) && ts.isIdentifier(msgArg.typeName)) {\n out.add(msgArg.typeName.text)\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n return out\n}\n\nexport function forEachMsgVariant(sf: ts.SourceFile, callback: (v: MsgVariant) => void): void {\n const componentMsgNames = collectComponentMsgArgNames(sf)\n forEachMsgVariantIn(sf, componentMsgNames, callback)\n}\n\n/**\n * Iterate variants of a SPECIFIC named Msg alias in an arbitrary\n * source file. Used by lint modules that have a cross-file Msg source\n * (typically the file declaring an imported `type Msg`) and need to\n * iterate its variants in addition to whatever the file-local pass\n * found. The alias name is required because the importing file may\n * rename the alias; pass the alias name as it appears in the\n * declaring file.\n */\nexport function forEachMsgVariantInExternalSource(\n externalSource: string,\n externalFileName: string,\n aliasName: string,\n callback: (v: MsgVariant) => void,\n): void {\n const sf = ts.createSourceFile(externalFileName, externalSource, ts.ScriptTarget.Latest, true)\n // Tight match: ONLY iterate the alias whose name was passed in.\n forEachMsgVariantIn(sf, new Set([aliasName]), callback)\n}\n\nfunction forEachMsgVariantIn(\n sf: ts.SourceFile,\n matchAliasNames: Set<string>,\n callback: (v: MsgVariant) => void,\n): void {\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n const aliasName = stmt.name.text\n if (aliasName !== 'Msg' && !matchAliasNames.has(aliasName)) continue\n const ann = stmt.type\n if (!ts.isUnionTypeNode(ann)) continue\n let prevEnd = stmt.getStart(sf)\n for (const member of ann.types) {\n if (!ts.isTypeLiteralNode(member)) {\n prevEnd = member.getEnd()\n continue\n }\n const variant = readDiscriminantLiteral(member)\n if (!variant) {\n prevEnd = member.getEnd()\n continue\n }\n const leadingCommentText = sf.text.slice(prevEnd, member.getStart(sf))\n callback({ variant, node: member, leadingCommentText })\n prevEnd = member.getEnd()\n }\n }\n}\n\n/** Variant has at least one property beyond `type`. */\nexport function variantHasPayload(node: ts.TypeLiteralNode): boolean {\n for (const m of node.members) {\n if (!ts.isPropertySignature(m)) continue\n if (!m.name || !ts.isIdentifier(m.name)) continue\n if (m.name.text === 'type') continue\n return true\n }\n return false\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-emits-drift.d.ts","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAmFlD,wBAAgB,qBAAqB,IAAI,cAAc,
|
|
1
|
+
{"version":3,"file":"agent-emits-drift.d.ts","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAmFlD,wBAAgB,qBAAqB,IAAI,cAAc,CAmHtD"}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// `@llui/eslint-plugin/src/rules/agent-emits-drift.ts`.
|
|
16
16
|
import ts from 'typescript';
|
|
17
17
|
import { rangeFromOffsets } from '../diagnostic.js';
|
|
18
|
-
import { forEachMsgVariant } from './_msg-variants.js';
|
|
18
|
+
import { forEachMsgVariant, forEachMsgVariantInExternalSource } from './_msg-variants.js';
|
|
19
19
|
function readEmits(commentText) {
|
|
20
20
|
const outer = commentText.match(/@emits\s*\(([^)]*)\)/);
|
|
21
21
|
if (!outer || outer[1] === undefined)
|
|
@@ -108,11 +108,22 @@ export function agentEmitsDriftModule() {
|
|
|
108
108
|
indexSwitchCases(sf, caseInfoByVariant);
|
|
109
109
|
if (caseInfoByVariant.size === 0)
|
|
110
110
|
return;
|
|
111
|
-
|
|
111
|
+
// Driver that runs the drift-check on a single Msg variant.
|
|
112
|
+
// Hoisted so we can call it for both file-local variants and
|
|
113
|
+
// cross-file (imported) Msg variants without duplicating the
|
|
114
|
+
// diff logic.
|
|
115
|
+
const check = (variant, typeLit, leadingCommentText, variantSf) => {
|
|
112
116
|
const declared = readEmits(leadingCommentText);
|
|
113
117
|
const caseInfo = caseInfoByVariant.get(variant);
|
|
114
118
|
if (!caseInfo)
|
|
115
119
|
return;
|
|
120
|
+
// Anchor diagnostics on the local file when the Msg variant
|
|
121
|
+
// lives in the same file; on the external Msg file when it's
|
|
122
|
+
// imported. Adapters that surface diagnostics by file
|
|
123
|
+
// (vite-plugin's this.error) will route the error to the
|
|
124
|
+
// right place.
|
|
125
|
+
const anchorFile = variantSf.fileName;
|
|
126
|
+
const anchorText = variantSf.text;
|
|
116
127
|
// Drift 1: literal emissions not in @emits — always warn.
|
|
117
128
|
for (const kind of caseInfo.literalKinds) {
|
|
118
129
|
if (!declared.includes(kind)) {
|
|
@@ -124,8 +135,8 @@ export function agentEmitsDriftModule() {
|
|
|
124
135
|
`declare it in @emits. Either add "${kind}" to the @emits list ` +
|
|
125
136
|
`(\`@emits("${kind}")\`), or remove the literal effect emission.`,
|
|
126
137
|
location: {
|
|
127
|
-
file:
|
|
128
|
-
range: rangeFromOffsets(
|
|
138
|
+
file: anchorFile,
|
|
139
|
+
range: rangeFromOffsets(anchorText, typeLit.getStart(variantSf), typeLit.getEnd()),
|
|
129
140
|
},
|
|
130
141
|
});
|
|
131
142
|
}
|
|
@@ -143,14 +154,33 @@ export function agentEmitsDriftModule() {
|
|
|
143
154
|
`emits it as a literal effect. Either remove "${kind}" from @emits, or add ` +
|
|
144
155
|
`the emission (\`return [state, [{ kind: '${kind}', … }]]\`).`,
|
|
145
156
|
location: {
|
|
146
|
-
file:
|
|
147
|
-
range: rangeFromOffsets(
|
|
157
|
+
file: anchorFile,
|
|
158
|
+
range: rangeFromOffsets(anchorText, typeLit.getStart(variantSf), typeLit.getEnd()),
|
|
148
159
|
},
|
|
149
160
|
});
|
|
150
161
|
}
|
|
151
162
|
}
|
|
152
163
|
}
|
|
164
|
+
};
|
|
165
|
+
// File-local Msg variants — the common case.
|
|
166
|
+
forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {
|
|
167
|
+
check(variant, typeLit, leadingCommentText, sf);
|
|
153
168
|
});
|
|
169
|
+
// Cross-file Msg: when `component<S, ImportedMsg, E>()` resolved
|
|
170
|
+
// the M type arg to another file, the host adapter passes the
|
|
171
|
+
// declaring source here. Iterate its variants too so @emits
|
|
172
|
+
// drift surfaces against imported Msg unions.
|
|
173
|
+
const externalMsg = ctx.externalTypes?.msg;
|
|
174
|
+
if (externalMsg) {
|
|
175
|
+
forEachMsgVariantInExternalSource(externalMsg.source,
|
|
176
|
+
// Synthetic filename — adapters that report by file get a
|
|
177
|
+
// recognisable marker. The vite-plugin doesn't currently
|
|
178
|
+
// map this back to the real on-disk path; future work.
|
|
179
|
+
`<external:${externalMsg.typeName}>`, externalMsg.typeName, ({ variant, node: typeLit, leadingCommentText }) => {
|
|
180
|
+
const externalSf = typeLit.getSourceFile();
|
|
181
|
+
check(variant, typeLit, leadingCommentText, externalSf);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
154
184
|
},
|
|
155
185
|
},
|
|
156
186
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-emits-drift.js","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"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","sourcesContent":["// `agent-emits-drift` — verifies `@emits(\"k1\", \"k2\")` declarations on\n// Msg variants match the effect kinds the corresponding case in\n// update() actually emits.\n//\n// Two failure modes detected:\n// 1. **Emitted but not declared** — a literal `{ kind: 'X' }` appears\n// in the case's effect array but 'X' isn't in @emits.\n// 2. **Declared but not emitted** — a kind in @emits doesn't appear in\n// any literal effect (and the case has no opaque helper calls that\n// might emit it).\n//\n// File-local scope: Msg union and the update() switch must be in the\n// same source file. Cross-file resolution would require type-checker\n// integration. Migrated from\n// `@llui/eslint-plugin/src/rules/agent-emits-drift.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { forEachMsgVariant } from './_msg-variants.js'\n\ninterface CaseInfo {\n literalKinds: Set<string>\n hasOpaqueHelpers: boolean\n}\n\nfunction readEmits(commentText: string): string[] {\n const outer = commentText.match(/@emits\\s*\\(([^)]*)\\)/)\n if (!outer || outer[1] === undefined) return []\n const inner = outer[1]\n const seen = new Set<string>()\n const out: string[] = []\n const re = /[\"“]([^\"”]*)[\"”]/g\n let m: RegExpExecArray | null\n while ((m = re.exec(inner)) !== null) {\n if (m[1] !== undefined && !seen.has(m[1])) {\n seen.add(m[1])\n out.push(m[1])\n }\n }\n return out\n}\n\nfunction walkCaseBody(body: ts.Node, info: CaseInfo): void {\n const walk = (n: ts.Node): void => {\n if (ts.isReturnStatement(n) && n.expression) {\n const arg = n.expression\n if (ts.isArrayLiteralExpression(arg) && arg.elements.length === 2) {\n const effects = arg.elements[1]\n if (effects && ts.isArrayLiteralExpression(effects)) {\n for (const el of effects.elements) {\n if (ts.isObjectLiteralExpression(el)) {\n for (const prop of el.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'kind' &&\n ts.isStringLiteral(prop.initializer)\n ) {\n info.literalKinds.add(prop.initializer.text)\n }\n }\n } else if (ts.isCallExpression(el) || ts.isSpreadElement(el)) {\n info.hasOpaqueHelpers = true\n }\n }\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n}\n\nfunction indexSwitchCases(sf: ts.SourceFile, into: Map<string, CaseInfo>): void {\n const walk = (n: ts.Node): void => {\n if (ts.isSwitchStatement(n)) {\n const disc = n.expression\n const looksLikeMsgType =\n ts.isPropertyAccessExpression(disc) &&\n ts.isIdentifier(disc.name) &&\n disc.name.text === 'type'\n if (looksLikeMsgType) {\n for (const sc of n.caseBlock.clauses) {\n if (!ts.isCaseClause(sc)) continue\n if (!sc.expression || !ts.isStringLiteral(sc.expression)) continue\n const variant = sc.expression.text\n const info: CaseInfo = into.get(variant) ?? {\n literalKinds: new Set(),\n hasOpaqueHelpers: false,\n }\n for (const stmt of sc.statements) walkCaseBody(stmt, info)\n into.set(variant, info)\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n}\n\nexport function agentEmitsDriftModule(): CompilerModule {\n return {\n name: 'agent-emits-drift',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/agent-emits-drift',\n description:\n '@emits declaration on Msg variant drifts from actual effect emissions in update().',\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n const visited = node as ts.SourceFile\n const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n const caseInfoByVariant = new Map<string, CaseInfo>()\n indexSwitchCases(sf, caseInfoByVariant)\n if (caseInfoByVariant.size === 0) return\n forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {\n const declared = readEmits(leadingCommentText)\n const caseInfo = caseInfoByVariant.get(variant)\n if (!caseInfo) return\n // Drift 1: literal emissions not in @emits — always warn.\n for (const kind of caseInfo.literalKinds) {\n if (!declared.includes(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" emits effect kind \"${kind}\" in update() but doesn't ` +\n `declare it in @emits. Either add \"${kind}\" to the @emits list ` +\n `(\\`@emits(\"${kind}\")\\`), or remove the literal effect emission.`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n }\n }\n // Drift 2: @emits-declared but no literal emission in the case.\n // Skip when opaque helpers might be emitting the kind.\n if (!caseInfo.hasOpaqueHelpers) {\n for (const kind of declared) {\n if (!caseInfo.literalKinds.has(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" declares @emits \"${kind}\" but no case in update() ` +\n `emits it as a literal effect. Either remove \"${kind}\" from @emits, or add ` +\n `the emission (\\`return [state, [{ kind: '${kind}', … }]]\\`).`,\n location: {\n file: sf.fileName,\n range: rangeFromOffsets(sf.text, typeLit.getStart(sf), typeLit.getEnd()),\n },\n })\n }\n }\n }\n })\n },\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent-emits-drift.js","sourceRoot":"","sources":["../../src/modules/agent-emits-drift.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,gEAAgE;AAChE,2BAA2B;AAC3B,EAAE;AACF,8BAA8B;AAC9B,wEAAwE;AACxE,2DAA2D;AAC3D,yEAAyE;AACzE,wEAAwE;AACxE,uBAAuB;AACvB,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,6BAA6B;AAC7B,wDAAwD;AAExD,OAAO,EAAE,MAAM,YAAY,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAEnD,OAAO,EAAE,iBAAiB,EAAE,iCAAiC,EAAE,MAAM,oBAAoB,CAAA;AAOzF,SAAS,SAAS,CAAC,WAAmB;IACpC,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAA;IACvD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,MAAM,EAAE,GAAG,mBAAmB,CAAA;IAC9B,IAAI,CAAyB,CAAA;IAC7B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACd,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,YAAY,CAAC,IAAa,EAAE,IAAc;IACjD,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;QAChC,IAAI,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;YAC5C,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAA;YACxB,IAAI,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClE,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;gBAC/B,IAAI,OAAO,IAAI,EAAE,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpD,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;wBAClC,IAAI,EAAE,CAAC,yBAAyB,CAAC,EAAE,CAAC,EAAE,CAAC;4BACrC,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU,EAAE,CAAC;gCACjC,IACE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC;oCAC7B,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;oCAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;oCACzB,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,EACpC,CAAC;oCACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;gCAC9C,CAAC;4BACH,CAAC;wBACH,CAAC;6BAAM,IAAI,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,CAAC;4BAC7D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;wBAC9B,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAC1B,CAAC,CAAA;IACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACZ,CAAC;AAED,SAAS,gBAAgB,CAAC,EAAiB,EAAE,IAA2B;IACtE,MAAM,IAAI,GAAG,CAAC,CAAU,EAAQ,EAAE;QAChC,IAAI,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAA;YACzB,MAAM,gBAAgB,GACpB,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC;gBACnC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAA;YAC3B,IAAI,gBAAgB,EAAE,CAAC;gBACrB,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;oBACrC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;wBAAE,SAAQ;oBAClC,IAAI,CAAC,EAAE,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,UAAU,CAAC;wBAAE,SAAQ;oBAClE,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAA;oBAClC,MAAM,IAAI,GAAa,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI;wBAC1C,YAAY,EAAE,IAAI,GAAG,EAAE;wBACvB,gBAAgB,EAAE,KAAK;qBACxB,CAAA;oBACD,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,UAAU;wBAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;oBAC1D,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QACD,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAC1B,CAAC,CAAA;IACD,IAAI,CAAC,EAAE,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,wBAAwB;gBAC5B,WAAW,EACT,oFAAoF;aACvF;SACF;QACD,QAAQ,EAAE;YACR,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBACxC,MAAM,OAAO,GAAG,IAAqB,CAAA;gBACrC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;gBAC5F,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAoB,CAAA;gBACrD,gBAAgB,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAA;gBACvC,IAAI,iBAAiB,CAAC,IAAI,KAAK,CAAC;oBAAE,OAAM;gBAExC,4DAA4D;gBAC5D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,cAAc;gBACd,MAAM,KAAK,GAAG,CACZ,OAAe,EACf,OAA2B,EAC3B,kBAA0B,EAC1B,SAAwB,EAClB,EAAE;oBACR,MAAM,QAAQ,GAAG,SAAS,CAAC,kBAAkB,CAAC,CAAA;oBAC9C,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;oBAC/C,IAAI,CAAC,QAAQ;wBAAE,OAAM;oBACrB,4DAA4D;oBAC5D,6DAA6D;oBAC7D,sDAAsD;oBACtD,yDAAyD;oBACzD,eAAe;oBACf,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAA;oBACrC,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAA;oBACjC,0DAA0D;oBAC1D,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;wBACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;4BAC7B,GAAG,CAAC,gBAAgB,CAAC;gCACnB,EAAE,EAAE,wBAAwB;gCAC5B,QAAQ,EAAE,OAAO;gCACjB,QAAQ,EAAE,OAAO;gCACjB,OAAO,EACL,gBAAgB,OAAO,wBAAwB,IAAI,4BAA4B;oCAC/E,qCAAqC,IAAI,uBAAuB;oCAChE,cAAc,IAAI,+CAA+C;gCACnE,QAAQ,EAAE;oCACR,IAAI,EAAE,UAAU;oCAChB,KAAK,EAAE,gBAAgB,CACrB,UAAU,EACV,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC3B,OAAO,CAAC,MAAM,EAAE,CACjB;iCACF;6BACF,CAAC,CAAA;wBACJ,CAAC;oBACH,CAAC;oBACD,gEAAgE;oBAChE,uDAAuD;oBACvD,IAAI,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC;wBAC/B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;4BAC5B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gCACrC,GAAG,CAAC,gBAAgB,CAAC;oCACnB,EAAE,EAAE,wBAAwB;oCAC5B,QAAQ,EAAE,OAAO;oCACjB,QAAQ,EAAE,OAAO;oCACjB,OAAO,EACL,gBAAgB,OAAO,sBAAsB,IAAI,4BAA4B;wCAC7E,gDAAgD,IAAI,wBAAwB;wCAC5E,4CAA4C,IAAI,cAAc;oCAChE,QAAQ,EAAE;wCACR,IAAI,EAAE,UAAU;wCAChB,KAAK,EAAE,gBAAgB,CACrB,UAAU,EACV,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC3B,OAAO,CAAC,MAAM,EAAE,CACjB;qCACF;iCACF,CAAC,CAAA;4BACJ,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC,CAAA;gBAED,6CAA6C;gBAC7C,iBAAiB,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE;oBACvE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAA;gBACjD,CAAC,CAAC,CAAA;gBAEF,iEAAiE;gBACjE,8DAA8D;gBAC9D,4DAA4D;gBAC5D,8CAA8C;gBAC9C,MAAM,WAAW,GAAG,GAAG,CAAC,aAAa,EAAE,GAAG,CAAA;gBAC1C,IAAI,WAAW,EAAE,CAAC;oBAChB,iCAAiC,CAC/B,WAAW,CAAC,MAAM;oBAClB,0DAA0D;oBAC1D,yDAAyD;oBACzD,uDAAuD;oBACvD,aAAa,WAAW,CAAC,QAAQ,GAAG,EACpC,WAAW,CAAC,QAAQ,EACpB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE;wBACjD,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,CAAA;wBAC1C,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,CAAC,CAAA;oBACzD,CAAC,CACF,CAAA;gBACH,CAAC;YACH,CAAC;SACF;KACF,CAAA;AACH,CAAC","sourcesContent":["// `agent-emits-drift` — verifies `@emits(\"k1\", \"k2\")` declarations on\n// Msg variants match the effect kinds the corresponding case in\n// update() actually emits.\n//\n// Two failure modes detected:\n// 1. **Emitted but not declared** — a literal `{ kind: 'X' }` appears\n// in the case's effect array but 'X' isn't in @emits.\n// 2. **Declared but not emitted** — a kind in @emits doesn't appear in\n// any literal effect (and the case has no opaque helper calls that\n// might emit it).\n//\n// File-local scope: Msg union and the update() switch must be in the\n// same source file. Cross-file resolution would require type-checker\n// integration. Migrated from\n// `@llui/eslint-plugin/src/rules/agent-emits-drift.ts`.\n\nimport ts from 'typescript'\nimport { rangeFromOffsets } from '../diagnostic.js'\nimport type { CompilerModule } from '../module.js'\nimport { forEachMsgVariant, forEachMsgVariantInExternalSource } from './_msg-variants.js'\n\ninterface CaseInfo {\n literalKinds: Set<string>\n hasOpaqueHelpers: boolean\n}\n\nfunction readEmits(commentText: string): string[] {\n const outer = commentText.match(/@emits\\s*\\(([^)]*)\\)/)\n if (!outer || outer[1] === undefined) return []\n const inner = outer[1]\n const seen = new Set<string>()\n const out: string[] = []\n const re = /[\"“]([^\"”]*)[\"”]/g\n let m: RegExpExecArray | null\n while ((m = re.exec(inner)) !== null) {\n if (m[1] !== undefined && !seen.has(m[1])) {\n seen.add(m[1])\n out.push(m[1])\n }\n }\n return out\n}\n\nfunction walkCaseBody(body: ts.Node, info: CaseInfo): void {\n const walk = (n: ts.Node): void => {\n if (ts.isReturnStatement(n) && n.expression) {\n const arg = n.expression\n if (ts.isArrayLiteralExpression(arg) && arg.elements.length === 2) {\n const effects = arg.elements[1]\n if (effects && ts.isArrayLiteralExpression(effects)) {\n for (const el of effects.elements) {\n if (ts.isObjectLiteralExpression(el)) {\n for (const prop of el.properties) {\n if (\n ts.isPropertyAssignment(prop) &&\n ts.isIdentifier(prop.name) &&\n prop.name.text === 'kind' &&\n ts.isStringLiteral(prop.initializer)\n ) {\n info.literalKinds.add(prop.initializer.text)\n }\n }\n } else if (ts.isCallExpression(el) || ts.isSpreadElement(el)) {\n info.hasOpaqueHelpers = true\n }\n }\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n}\n\nfunction indexSwitchCases(sf: ts.SourceFile, into: Map<string, CaseInfo>): void {\n const walk = (n: ts.Node): void => {\n if (ts.isSwitchStatement(n)) {\n const disc = n.expression\n const looksLikeMsgType =\n ts.isPropertyAccessExpression(disc) &&\n ts.isIdentifier(disc.name) &&\n disc.name.text === 'type'\n if (looksLikeMsgType) {\n for (const sc of n.caseBlock.clauses) {\n if (!ts.isCaseClause(sc)) continue\n if (!sc.expression || !ts.isStringLiteral(sc.expression)) continue\n const variant = sc.expression.text\n const info: CaseInfo = into.get(variant) ?? {\n literalKinds: new Set(),\n hasOpaqueHelpers: false,\n }\n for (const stmt of sc.statements) walkCaseBody(stmt, info)\n into.set(variant, info)\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n}\n\nexport function agentEmitsDriftModule(): CompilerModule {\n return {\n name: 'agent-emits-drift',\n compilerVersion: '^0.3.0',\n diagnostics: [\n {\n id: 'llui/agent-emits-drift',\n description:\n '@emits declaration on Msg variant drifts from actual effect emissions in update().',\n },\n ],\n visitors: {\n [ts.SyntaxKind.SourceFile]: (ctx, node) => {\n const visited = node as ts.SourceFile\n const sf = ts.createSourceFile(visited.fileName, visited.text, ts.ScriptTarget.Latest, true)\n const caseInfoByVariant = new Map<string, CaseInfo>()\n indexSwitchCases(sf, caseInfoByVariant)\n if (caseInfoByVariant.size === 0) return\n\n // Driver that runs the drift-check on a single Msg variant.\n // Hoisted so we can call it for both file-local variants and\n // cross-file (imported) Msg variants without duplicating the\n // diff logic.\n const check = (\n variant: string,\n typeLit: ts.TypeLiteralNode,\n leadingCommentText: string,\n variantSf: ts.SourceFile,\n ): void => {\n const declared = readEmits(leadingCommentText)\n const caseInfo = caseInfoByVariant.get(variant)\n if (!caseInfo) return\n // Anchor diagnostics on the local file when the Msg variant\n // lives in the same file; on the external Msg file when it's\n // imported. Adapters that surface diagnostics by file\n // (vite-plugin's this.error) will route the error to the\n // right place.\n const anchorFile = variantSf.fileName\n const anchorText = variantSf.text\n // Drift 1: literal emissions not in @emits — always warn.\n for (const kind of caseInfo.literalKinds) {\n if (!declared.includes(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" emits effect kind \"${kind}\" in update() but doesn't ` +\n `declare it in @emits. Either add \"${kind}\" to the @emits list ` +\n `(\\`@emits(\"${kind}\")\\`), or remove the literal effect emission.`,\n location: {\n file: anchorFile,\n range: rangeFromOffsets(\n anchorText,\n typeLit.getStart(variantSf),\n typeLit.getEnd(),\n ),\n },\n })\n }\n }\n // Drift 2: @emits-declared but no literal emission in the case.\n // Skip when opaque helpers might be emitting the kind.\n if (!caseInfo.hasOpaqueHelpers) {\n for (const kind of declared) {\n if (!caseInfo.literalKinds.has(kind)) {\n ctx.reportDiagnostic({\n id: 'llui/agent-emits-drift',\n severity: 'error',\n category: 'agent',\n message:\n `Msg variant \"${variant}\" declares @emits \"${kind}\" but no case in update() ` +\n `emits it as a literal effect. Either remove \"${kind}\" from @emits, or add ` +\n `the emission (\\`return [state, [{ kind: '${kind}', … }]]\\`).`,\n location: {\n file: anchorFile,\n range: rangeFromOffsets(\n anchorText,\n typeLit.getStart(variantSf),\n typeLit.getEnd(),\n ),\n },\n })\n }\n }\n }\n }\n\n // File-local Msg variants — the common case.\n forEachMsgVariant(sf, ({ variant, node: typeLit, leadingCommentText }) => {\n check(variant, typeLit, leadingCommentText, sf)\n })\n\n // Cross-file Msg: when `component<S, ImportedMsg, E>()` resolved\n // the M type arg to another file, the host adapter passes the\n // declaring source here. Iterate its variants too so @emits\n // drift surfaces against imported Msg unions.\n const externalMsg = ctx.externalTypes?.msg\n if (externalMsg) {\n forEachMsgVariantInExternalSource(\n externalMsg.source,\n // Synthetic filename — adapters that report by file get a\n // recognisable marker. The vite-plugin doesn't currently\n // map this back to the real on-disk path; future work.\n `<external:${externalMsg.typeName}>`,\n externalMsg.typeName,\n ({ variant, node: typeLit, leadingCommentText }) => {\n const externalSf = typeLit.getSourceFile()\n check(variant, typeLit, leadingCommentText, externalSf)\n },\n )\n }\n },\n },\n }\n}\n"]}
|