@ontrails/warden 1.0.0-beta.4 → 1.0.0-beta.42
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 +837 -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,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flags leftover legacy app-module CLI alias exports removed in TRL-1207.
|
|
3
|
+
*
|
|
4
|
+
* Before the surfaces-overlay cutover, apps published CLI alias maps through
|
|
5
|
+
* a module-level export named `cliAliases` or `trailsCliAliases`. TRL-1207 is
|
|
6
|
+
* a hard cutover: app-owned CLI bindings now live in a
|
|
7
|
+
* `surfaceOverlay({ cli: { ... } })` envelope inside the app module's
|
|
8
|
+
* `trailsOverlays` array export, and the legacy export convention was
|
|
9
|
+
* deleted. This rule makes a leftover legacy export an error so stale app
|
|
10
|
+
* modules get a fix-forward diagnostic instead of silently losing their
|
|
11
|
+
* bindings.
|
|
12
|
+
*
|
|
13
|
+
* Detection is AST-scoped and export-shaped:
|
|
14
|
+
*
|
|
15
|
+
* - `export const cliAliases = { ... }` (also `let`/`var`, destructured
|
|
16
|
+
* bindings, and function/class declarations) is flagged.
|
|
17
|
+
* - `export { cliAliases }` and `export { whatever as trailsCliAliases }`
|
|
18
|
+
* specifiers are flagged, including re-export statements such as
|
|
19
|
+
* `export { trailsCliAliases } from './app.js'`.
|
|
20
|
+
* - A non-exported local `const cliAliases = ...` is legal and never
|
|
21
|
+
* flagged, and the identifiers appearing only in strings or comments do
|
|
22
|
+
* not match because the rule inspects export bindings, not raw text.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
getNodeArgument,
|
|
27
|
+
getNodeBodyStatements,
|
|
28
|
+
getNodeDeclaration,
|
|
29
|
+
getNodeDeclarations,
|
|
30
|
+
getNodeElements,
|
|
31
|
+
getNodeExported,
|
|
32
|
+
getNodeExportKind,
|
|
33
|
+
getNodeId,
|
|
34
|
+
getNodeLeft,
|
|
35
|
+
getNodeProperties,
|
|
36
|
+
getNodeSpecifiers,
|
|
37
|
+
getNodeValueNode,
|
|
38
|
+
getStringValue,
|
|
39
|
+
identifierName,
|
|
40
|
+
isStringLiteral,
|
|
41
|
+
offsetToLine,
|
|
42
|
+
parse,
|
|
43
|
+
} from '@ontrails/source';
|
|
44
|
+
import type { AstNode } from '@ontrails/source';
|
|
45
|
+
import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
|
|
46
|
+
|
|
47
|
+
const RULE_NAME = 'no-legacy-cli-alias-export';
|
|
48
|
+
|
|
49
|
+
/** Legacy app-module CLI alias export names removed in TRL-1207. */
|
|
50
|
+
const LEGACY_CLI_ALIAS_EXPORT_NAMES: ReadonlySet<string> = new Set([
|
|
51
|
+
'cliAliases',
|
|
52
|
+
'trailsCliAliases',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/** Declaration node types that only propagate types, never value bindings. */
|
|
56
|
+
const TYPE_ONLY_DECLARATION_TYPES: ReadonlySet<string> = new Set([
|
|
57
|
+
'TSInterfaceDeclaration',
|
|
58
|
+
'TSTypeAliasDeclaration',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
interface ExportedBinding {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly start: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Exported name of a specifier: identifier or string-literal alias. */
|
|
67
|
+
const exportedSpecifierName = (node: AstNode | undefined): string | null =>
|
|
68
|
+
identifierName(node) ??
|
|
69
|
+
(node && isStringLiteral(node) ? getStringValue(node) : null);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Expand one binding-pattern node into legacy-named bindings, pushing child
|
|
73
|
+
* patterns onto the worklist. Destructured exports such as
|
|
74
|
+
* `export const { cliAliases } = mod` still introduce a module-level exported
|
|
75
|
+
* value binding, so patterns cannot be skipped.
|
|
76
|
+
*/
|
|
77
|
+
const visitBindingPatternNode = (
|
|
78
|
+
node: AstNode,
|
|
79
|
+
into: ExportedBinding[],
|
|
80
|
+
worklist: AstNode[]
|
|
81
|
+
): void => {
|
|
82
|
+
const name = identifierName(node);
|
|
83
|
+
if (name !== null) {
|
|
84
|
+
if (LEGACY_CLI_ALIAS_EXPORT_NAMES.has(name)) {
|
|
85
|
+
into.push({ name, start: node.start });
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (node.type === 'AssignmentPattern') {
|
|
90
|
+
const left = getNodeLeft(node);
|
|
91
|
+
if (left) {
|
|
92
|
+
worklist.push(left);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (node.type === 'RestElement') {
|
|
97
|
+
const argument = getNodeArgument(node);
|
|
98
|
+
if (argument) {
|
|
99
|
+
worklist.push(argument);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (node.type === 'ObjectPattern') {
|
|
104
|
+
for (const property of getNodeProperties(node)) {
|
|
105
|
+
const value = getNodeValueNode(property) ?? getNodeArgument(property);
|
|
106
|
+
if (value) {
|
|
107
|
+
worklist.push(value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (node.type === 'ArrayPattern') {
|
|
113
|
+
for (const element of getNodeElements(node)) {
|
|
114
|
+
if (element) {
|
|
115
|
+
worklist.push(element);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const collectLegacyPatternBindings = (
|
|
122
|
+
pattern: AstNode | undefined,
|
|
123
|
+
into: ExportedBinding[]
|
|
124
|
+
): void => {
|
|
125
|
+
if (!pattern) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const worklist: AstNode[] = [pattern];
|
|
129
|
+
while (worklist.length > 0) {
|
|
130
|
+
const node = worklist.pop();
|
|
131
|
+
if (node) {
|
|
132
|
+
visitBindingPatternNode(node, into, worklist);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const collectLegacyDeclarationBindings = (
|
|
138
|
+
declaration: AstNode
|
|
139
|
+
): readonly ExportedBinding[] => {
|
|
140
|
+
if (TYPE_ONLY_DECLARATION_TYPES.has(declaration.type)) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const bindings: ExportedBinding[] = [];
|
|
144
|
+
if (declaration.type === 'VariableDeclaration') {
|
|
145
|
+
for (const declarator of getNodeDeclarations(declaration)) {
|
|
146
|
+
collectLegacyPatternBindings(getNodeId(declarator), bindings);
|
|
147
|
+
}
|
|
148
|
+
return bindings;
|
|
149
|
+
}
|
|
150
|
+
const name = identifierName(getNodeId(declaration));
|
|
151
|
+
if (name !== null && LEGACY_CLI_ALIAS_EXPORT_NAMES.has(name)) {
|
|
152
|
+
bindings.push({ name, start: declaration.start });
|
|
153
|
+
}
|
|
154
|
+
return bindings;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const collectLegacySpecifierBindings = (
|
|
158
|
+
statement: AstNode
|
|
159
|
+
): readonly ExportedBinding[] => {
|
|
160
|
+
const bindings: ExportedBinding[] = [];
|
|
161
|
+
for (const specifier of getNodeSpecifiers(statement)) {
|
|
162
|
+
if (
|
|
163
|
+
specifier.type !== 'ExportSpecifier' ||
|
|
164
|
+
getNodeExportKind(specifier) === 'type'
|
|
165
|
+
) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const name = exportedSpecifierName(getNodeExported(specifier));
|
|
169
|
+
if (name !== null && LEGACY_CLI_ALIAS_EXPORT_NAMES.has(name)) {
|
|
170
|
+
bindings.push({ name, start: specifier.start });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return bindings;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const collectLegacyExportBindings = (
|
|
177
|
+
ast: AstNode
|
|
178
|
+
): readonly ExportedBinding[] => {
|
|
179
|
+
const bindings: ExportedBinding[] = [];
|
|
180
|
+
for (const statement of getNodeBodyStatements(ast)) {
|
|
181
|
+
if (
|
|
182
|
+
statement.type !== 'ExportNamedDeclaration' ||
|
|
183
|
+
getNodeExportKind(statement) === 'type'
|
|
184
|
+
) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const declaration = getNodeDeclaration(statement);
|
|
188
|
+
if (declaration) {
|
|
189
|
+
bindings.push(...collectLegacyDeclarationBindings(declaration));
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
bindings.push(...collectLegacySpecifierBindings(statement));
|
|
193
|
+
}
|
|
194
|
+
return bindings;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const buildMessage = (name: string): string =>
|
|
198
|
+
`Legacy CLI alias export '${name}' was removed in the TRL-1207 surfaces-overlay cutover. Wrap the alias map into surfaceOverlay({ cli: { ... } }) from @ontrails/core inside the module's trailsOverlays array export.`;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build the fix metadata for a legacy CLI alias export finding.
|
|
202
|
+
*
|
|
203
|
+
* The rewrite is an export restructure, not a rename, so there is no
|
|
204
|
+
* mechanical single-span replacement: the fix carries no edits and Warden
|
|
205
|
+
* never applies it. Downstream, the `export-restructure` fix class routes the
|
|
206
|
+
* finding to the Regrade `export-restructure:cli-aliases` class (TRL-1210),
|
|
207
|
+
* which inverts the alias map into `surfaceOverlay({ cli: { ... } })`
|
|
208
|
+
* bindings inside the module's `trailsOverlays` array export.
|
|
209
|
+
*/
|
|
210
|
+
const buildFix = (name: string): WardenFix => ({
|
|
211
|
+
class: 'export-restructure',
|
|
212
|
+
reason: `Legacy CLI alias export '${name}' must be rewritten into a surfaceOverlay({ cli: { ... } }) entry inside the module's trailsOverlays array export; run the Regrade class export-restructure:cli-aliases (TRL-1210) to automate this restructure.`,
|
|
213
|
+
safety: 'review',
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Flags exported bindings named `cliAliases` or `trailsCliAliases`, the
|
|
218
|
+
* legacy app-module CLI alias export convention deleted in TRL-1207.
|
|
219
|
+
*/
|
|
220
|
+
export const noLegacyCliAliasExport: WardenRule = {
|
|
221
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
222
|
+
if (
|
|
223
|
+
!(
|
|
224
|
+
sourceCode.includes('cliAliases') ||
|
|
225
|
+
sourceCode.includes('trailsCliAliases')
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
const ast = parse(filePath, sourceCode);
|
|
231
|
+
if (!ast) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
return collectLegacyExportBindings(ast).map((binding) => ({
|
|
235
|
+
filePath,
|
|
236
|
+
fix: buildFix(binding.name),
|
|
237
|
+
line: offsetToLine(sourceCode, binding.start),
|
|
238
|
+
message: buildMessage(binding.name),
|
|
239
|
+
rule: RULE_NAME,
|
|
240
|
+
severity: 'error',
|
|
241
|
+
}));
|
|
242
|
+
},
|
|
243
|
+
description:
|
|
244
|
+
'Disallow the legacy app-module CLI alias exports (cliAliases, trailsCliAliases) removed in the TRL-1207 surfaces-overlay cutover.',
|
|
245
|
+
name: RULE_NAME,
|
|
246
|
+
severity: 'error',
|
|
247
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flags references to legacy layer symbols removed during Layer Evolution.
|
|
3
|
+
*
|
|
4
|
+
* The pre-TRL-469 era of Trails shipped three first-party layers as ergonomics
|
|
5
|
+
* over `executeTrail`:
|
|
6
|
+
*
|
|
7
|
+
* - `authLayer` — re-exposed permit enforcement, even though permits were
|
|
8
|
+
* intrinsic to `executeTrail`.
|
|
9
|
+
* - `autoIterateLayer` — wrapped the CLI iteration ergonomics that the
|
|
10
|
+
* surface now derives directly via `--all` (TRL-469).
|
|
11
|
+
* - `dateShortcutsLayer` — wrapped the CLI date-expansion helpers that the
|
|
12
|
+
* surface now derives directly (TRL-470).
|
|
13
|
+
*
|
|
14
|
+
* TRL-475 removed `authLayer`; TRL-476 removed the CLI layer exports
|
|
15
|
+
* `autoIterateLayer` and `dateShortcutsLayer`. Importing them from
|
|
16
|
+
* `@ontrails/permits` or `@ontrails/cli` produces a TypeScript error today;
|
|
17
|
+
* this rule layers a coaching diagnostic on top so authors that still type
|
|
18
|
+
* the symbol — in a comment, a string, or a stale import — get a redirect to
|
|
19
|
+
* the migration ADR during the deprecation window.
|
|
20
|
+
*
|
|
21
|
+
* The rule is intentionally text-based: legacy names appear in a mix of
|
|
22
|
+
* imports, JSDoc, error messages, and migration notes, all of which should
|
|
23
|
+
* surface a coaching message.
|
|
24
|
+
*
|
|
25
|
+
* Allow-list: a small set of files documenting the removal itself reference
|
|
26
|
+
* the literal symbol names. The rule's own implementation file references
|
|
27
|
+
* each name and is also exempt.
|
|
28
|
+
*
|
|
29
|
+
* Test files (`__tests__/`, `*.test.ts`) are filtered before this rule is
|
|
30
|
+
* invoked by the warden runner, so the regression test referencing
|
|
31
|
+
* `authLayer` in its name (`packages/core/src/__tests__/execute-permit.test.ts`)
|
|
32
|
+
* does not need a separate exemption.
|
|
33
|
+
*/
|
|
34
|
+
import { resolve, sep } from 'node:path';
|
|
35
|
+
|
|
36
|
+
import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
|
|
37
|
+
|
|
38
|
+
const RULE_NAME = 'no-legacy-layer-imports';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Legacy layer symbol names that were removed during Layer Evolution.
|
|
42
|
+
*
|
|
43
|
+
* Listed longest-first is not required for correctness (none of these names
|
|
44
|
+
* are substrings of one another), but kept alphabetized for stable output
|
|
45
|
+
* across rule runs.
|
|
46
|
+
*/
|
|
47
|
+
const LEGACY_LAYER_NAMES = [
|
|
48
|
+
'authLayer',
|
|
49
|
+
'autoIterateLayer',
|
|
50
|
+
'dateShortcutsLayer',
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
type LegacyLayerName = (typeof LEGACY_LAYER_NAMES)[number];
|
|
54
|
+
|
|
55
|
+
interface LegacyLayerMigration {
|
|
56
|
+
readonly guidance: string;
|
|
57
|
+
readonly removedIn: 'TRL-475' | 'TRL-476';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const LEGACY_LAYER_MIGRATIONS: Record<LegacyLayerName, LegacyLayerMigration> = {
|
|
61
|
+
authLayer: {
|
|
62
|
+
guidance: 'Permit enforcement is intrinsic to executeTrail',
|
|
63
|
+
removedIn: 'TRL-475',
|
|
64
|
+
},
|
|
65
|
+
autoIterateLayer: {
|
|
66
|
+
guidance: 'Pagination uses CLI surface derivation (--all) per TRL-469',
|
|
67
|
+
removedIn: 'TRL-476',
|
|
68
|
+
},
|
|
69
|
+
dateShortcutsLayer: {
|
|
70
|
+
guidance: 'Date shortcuts use CLI surface derivation per TRL-470',
|
|
71
|
+
removedIn: 'TRL-476',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Path suffixes (in POSIX form) for source files that legitimately reference
|
|
77
|
+
* one or more removed legacy layer names. Other files are flagged.
|
|
78
|
+
*
|
|
79
|
+
* Each entry is matched against the normalized (forward-slash) absolute path
|
|
80
|
+
* with a trailing-segment match, so the rule stays correct regardless of the
|
|
81
|
+
* consumer's repository root.
|
|
82
|
+
*/
|
|
83
|
+
const ALLOWED_PATH_SUFFIXES: readonly string[] = [
|
|
84
|
+
// The CLI pagination module documents the removed `autoIterateLayer` in a
|
|
85
|
+
// migration note for apps moving onto the derived `--all` ergonomics.
|
|
86
|
+
'/packages/cli/src/pagination.ts',
|
|
87
|
+
// The CLI date-shortcuts module documents the removed `dateShortcutsLayer`
|
|
88
|
+
// in a migration note for apps moving onto the derived expansion helpers.
|
|
89
|
+
'/packages/cli/src/date-shortcuts.ts',
|
|
90
|
+
// The rule's own implementation file references the literals it searches for.
|
|
91
|
+
'/packages/warden/src/rules/no-legacy-layer-imports.ts',
|
|
92
|
+
// Warden rule metadata names the legacy symbols in the rule's invariant
|
|
93
|
+
// string for traceability between the rule and the removed exports.
|
|
94
|
+
'/packages/warden/src/rules/metadata.ts',
|
|
95
|
+
// The rule trail includes an executable example that demonstrates the
|
|
96
|
+
// diagnostic emitted for legacy layer imports.
|
|
97
|
+
'/packages/warden/src/trails/no-legacy-layer-imports.trail.ts',
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const normalizePath = (filePath: string): string =>
|
|
101
|
+
resolve(filePath).split(sep).join('/');
|
|
102
|
+
|
|
103
|
+
const isAllowedFile = (filePath: string): boolean => {
|
|
104
|
+
const normalized = normalizePath(filePath);
|
|
105
|
+
return ALLOWED_PATH_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
interface Match {
|
|
109
|
+
readonly name: LegacyLayerName;
|
|
110
|
+
readonly index: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const IDENTIFIER_CHAR = /[$0-9A-Z_a-z]/u;
|
|
114
|
+
|
|
115
|
+
const isIdentifierChar = (value: string): boolean =>
|
|
116
|
+
value !== '' && IDENTIFIER_CHAR.test(value);
|
|
117
|
+
|
|
118
|
+
const indexOfStandaloneName = (
|
|
119
|
+
sourceCode: string,
|
|
120
|
+
name: LegacyLayerName
|
|
121
|
+
): number => {
|
|
122
|
+
let fromIndex = 0;
|
|
123
|
+
while (fromIndex < sourceCode.length) {
|
|
124
|
+
const index = sourceCode.indexOf(name, fromIndex);
|
|
125
|
+
if (index === -1) {
|
|
126
|
+
return -1;
|
|
127
|
+
}
|
|
128
|
+
const before = index === 0 ? '' : (sourceCode[index - 1] ?? '');
|
|
129
|
+
const after = sourceCode[index + name.length] ?? '';
|
|
130
|
+
if (!(isIdentifierChar(before) || isIdentifierChar(after))) {
|
|
131
|
+
return index;
|
|
132
|
+
}
|
|
133
|
+
fromIndex = index + name.length;
|
|
134
|
+
}
|
|
135
|
+
return -1;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const findFirstMatch = (sourceCode: string): Match | null => {
|
|
139
|
+
let earliest: Match | null = null;
|
|
140
|
+
for (const name of LEGACY_LAYER_NAMES) {
|
|
141
|
+
const index = indexOfStandaloneName(sourceCode, name);
|
|
142
|
+
if (index === -1) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (earliest === null || index < earliest.index) {
|
|
146
|
+
earliest = { index, name };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return earliest;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const lineForOffset = (sourceCode: string, offset: number): number => {
|
|
153
|
+
let line = 1;
|
|
154
|
+
for (let i = 0; i < offset; i += 1) {
|
|
155
|
+
if (sourceCode.codePointAt(i) === 10) {
|
|
156
|
+
line += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return line;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const buildMessage = (name: LegacyLayerName): string => {
|
|
163
|
+
const migration = LEGACY_LAYER_MIGRATIONS[name];
|
|
164
|
+
return `Legacy layer '${name}' was removed in ${migration.removedIn}. ${migration.guidance}. See docs/adr/0043-layer-evolution.md.`;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build the term-rewrite fix metadata for a legacy layer finding.
|
|
169
|
+
*
|
|
170
|
+
* These layers were removed, not renamed, so there is no mechanical
|
|
171
|
+
* replacement: the fix is review-required and carries no edits. It still
|
|
172
|
+
* advertises the transform class and migration reason so `warden --fix`
|
|
173
|
+
* reports (but never auto-applies) it and downstream regrades can route it.
|
|
174
|
+
*/
|
|
175
|
+
const buildFix = (name: LegacyLayerName): WardenFix => {
|
|
176
|
+
const migration = LEGACY_LAYER_MIGRATIONS[name];
|
|
177
|
+
return {
|
|
178
|
+
class: 'term-rewrite',
|
|
179
|
+
reason: `Legacy layer '${name}' was removed in ${migration.removedIn}; ${migration.guidance}. Removal has no mechanical replacement, so it needs human migration.`,
|
|
180
|
+
safety: 'review',
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Flags references to the removed legacy layer symbols in committed source.
|
|
186
|
+
*/
|
|
187
|
+
export const noLegacyLayerImports: WardenRule = {
|
|
188
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
189
|
+
if (isAllowedFile(filePath)) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const match = findFirstMatch(sourceCode);
|
|
193
|
+
if (!match) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
filePath,
|
|
199
|
+
fix: buildFix(match.name),
|
|
200
|
+
line: lineForOffset(sourceCode, match.index),
|
|
201
|
+
message: buildMessage(match.name),
|
|
202
|
+
rule: RULE_NAME,
|
|
203
|
+
severity: 'error',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
},
|
|
207
|
+
description:
|
|
208
|
+
'Disallow references to legacy layer exports (authLayer, autoIterateLayer, dateShortcutsLayer) removed across TRL-475/TRL-476.',
|
|
209
|
+
name: RULE_NAME,
|
|
210
|
+
severity: 'error',
|
|
211
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getNodeArguments,
|
|
3
|
+
getNodeCallee,
|
|
4
|
+
getNodeObject,
|
|
5
|
+
getNodeProperty,
|
|
6
|
+
identifierName,
|
|
7
|
+
offsetToLine,
|
|
8
|
+
parse,
|
|
9
|
+
walk,
|
|
10
|
+
} from '@ontrails/source';
|
|
11
|
+
import type { AstNode } from '@ontrails/source';
|
|
12
|
+
import { isFrameworkInternalFile } from './scan.js';
|
|
13
|
+
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
14
|
+
|
|
15
|
+
const RULE_NAME = 'no-native-error-result';
|
|
16
|
+
|
|
17
|
+
const NATIVE_ERROR_CONSTRUCTORS = new Set([
|
|
18
|
+
'AggregateError',
|
|
19
|
+
'Error',
|
|
20
|
+
'EvalError',
|
|
21
|
+
'RangeError',
|
|
22
|
+
'ReferenceError',
|
|
23
|
+
'SyntaxError',
|
|
24
|
+
'TypeError',
|
|
25
|
+
'URIError',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const getMemberPropertyName = (node: AstNode): string | null => {
|
|
29
|
+
if (
|
|
30
|
+
node.type !== 'MemberExpression' &&
|
|
31
|
+
node.type !== 'StaticMemberExpression'
|
|
32
|
+
) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return identifierName(getNodeProperty(node));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isResultObject = (node: AstNode | undefined): boolean => {
|
|
40
|
+
if (!node) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (identifierName(node) === 'Result') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return getMemberPropertyName(node) === 'Result';
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const isResultErrCall = (node: AstNode): boolean => {
|
|
52
|
+
if (node.type !== 'CallExpression') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const callee = getNodeCallee(node);
|
|
57
|
+
if (!callee || getMemberPropertyName(callee) !== 'err') {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return isResultObject(getNodeObject(callee));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const isNativeErrorConstruction = (node: AstNode | undefined): boolean => {
|
|
65
|
+
if (!node || node.type !== 'NewExpression') {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const constructorName = identifierName(getNodeCallee(node));
|
|
70
|
+
return constructorName
|
|
71
|
+
? NATIVE_ERROR_CONSTRUCTORS.has(constructorName)
|
|
72
|
+
: false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getFirstArgument = (node: AstNode): AstNode | undefined =>
|
|
76
|
+
getNodeArguments(node)?.[0];
|
|
77
|
+
|
|
78
|
+
const createDiagnostic = (
|
|
79
|
+
filePath: string,
|
|
80
|
+
sourceCode: string,
|
|
81
|
+
node: AstNode
|
|
82
|
+
): WardenDiagnostic => ({
|
|
83
|
+
filePath,
|
|
84
|
+
line: offsetToLine(sourceCode, node.start),
|
|
85
|
+
message:
|
|
86
|
+
'Use a specific TrailsError subclass with Result.err(...) instead of native Error.',
|
|
87
|
+
rule: RULE_NAME,
|
|
88
|
+
severity: 'error',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const noNativeErrorResult: WardenRule = {
|
|
92
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
93
|
+
if (isFrameworkInternalFile(filePath)) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ast = parse(filePath, sourceCode);
|
|
98
|
+
if (!ast) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
103
|
+
walk(ast, (node) => {
|
|
104
|
+
if (
|
|
105
|
+
isResultErrCall(node) &&
|
|
106
|
+
isNativeErrorConstruction(getFirstArgument(node))
|
|
107
|
+
) {
|
|
108
|
+
diagnostics.push(createDiagnostic(filePath, sourceCode, node));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return diagnostics;
|
|
113
|
+
},
|
|
114
|
+
description:
|
|
115
|
+
'Require Result.err(...) calls to carry specific TrailsError subclasses instead of native Error.',
|
|
116
|
+
name: RULE_NAME,
|
|
117
|
+
severity: 'error',
|
|
118
|
+
};
|