@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.41
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/CHANGELOG.md +835 -13
- package/README.md +133 -26
- package/bin/warden.ts +51 -0
- package/package.json +27 -6
- package/src/adapter-check.ts +136 -0
- package/src/cli.ts +1570 -105
- package/src/command.ts +986 -0
- package/src/config.ts +193 -0
- package/src/draft.ts +22 -0
- package/src/drift.ts +233 -23
- package/src/fix.ts +126 -0
- package/src/formatters.ts +78 -13
- package/src/guide.ts +245 -0
- package/src/index.ts +247 -15
- package/src/project-context.ts +446 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +531 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/captured-kernel.ts +375 -0
- package/src/rules/circular-refs.ts +150 -0
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +839 -0
- package/src/rules/context-no-surface-types.ts +79 -15
- package/src/rules/dead-internal-trail.ts +161 -0
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/draft-file-marking.ts +155 -0
- package/src/rules/draft-visible-debt.ts +82 -0
- package/src/rules/duplicate-exported-symbol.ts +211 -0
- package/src/rules/duplicate-public-contract.ts +137 -0
- package/src/rules/entity-exists.ts +254 -0
- package/src/rules/entity-ids.ts +15 -0
- package/src/rules/error-mapping-completeness.ts +290 -0
- package/src/rules/example-valid.ts +395 -0
- package/src/rules/fires-declarations.ts +740 -0
- package/src/rules/governed-symbol-residue.ts +438 -0
- package/src/rules/implementation-returns-result.ts +1409 -166
- package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
- package/src/rules/incomplete-crud.ts +583 -0
- package/src/rules/index.ts +277 -10
- package/src/rules/intent-propagation.ts +125 -0
- package/src/rules/layer-field-name-drift.ts +102 -0
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +871 -0
- package/src/rules/missing-reconcile.ts +97 -0
- package/src/rules/missing-visibility.ts +111 -0
- package/src/rules/no-destructured-compose.ts +196 -0
- package/src/rules/no-dev-permit-in-source.ts +99 -0
- package/src/rules/no-direct-implementation-call.ts +12 -7
- package/src/rules/no-legacy-cli-alias-export.ts +247 -0
- package/src/rules/no-legacy-layer-imports.ts +211 -0
- package/src/rules/no-native-error-result.ts +118 -0
- package/src/rules/no-redundant-result-error-wrap.ts +384 -0
- package/src/rules/no-retired-cross-vocabulary.ts +204 -0
- package/src/rules/no-sync-result-assumption.ts +1141 -98
- package/src/rules/no-throw-in-detour-recover.ts +225 -0
- package/src/rules/no-throw-in-implementation.ts +15 -8
- package/src/rules/no-top-level-surface.ts +371 -0
- package/src/rules/on-references-exist.ts +194 -0
- package/src/rules/orphaned-signal.ts +149 -0
- package/src/rules/owner-projection-parity.ts +143 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/public-export-example-coverage.ts +573 -0
- package/src/rules/public-internal-deep-imports.ts +456 -0
- package/src/rules/public-output-schema.ts +29 -0
- package/src/rules/public-union-output-discriminants.ts +150 -0
- package/src/rules/read-intent-fires.ts +188 -0
- package/src/rules/reference-exists.ts +97 -0
- package/src/rules/registry-names.ts +167 -0
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/resource-declarations.ts +697 -0
- package/src/rules/resource-exists.ts +181 -0
- package/src/rules/resource-id-grammar.ts +65 -0
- package/src/rules/resource-mock-coverage.ts +115 -0
- package/src/rules/retired-vocabulary.ts +633 -0
- package/src/rules/scan.ts +38 -25
- package/src/rules/scheduled-destroy-intent.ts +44 -0
- package/src/rules/signal-graph-coaching.ts +220 -0
- package/src/rules/source/composition.ts +165 -0
- package/src/rules/source/drafts.ts +164 -0
- package/src/rules/source/entities.ts +618 -0
- package/src/rules/source/pragmas.ts +45 -0
- package/src/rules/source/resources.ts +64 -0
- package/src/rules/source/signals.ts +397 -0
- package/src/rules/source/stores.ts +310 -0
- package/src/rules/specs.ts +9 -5
- package/src/rules/static-resource-accessor-preference.ts +653 -0
- package/src/rules/surface-overlay-coherence.ts +262 -0
- package/src/rules/surface-trailhead-coherence.ts +366 -0
- package/src/rules/trail-fork-coaching.ts +625 -0
- package/src/rules/trail-versioning-source.ts +1076 -0
- package/src/rules/trail-versioning-topo.ts +172 -0
- package/src/rules/trailhead-override-divergence.ts +356 -0
- package/src/rules/types.ts +354 -8
- package/src/rules/unmaterialized-activation-source.ts +85 -0
- package/src/rules/unreachable-detour-shadowing.ts +339 -0
- package/src/rules/valid-describe-refs.ts +162 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +540 -0
- package/src/rules/warden-rules-use-ast.ts +1109 -0
- package/src/rules/webhook-route-collision.ts +306 -0
- package/src/trails/activation-orphan.trail.ts +84 -0
- package/src/trails/captured-kernel.trail.ts +108 -0
- package/src/trails/circular-refs.trail.ts +29 -0
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/composes-declarations.trail.ts +22 -0
- package/src/trails/context-no-surface-types.trail.ts +21 -0
- package/src/trails/dead-internal-trail.trail.ts +26 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/deprecation-without-guidance.trail.ts +21 -0
- package/src/trails/draft-file-marking.trail.ts +16 -0
- package/src/trails/draft-visible-debt.trail.ts +16 -0
- package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/entity-exists.trail.ts +21 -0
- package/src/trails/error-mapping-completeness.trail.ts +30 -0
- package/src/trails/example-valid.trail.ts +25 -0
- package/src/trails/fires-declarations.trail.ts +23 -0
- package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
- package/src/trails/governed-symbol-residue.trail.ts +24 -0
- package/src/trails/implementation-returns-result.trail.ts +20 -0
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
- package/src/trails/incomplete-crud.trail.ts +39 -0
- package/src/trails/index.ts +89 -0
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/layer-field-name-drift.trail.ts +39 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/marker-schema-unsupported.trail.ts +23 -0
- package/src/trails/missing-reconcile.trail.ts +33 -0
- package/src/trails/missing-visibility.trail.ts +22 -0
- package/src/trails/no-destructured-compose.trail.ts +44 -0
- package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
- package/src/trails/no-direct-implementation-call.trail.ts +16 -0
- package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
- package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
- package/src/trails/no-native-error-result.trail.ts +18 -0
- package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
- package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
- package/src/trails/no-sync-result-assumption.trail.ts +19 -0
- package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
- package/src/trails/no-throw-in-implementation.trail.ts +20 -0
- package/src/trails/no-top-level-surface.trail.ts +43 -0
- package/src/trails/on-references-exist.trail.ts +21 -0
- package/src/trails/orphaned-signal.trail.ts +36 -0
- package/src/trails/owner-projection-parity.trail.ts +26 -0
- package/src/trails/pending-force.trail.ts +21 -0
- package/src/trails/permit-governance.trail.ts +51 -0
- package/src/trails/prefer-schema-inference.trail.ts +21 -0
- package/src/trails/public-export-example-coverage.trail.ts +16 -0
- package/src/trails/public-internal-deep-imports.trail.ts +94 -0
- package/src/trails/public-output-schema.trail.ts +55 -0
- package/src/trails/public-union-output-discriminants.trail.ts +33 -0
- package/src/trails/read-intent-fires.trail.ts +20 -0
- package/src/trails/reference-exists.trail.ts +25 -0
- package/src/trails/resolved-import-boundary.trail.ts +109 -0
- package/src/trails/resource-declarations.trail.ts +25 -0
- package/src/trails/resource-exists.trail.ts +27 -0
- package/src/trails/resource-id-grammar.trail.ts +39 -0
- package/src/trails/resource-mock-coverage.trail.ts +40 -0
- package/src/trails/run.ts +160 -0
- package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
- package/src/trails/schema.ts +237 -0
- package/src/trails/signal-graph-coaching.trail.ts +77 -0
- package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
- package/src/trails/surface-overlay-coherence.trail.ts +24 -0
- package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
- package/src/trails/topo.ts +6 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/trailhead-override-divergence.trail.ts +47 -0
- package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
- package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
- package/src/trails/valid-describe-refs.trail.ts +18 -0
- package/src/trails/valid-detour-contract.trail.ts +71 -0
- package/src/trails/version-gap.trail.ts +35 -0
- package/src/trails/version-pinned-compose.trail.ts +23 -0
- package/src/trails/version-without-examples.trail.ts +38 -0
- package/src/trails/warden-export-symmetry.trail.ts +16 -0
- package/src/trails/warden-rules-use-ast.trail.ts +64 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +224 -0
- package/src/workspaces.ts +199 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/cli.d.ts +0 -46
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -218
- package/dist/cli.js.map +0 -1
- package/dist/drift.d.ts +0 -26
- package/dist/drift.d.ts.map +0 -1
- package/dist/drift.js +0 -27
- package/dist/drift.js.map +0 -1
- package/dist/formatters.d.ts +0 -29
- package/dist/formatters.d.ts.map +0 -1
- package/dist/formatters.js +0 -87
- package/dist/formatters.js.map +0 -1
- package/dist/index.d.ts +0 -26
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/rules/ast.d.ts +0 -41
- package/dist/rules/ast.d.ts.map +0 -1
- package/dist/rules/ast.js +0 -161
- package/dist/rules/ast.js.map +0 -1
- package/dist/rules/context-no-surface-types.d.ts +0 -12
- package/dist/rules/context-no-surface-types.d.ts.map +0 -1
- package/dist/rules/context-no-surface-types.js +0 -96
- package/dist/rules/context-no-surface-types.js.map +0 -1
- package/dist/rules/implementation-returns-result.d.ts +0 -13
- package/dist/rules/implementation-returns-result.d.ts.map +0 -1
- package/dist/rules/implementation-returns-result.js +0 -277
- package/dist/rules/implementation-returns-result.js.map +0 -1
- package/dist/rules/index.d.ts +0 -15
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -34
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
- package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
- package/dist/rules/no-direct-impl-in-route.js +0 -47
- package/dist/rules/no-direct-impl-in-route.js.map +0 -1
- package/dist/rules/no-direct-implementation-call.d.ts +0 -12
- package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
- package/dist/rules/no-direct-implementation-call.js +0 -39
- package/dist/rules/no-direct-implementation-call.js.map +0 -1
- package/dist/rules/no-sync-result-assumption.d.ts +0 -6
- package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
- package/dist/rules/no-sync-result-assumption.js +0 -98
- package/dist/rules/no-sync-result-assumption.js.map +0 -1
- package/dist/rules/no-throw-in-detour-target.d.ts +0 -12
- package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
- package/dist/rules/no-throw-in-detour-target.js +0 -87
- package/dist/rules/no-throw-in-detour-target.js.map +0 -1
- package/dist/rules/no-throw-in-implementation.d.ts +0 -9
- package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
- package/dist/rules/no-throw-in-implementation.js +0 -34
- package/dist/rules/no-throw-in-implementation.js.map +0 -1
- package/dist/rules/prefer-schema-inference.d.ts +0 -7
- package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
- package/dist/rules/prefer-schema-inference.js +0 -86
- package/dist/rules/prefer-schema-inference.js.map +0 -1
- package/dist/rules/scan.d.ts +0 -8
- package/dist/rules/scan.d.ts.map +0 -1
- package/dist/rules/scan.js +0 -32
- package/dist/rules/scan.js.map +0 -1
- package/dist/rules/specs.d.ts +0 -29
- package/dist/rules/specs.d.ts.map +0 -1
- package/dist/rules/specs.js +0 -192
- package/dist/rules/specs.js.map +0 -1
- package/dist/rules/structure.d.ts +0 -13
- package/dist/rules/structure.d.ts.map +0 -1
- package/dist/rules/structure.js +0 -142
- package/dist/rules/structure.js.map +0 -1
- package/dist/rules/types.d.ts +0 -52
- package/dist/rules/types.d.ts.map +0 -1
- package/dist/rules/types.js +0 -2
- package/dist/rules/types.js.map +0 -1
- package/dist/rules/valid-describe-refs.d.ts +0 -7
- package/dist/rules/valid-describe-refs.d.ts.map +0 -1
- package/dist/rules/valid-describe-refs.js +0 -51
- package/dist/rules/valid-describe-refs.js.map +0 -1
- package/dist/rules/valid-detour-refs.d.ts +0 -6
- package/dist/rules/valid-detour-refs.d.ts.map +0 -1
- package/dist/rules/valid-detour-refs.js +0 -116
- package/dist/rules/valid-detour-refs.js.map +0 -1
- package/src/__tests__/cli.test.ts +0 -198
- package/src/__tests__/drift.test.ts +0 -74
- package/src/__tests__/formatters.test.ts +0 -157
- package/src/__tests__/implementation-returns-result.test.ts +0 -129
- package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
- package/src/__tests__/no-sync-result-assumption.test.ts +0 -85
- package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
- package/src/__tests__/prefer-schema-inference.test.ts +0 -84
- package/src/__tests__/rules.test.ts +0 -227
- package/src/__tests__/valid-describe-refs.test.ts +0 -60
- package/src/rules/ast.ts +0 -213
- package/src/rules/no-direct-impl-in-route.ts +0 -81
- package/src/rules/no-throw-in-detour-target.ts +0 -150
- package/src/rules/valid-detour-refs.ts +0 -187
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Warden-private resource declaration helpers. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
extractBindingName,
|
|
5
|
+
extractFirstStringArg,
|
|
6
|
+
identifierName,
|
|
7
|
+
walk,
|
|
8
|
+
} from '@ontrails/source';
|
|
9
|
+
import type { AstNode } from '@ontrails/source';
|
|
10
|
+
|
|
11
|
+
const isResourceCall = (node: AstNode | undefined): boolean =>
|
|
12
|
+
!!node &&
|
|
13
|
+
node.type === 'CallExpression' &&
|
|
14
|
+
identifierName((node as unknown as { callee?: AstNode }).callee) ===
|
|
15
|
+
'resource';
|
|
16
|
+
|
|
17
|
+
/** Collect `const foo = resource('id', ...)` bindings from a parsed file. */
|
|
18
|
+
export const collectNamedResourceIds = (
|
|
19
|
+
ast: AstNode
|
|
20
|
+
): ReadonlyMap<string, string> => {
|
|
21
|
+
const ids = new Map<string, string>();
|
|
22
|
+
|
|
23
|
+
walk(ast, (node) => {
|
|
24
|
+
if (node.type !== 'VariableDeclarator') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { id, init } = node as unknown as {
|
|
29
|
+
readonly id?: AstNode;
|
|
30
|
+
readonly init?: AstNode;
|
|
31
|
+
};
|
|
32
|
+
if (!isResourceCall(init)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const name = extractBindingName(id);
|
|
37
|
+
const resourceId = init ? extractFirstStringArg(init) : null;
|
|
38
|
+
if (name && resourceId) {
|
|
39
|
+
ids.set(name, resourceId);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return ids;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Collect all inline `resource('id', ...)` definition IDs from a parsed file. */
|
|
47
|
+
export const collectResourceDefinitionIds = (
|
|
48
|
+
ast: AstNode
|
|
49
|
+
): ReadonlySet<string> => {
|
|
50
|
+
const ids = new Set<string>();
|
|
51
|
+
|
|
52
|
+
walk(ast, (node) => {
|
|
53
|
+
if (!isResourceCall(node)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const id = extractFirstStringArg(node);
|
|
58
|
+
if (id) {
|
|
59
|
+
ids.add(id);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return ids;
|
|
64
|
+
};
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/** Warden-private signal declaration and on-target helpers. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildFrameworkNamespaceContext,
|
|
5
|
+
collectScopeFrameBindings,
|
|
6
|
+
deriveConstString,
|
|
7
|
+
extractBindingName,
|
|
8
|
+
extractPlainTemplateLiteral,
|
|
9
|
+
extractStringLiteral,
|
|
10
|
+
extractTrailDefinition,
|
|
11
|
+
findConfigProperty,
|
|
12
|
+
findTrailDefinitions,
|
|
13
|
+
getPropertyName,
|
|
14
|
+
getStringValue,
|
|
15
|
+
identifierName,
|
|
16
|
+
isScopeFrameNode,
|
|
17
|
+
isStringLiteral,
|
|
18
|
+
walk,
|
|
19
|
+
} from '@ontrails/source';
|
|
20
|
+
import type { AstNode, FrameworkNamespaceContext } from '@ontrails/source';
|
|
21
|
+
import {
|
|
22
|
+
collectNamedStoreTableIds,
|
|
23
|
+
deriveStoreTableId,
|
|
24
|
+
getMemberExpression,
|
|
25
|
+
} from './stores.js';
|
|
26
|
+
|
|
27
|
+
export const collectSignalDefinitionIds = (
|
|
28
|
+
ast: AstNode
|
|
29
|
+
): ReadonlySet<string> => {
|
|
30
|
+
const ids = new Set<string>();
|
|
31
|
+
for (const def of findTrailDefinitions(ast)) {
|
|
32
|
+
if (def.kind === 'signal') {
|
|
33
|
+
ids.add(def.id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ids;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const unwrapTopLevelDeclaration = (stmt: AstNode): AstNode => {
|
|
40
|
+
if (
|
|
41
|
+
stmt.type === 'ExportNamedDeclaration' ||
|
|
42
|
+
stmt.type === 'ExportDefaultDeclaration'
|
|
43
|
+
) {
|
|
44
|
+
return (stmt as unknown as { declaration?: AstNode }).declaration ?? stmt;
|
|
45
|
+
}
|
|
46
|
+
return stmt;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const collectSignalIdsFromDeclaration = (
|
|
50
|
+
declaration: AstNode,
|
|
51
|
+
context: FrameworkNamespaceContext,
|
|
52
|
+
ids: Map<string, string>
|
|
53
|
+
): void => {
|
|
54
|
+
const declarations =
|
|
55
|
+
(
|
|
56
|
+
unwrapTopLevelDeclaration(declaration) as unknown as {
|
|
57
|
+
declarations?: readonly AstNode[];
|
|
58
|
+
}
|
|
59
|
+
).declarations ?? [];
|
|
60
|
+
|
|
61
|
+
for (const node of declarations) {
|
|
62
|
+
const { id, init } = node as unknown as {
|
|
63
|
+
readonly id?: AstNode;
|
|
64
|
+
readonly init?: AstNode;
|
|
65
|
+
};
|
|
66
|
+
if (!init) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const def = extractTrailDefinition(init, context);
|
|
71
|
+
const name = extractBindingName(id);
|
|
72
|
+
if (def?.kind === 'signal' && name && !ids.has(name)) {
|
|
73
|
+
ids.set(name, def.id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const collectStringIdsFromDeclaration = (
|
|
79
|
+
declaration: AstNode,
|
|
80
|
+
ids: Map<string, string>
|
|
81
|
+
): void => {
|
|
82
|
+
const declarations =
|
|
83
|
+
(
|
|
84
|
+
unwrapTopLevelDeclaration(declaration) as unknown as {
|
|
85
|
+
declarations?: readonly AstNode[];
|
|
86
|
+
}
|
|
87
|
+
).declarations ?? [];
|
|
88
|
+
|
|
89
|
+
for (const node of declarations) {
|
|
90
|
+
const { id, init } = node as unknown as {
|
|
91
|
+
readonly id?: AstNode;
|
|
92
|
+
readonly init?: AstNode;
|
|
93
|
+
};
|
|
94
|
+
if (!init) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const name = extractBindingName(id);
|
|
99
|
+
const value =
|
|
100
|
+
extractStringLiteral(init) ?? extractPlainTemplateLiteral(init);
|
|
101
|
+
if (name && value !== null && !ids.has(name)) {
|
|
102
|
+
ids.set(name, value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type SignalIdentifierResolution =
|
|
108
|
+
| {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
readonly kind: 'signal' | 'string';
|
|
111
|
+
}
|
|
112
|
+
| {
|
|
113
|
+
readonly kind: 'shadowed' | 'unbound';
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export interface SignalIdentifierResolver {
|
|
117
|
+
readonly resolve: (reference: AstNode) => SignalIdentifierResolution;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface SignalScopeFrame {
|
|
121
|
+
readonly bindings: ReadonlySet<string>;
|
|
122
|
+
readonly end: number;
|
|
123
|
+
readonly signals: ReadonlyMap<string, string>;
|
|
124
|
+
readonly start: number;
|
|
125
|
+
readonly strings: ReadonlyMap<string, string>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const collectSignalFrameValues = (
|
|
129
|
+
node: AstNode,
|
|
130
|
+
context: FrameworkNamespaceContext
|
|
131
|
+
): {
|
|
132
|
+
readonly signals: ReadonlyMap<string, string>;
|
|
133
|
+
readonly strings: ReadonlyMap<string, string>;
|
|
134
|
+
} => {
|
|
135
|
+
const signals = new Map<string, string>();
|
|
136
|
+
const strings = new Map<string, string>();
|
|
137
|
+
|
|
138
|
+
const collectDeclaration = (statement: AstNode): void => {
|
|
139
|
+
const declaration = unwrapTopLevelDeclaration(statement);
|
|
140
|
+
if (declaration.type !== 'VariableDeclaration') {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
collectSignalIdsFromDeclaration(declaration, context, signals);
|
|
144
|
+
collectStringIdsFromDeclaration(declaration, strings);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (
|
|
148
|
+
node.type === 'Program' ||
|
|
149
|
+
node.type === 'BlockStatement' ||
|
|
150
|
+
node.type === 'FunctionBody'
|
|
151
|
+
) {
|
|
152
|
+
const body = (node as unknown as { body?: readonly AstNode[] }).body ?? [];
|
|
153
|
+
for (const statement of body) {
|
|
154
|
+
collectDeclaration(statement);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (node.type === 'ForStatement') {
|
|
159
|
+
const { init } = node as unknown as { init?: AstNode };
|
|
160
|
+
if (init) {
|
|
161
|
+
collectDeclaration(init);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (node.type === 'SwitchStatement') {
|
|
166
|
+
const cases =
|
|
167
|
+
(node as unknown as { cases?: readonly AstNode[] }).cases ?? [];
|
|
168
|
+
for (const item of cases) {
|
|
169
|
+
const consequent =
|
|
170
|
+
(item as unknown as { consequent?: readonly AstNode[] }).consequent ??
|
|
171
|
+
[];
|
|
172
|
+
for (const statement of consequent) {
|
|
173
|
+
collectDeclaration(statement);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { signals, strings };
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const collectSignalScopeFrames = (
|
|
182
|
+
ast: AstNode,
|
|
183
|
+
context: FrameworkNamespaceContext
|
|
184
|
+
): readonly SignalScopeFrame[] => {
|
|
185
|
+
const frames: SignalScopeFrame[] = [];
|
|
186
|
+
|
|
187
|
+
walk(ast, (node) => {
|
|
188
|
+
if (!isScopeFrameNode(node)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const values = collectSignalFrameValues(node, context);
|
|
192
|
+
frames.push({
|
|
193
|
+
bindings: collectScopeFrameBindings(node),
|
|
194
|
+
end: node.end,
|
|
195
|
+
signals: values.signals,
|
|
196
|
+
start: node.start,
|
|
197
|
+
strings: values.strings,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return frames;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const isInsideFrame = (reference: AstNode, frame: SignalScopeFrame): boolean =>
|
|
205
|
+
frame.start <= reference.start && reference.end <= frame.end;
|
|
206
|
+
|
|
207
|
+
const compareInnermostFrame = (
|
|
208
|
+
a: SignalScopeFrame,
|
|
209
|
+
b: SignalScopeFrame
|
|
210
|
+
): number => {
|
|
211
|
+
const aSize = a.end - a.start;
|
|
212
|
+
const bSize = b.end - b.start;
|
|
213
|
+
return aSize - bSize || b.start - a.start;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const buildSignalIdentifierResolver = (
|
|
217
|
+
ast: AstNode
|
|
218
|
+
): SignalIdentifierResolver => {
|
|
219
|
+
const context = buildFrameworkNamespaceContext(ast);
|
|
220
|
+
const frames = collectSignalScopeFrames(ast, context);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
resolve(reference: AstNode): SignalIdentifierResolution {
|
|
224
|
+
const name = identifierName(reference);
|
|
225
|
+
if (!name) {
|
|
226
|
+
return { kind: 'unbound' };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const containingFrames = frames
|
|
230
|
+
.filter((frame) => isInsideFrame(reference, frame))
|
|
231
|
+
.toSorted(compareInnermostFrame);
|
|
232
|
+
|
|
233
|
+
for (const frame of containingFrames) {
|
|
234
|
+
if (!frame.bindings.has(name)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const signalId = frame.signals.get(name);
|
|
238
|
+
if (signalId) {
|
|
239
|
+
return { id: signalId, kind: 'signal' };
|
|
240
|
+
}
|
|
241
|
+
const stringId = frame.strings.get(name);
|
|
242
|
+
if (stringId) {
|
|
243
|
+
return { id: stringId, kind: 'string' };
|
|
244
|
+
}
|
|
245
|
+
return { kind: 'shadowed' };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { kind: 'unbound' };
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const STORE_SIGNAL_OPERATIONS = new Set(['created', 'removed', 'updated']);
|
|
254
|
+
|
|
255
|
+
const extractStoreSignalIdFromMember = (
|
|
256
|
+
node: AstNode | undefined,
|
|
257
|
+
namedStoreTableIds: ReadonlyMap<string, string>
|
|
258
|
+
): string | null => {
|
|
259
|
+
const member = getMemberExpression(node);
|
|
260
|
+
const operation = member ? getPropertyName(member.property) : null;
|
|
261
|
+
if (!operation || !STORE_SIGNAL_OPERATIONS.has(operation)) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const signalsMember = member ? getMemberExpression(member.object) : null;
|
|
266
|
+
if (!signalsMember || getPropertyName(signalsMember.property) !== 'signals') {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const tableId = deriveStoreTableId(signalsMember.object, namedStoreTableIds);
|
|
271
|
+
return tableId ? `${tableId}.${operation}` : null;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const collectNamedStoreSignalIds = (
|
|
275
|
+
ast: AstNode,
|
|
276
|
+
namedStoreTableIds: ReadonlyMap<string, string>
|
|
277
|
+
): ReadonlyMap<string, string> => {
|
|
278
|
+
const ids = new Map<string, string>();
|
|
279
|
+
|
|
280
|
+
walk(ast, (node) => {
|
|
281
|
+
if (node.type !== 'VariableDeclarator') {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const { id, init } = node as unknown as {
|
|
286
|
+
readonly id?: AstNode;
|
|
287
|
+
readonly init?: AstNode;
|
|
288
|
+
};
|
|
289
|
+
const name = extractBindingName(id);
|
|
290
|
+
const signalId = extractStoreSignalIdFromMember(init, namedStoreTableIds);
|
|
291
|
+
if (name && signalId) {
|
|
292
|
+
ids.set(name, signalId);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return ids;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const getOnElements = (config: AstNode): readonly AstNode[] => {
|
|
300
|
+
const onProp = findConfigProperty(config, 'on');
|
|
301
|
+
if (!onProp) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const arrayNode = onProp.value;
|
|
306
|
+
if (!arrayNode || (arrayNode as AstNode).type !== 'ArrayExpression') {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const elements = (arrayNode as AstNode)['elements'] as
|
|
311
|
+
| readonly AstNode[]
|
|
312
|
+
| undefined;
|
|
313
|
+
return elements ?? [];
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const resolveNamedOnSignalId = (
|
|
317
|
+
element: AstNode,
|
|
318
|
+
sourceCode: string,
|
|
319
|
+
namedStoreSignalIds: ReadonlyMap<string, string>
|
|
320
|
+
): string | null => {
|
|
321
|
+
if (element.type !== 'Identifier') {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const name = identifierName(element);
|
|
326
|
+
return name
|
|
327
|
+
? (namedStoreSignalIds.get(name) ?? deriveConstString(name, sourceCode))
|
|
328
|
+
: null;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const resolveInlineOnSignalId = (element: AstNode): string | null => {
|
|
332
|
+
const definition = extractTrailDefinition(element);
|
|
333
|
+
return definition?.kind === 'signal' ? definition.id : null;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const resolveOnElementSignalId = (
|
|
337
|
+
element: AstNode,
|
|
338
|
+
sourceCode: string,
|
|
339
|
+
namedStoreSignalIds: ReadonlyMap<string, string>,
|
|
340
|
+
namedStoreTableIds: ReadonlyMap<string, string>
|
|
341
|
+
): string | null => {
|
|
342
|
+
if (isStringLiteral(element)) {
|
|
343
|
+
return getStringValue(element);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
extractStoreSignalIdFromMember(element, namedStoreTableIds) ??
|
|
348
|
+
resolveNamedOnSignalId(element, sourceCode, namedStoreSignalIds) ??
|
|
349
|
+
resolveInlineOnSignalId(element)
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const addOnTargetSignalIds = (
|
|
354
|
+
config: AstNode,
|
|
355
|
+
ids: Set<string>,
|
|
356
|
+
sourceCode: string,
|
|
357
|
+
namedStoreSignalIds: ReadonlyMap<string, string>,
|
|
358
|
+
namedStoreTableIds: ReadonlyMap<string, string>
|
|
359
|
+
): void => {
|
|
360
|
+
for (const element of getOnElements(config)) {
|
|
361
|
+
const signalId = resolveOnElementSignalId(
|
|
362
|
+
element,
|
|
363
|
+
sourceCode,
|
|
364
|
+
namedStoreSignalIds,
|
|
365
|
+
namedStoreTableIds
|
|
366
|
+
);
|
|
367
|
+
if (signalId) {
|
|
368
|
+
ids.add(signalId);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export const collectOnTargetSignalIds = (
|
|
374
|
+
ast: AstNode,
|
|
375
|
+
sourceCode: string
|
|
376
|
+
): ReadonlySet<string> => {
|
|
377
|
+
const ids = new Set<string>();
|
|
378
|
+
const namedStoreTableIds = collectNamedStoreTableIds(ast);
|
|
379
|
+
const namedStoreSignalIds = collectNamedStoreSignalIds(
|
|
380
|
+
ast,
|
|
381
|
+
namedStoreTableIds
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
for (const definition of findTrailDefinitions(ast)) {
|
|
385
|
+
if (definition.kind === 'trail') {
|
|
386
|
+
addOnTargetSignalIds(
|
|
387
|
+
definition.config,
|
|
388
|
+
ids,
|
|
389
|
+
sourceCode,
|
|
390
|
+
namedStoreSignalIds,
|
|
391
|
+
namedStoreTableIds
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return ids;
|
|
397
|
+
};
|