@ontrails/warden 1.0.0-beta.2 → 1.0.0-beta.21
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 +497 -6
- package/README.md +77 -26
- package/bin/warden.ts +50 -0
- package/package.json +27 -5
- package/src/adapter-check.ts +136 -0
- package/src/ast.ts +28 -0
- package/src/cli.ts +1374 -103
- package/src/command.ts +953 -0
- package/src/config.ts +184 -0
- package/src/draft.ts +22 -0
- package/src/drift.ts +106 -22
- package/src/fix.ts +120 -0
- package/src/formatters.ts +79 -9
- package/src/guide.ts +245 -0
- package/src/index.ts +206 -14
- package/src/project-context.ts +163 -0
- package/src/resolve.ts +530 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/ast.ts +3176 -85
- package/src/rules/circular-refs.ts +154 -0
- package/src/rules/composes-declarations.ts +704 -0
- package/src/rules/context-no-surface-types.ts +68 -8
- package/src/rules/contour-exists.ts +251 -0
- package/src/rules/contour-ids.ts +15 -0
- package/src/rules/dead-internal-trail.ts +154 -0
- package/src/rules/draft-file-marking.ts +160 -0
- package/src/rules/draft-visible-debt.ts +87 -0
- package/src/rules/error-mapping-completeness.ts +288 -0
- package/src/rules/example-valid.ts +401 -0
- package/src/rules/fires-declarations.ts +758 -0
- package/src/rules/implementation-returns-result.ts +1265 -95
- package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
- package/src/rules/incomplete-crud.ts +580 -0
- package/src/rules/index.ts +219 -18
- package/src/rules/intent-propagation.ts +127 -0
- package/src/rules/layer-field-name-drift.ts +96 -0
- package/src/rules/metadata.ts +654 -0
- package/src/rules/missing-reconcile.ts +98 -0
- package/src/rules/missing-visibility.ts +110 -0
- package/src/rules/no-destructured-compose.ts +192 -0
- package/src/rules/no-dev-permit-in-source.ts +99 -0
- package/src/rules/no-direct-implementation-call.ts +7 -7
- package/src/rules/no-legacy-layer-imports.ts +211 -0
- package/src/rules/no-native-error-result.ts +111 -0
- package/src/rules/no-redundant-result-error-wrap.ts +331 -0
- package/src/rules/no-retired-cross-vocabulary.ts +194 -0
- package/src/rules/no-sync-result-assumption.ts +1134 -99
- package/src/rules/no-throw-in-detour-recover.ts +225 -0
- package/src/rules/no-throw-in-implementation.ts +10 -9
- package/src/rules/no-top-level-surface.ts +389 -0
- package/src/rules/on-references-exist.ts +194 -0
- package/src/rules/orphaned-signal.ts +150 -0
- package/src/rules/owner-projection-parity.ts +146 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/public-export-example-coverage.ts +553 -0
- package/src/rules/public-internal-deep-imports.ts +517 -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 +187 -0
- package/src/rules/reference-exists.ts +98 -0
- package/src/rules/registry-names.ts +145 -0
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/resource-declarations.ts +704 -0
- package/src/rules/resource-exists.ts +179 -0
- package/src/rules/resource-id-grammar.ts +65 -0
- package/src/rules/resource-mock-coverage.ts +115 -0
- package/src/rules/scan.ts +38 -25
- package/src/rules/scheduled-destroy-intent.ts +44 -0
- package/src/rules/signal-graph-coaching.ts +191 -0
- package/src/rules/specs.ts +9 -5
- package/src/rules/static-resource-accessor-preference.ts +657 -0
- package/src/rules/surface-facet-coherence.ts +370 -0
- package/src/rules/trail-versioning-source.ts +1094 -0
- package/src/rules/trail-versioning-topo.ts +172 -0
- package/src/rules/types.ts +270 -6
- package/src/rules/unmaterialized-activation-source.ts +84 -0
- package/src/rules/unreachable-detour-shadowing.ts +344 -0
- package/src/rules/valid-describe-refs.ts +160 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +533 -0
- package/src/rules/warden-rules-use-ast.ts +996 -0
- package/src/rules/webhook-route-collision.ts +243 -0
- package/src/trails/activation-orphan.trail.ts +84 -0
- package/src/trails/circular-refs.trail.ts +29 -0
- package/src/trails/composes-declarations.trail.ts +22 -0
- package/src/trails/context-no-surface-types.trail.ts +21 -0
- package/src/trails/contour-exists.trail.ts +21 -0
- package/src/trails/dead-internal-trail.trail.ts +26 -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/error-mapping-completeness.trail.ts +29 -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-blaze.trail.ts +31 -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 +78 -0
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/layer-field-name-drift.trail.ts +39 -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-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 +162 -0
- package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
- package/src/trails/schema.ts +194 -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-facet-coherence.trail.ts +25 -0
- package/src/trails/topo.ts +6 -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 +45 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +213 -0
- package/src/workspaces.ts +238 -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 -221
- 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 -163
- 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 -231
- package/dist/rules/implementation-returns-result.js.map +0 -1
- package/dist/rules/index.d.ts +0 -22
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -41
- 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 -46
- 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 -75
- 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 -188
- package/src/__tests__/valid-describe-refs.test.ts +0 -60
- package/src/rules/no-direct-impl-in-route.ts +0 -77
- 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,401 @@
|
|
|
1
|
+
import { contour } from '@ontrails/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
extractStringLiteral,
|
|
6
|
+
findConfigProperty,
|
|
7
|
+
findContourDefinitions,
|
|
8
|
+
getStringValue,
|
|
9
|
+
identifierName,
|
|
10
|
+
offsetToLine,
|
|
11
|
+
parse,
|
|
12
|
+
} from './ast.js';
|
|
13
|
+
import type { AstNode, ContourDefinition } from './ast.js';
|
|
14
|
+
import { isTestFile } from './scan.js';
|
|
15
|
+
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
16
|
+
|
|
17
|
+
class UnsupportedContourEvaluationError extends Error {}
|
|
18
|
+
|
|
19
|
+
type ContourEvaluationEnvironment = ReadonlyMap<string, unknown>;
|
|
20
|
+
|
|
21
|
+
const buildInvalidExampleDiagnostic = (
|
|
22
|
+
contourName: string,
|
|
23
|
+
message: string,
|
|
24
|
+
filePath: string,
|
|
25
|
+
line: number
|
|
26
|
+
): WardenDiagnostic => ({
|
|
27
|
+
filePath,
|
|
28
|
+
line,
|
|
29
|
+
message: `Contour "${contourName}" has invalid examples: ${message}`,
|
|
30
|
+
rule: 'example-valid',
|
|
31
|
+
severity: 'error',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const getPropertyName = (node: unknown): string | null => {
|
|
35
|
+
if (typeof node !== 'object' || node === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { name } = node as { readonly name?: unknown };
|
|
40
|
+
if (typeof name === 'string') {
|
|
41
|
+
return name;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return extractStringLiteral(node as AstNode);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const requireNode = (node: AstNode | undefined): AstNode => {
|
|
48
|
+
if (!node) {
|
|
49
|
+
throw new UnsupportedContourEvaluationError(
|
|
50
|
+
'Missing node in contour evaluation.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return node;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type ContourNodeEvaluator = (
|
|
58
|
+
node: AstNode,
|
|
59
|
+
env: ContourEvaluationEnvironment
|
|
60
|
+
) => unknown;
|
|
61
|
+
|
|
62
|
+
// The evaluator table is declared before `evaluateNode` so the recursive
|
|
63
|
+
// dispatch can read it at call time without any forward references. Entries
|
|
64
|
+
// are registered below once each per-type evaluator is defined.
|
|
65
|
+
const contourNodeEvaluators = new Map<string, ContourNodeEvaluator>();
|
|
66
|
+
|
|
67
|
+
const evaluateNode: ContourNodeEvaluator = (
|
|
68
|
+
node: AstNode,
|
|
69
|
+
env: ContourEvaluationEnvironment
|
|
70
|
+
): unknown => {
|
|
71
|
+
const evaluator = contourNodeEvaluators.get(node.type);
|
|
72
|
+
if (evaluator) {
|
|
73
|
+
return evaluator(node, env);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new UnsupportedContourEvaluationError(
|
|
77
|
+
`Unsupported AST node "${node.type}" in contour evaluation.`
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const evaluateArrayExpression: ContourNodeEvaluator = (
|
|
82
|
+
node: AstNode,
|
|
83
|
+
env: ContourEvaluationEnvironment
|
|
84
|
+
): readonly unknown[] => {
|
|
85
|
+
const elements = node['elements'] as readonly AstNode[] | undefined;
|
|
86
|
+
return (elements ?? []).map((element) => evaluateNode(element, env));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const evaluateLiteralExpression: ContourNodeEvaluator = (
|
|
90
|
+
node: AstNode
|
|
91
|
+
): unknown => getStringValue(node) ?? node.value;
|
|
92
|
+
|
|
93
|
+
const evaluateIdentifierExpression: ContourNodeEvaluator = (
|
|
94
|
+
node: AstNode,
|
|
95
|
+
env: ContourEvaluationEnvironment
|
|
96
|
+
): unknown => {
|
|
97
|
+
const name = identifierName(node);
|
|
98
|
+
if (name === 'undefined') {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (name === 'z') {
|
|
103
|
+
return z;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!name || !env.has(name)) {
|
|
107
|
+
throw new UnsupportedContourEvaluationError(
|
|
108
|
+
`Unknown identifier "${name ?? '<unknown>'}" in contour evaluation.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return env.get(name);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const evaluateWrappedExpression: ContourNodeEvaluator = (
|
|
116
|
+
node: AstNode,
|
|
117
|
+
env: ContourEvaluationEnvironment
|
|
118
|
+
): unknown =>
|
|
119
|
+
evaluateNode(
|
|
120
|
+
requireNode((node as unknown as { expression?: AstNode }).expression),
|
|
121
|
+
env
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const evaluateNullExpression: ContourNodeEvaluator = (): null => null;
|
|
125
|
+
|
|
126
|
+
const evaluateObjectExpression: ContourNodeEvaluator = (
|
|
127
|
+
node: AstNode,
|
|
128
|
+
env: ContourEvaluationEnvironment
|
|
129
|
+
): Record<string, unknown> => {
|
|
130
|
+
const properties = node['properties'] as readonly AstNode[] | undefined;
|
|
131
|
+
const value: Record<string, unknown> = {};
|
|
132
|
+
|
|
133
|
+
for (const property of properties ?? []) {
|
|
134
|
+
if (property.type !== 'Property') {
|
|
135
|
+
throw new UnsupportedContourEvaluationError(
|
|
136
|
+
`Unsupported object property type "${property.type}".`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const propertyName = getPropertyName(property.key);
|
|
141
|
+
if (!propertyName) {
|
|
142
|
+
throw new UnsupportedContourEvaluationError(
|
|
143
|
+
'Unsupported object property key in contour evaluation.'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
value[propertyName] = evaluateNode(
|
|
148
|
+
requireNode(property.value as AstNode | undefined),
|
|
149
|
+
env
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return value;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const evaluateCallArguments = (
|
|
157
|
+
node: AstNode,
|
|
158
|
+
env: ContourEvaluationEnvironment
|
|
159
|
+
): readonly unknown[] =>
|
|
160
|
+
((node['arguments'] as readonly AstNode[] | undefined) ?? []).map((arg) =>
|
|
161
|
+
evaluateNode(arg, env)
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const resolveContourFallbackOptions = (
|
|
165
|
+
shape: unknown
|
|
166
|
+
): Parameters<typeof contour>[2] | null =>
|
|
167
|
+
typeof shape === 'object' &&
|
|
168
|
+
shape !== null &&
|
|
169
|
+
Object.hasOwn(shape, 'id') &&
|
|
170
|
+
(shape as z.ZodRawShape)['id'] !== undefined
|
|
171
|
+
? ({ identity: 'id' } as Parameters<typeof contour>[2])
|
|
172
|
+
: null;
|
|
173
|
+
|
|
174
|
+
const resolveContourOptions = (
|
|
175
|
+
shape: unknown,
|
|
176
|
+
options: unknown
|
|
177
|
+
): Parameters<typeof contour>[2] => {
|
|
178
|
+
if (options === undefined) {
|
|
179
|
+
const fallbackOptions = resolveContourFallbackOptions(shape);
|
|
180
|
+
if (fallbackOptions !== null) {
|
|
181
|
+
return fallbackOptions;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
throw new UnsupportedContourEvaluationError(
|
|
185
|
+
'Contour evaluator requires literal options, or an `id` field when options are omitted.'
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (typeof options !== 'object' || options === null) {
|
|
190
|
+
throw new UnsupportedContourEvaluationError(
|
|
191
|
+
'Contour evaluator requires literal options when provided.'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return options as Parameters<typeof contour>[2];
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const evaluateContourCall = (args: readonly unknown[]): unknown => {
|
|
199
|
+
const [name, shape, options] = args;
|
|
200
|
+
if (typeof name !== 'string') {
|
|
201
|
+
throw new UnsupportedContourEvaluationError(
|
|
202
|
+
'Contour evaluator requires a literal name.'
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return contour(
|
|
207
|
+
name,
|
|
208
|
+
shape as z.ZodRawShape,
|
|
209
|
+
resolveContourOptions(shape, options)
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const evaluateMemberCall = (
|
|
214
|
+
callee: AstNode,
|
|
215
|
+
args: readonly unknown[],
|
|
216
|
+
env: ContourEvaluationEnvironment
|
|
217
|
+
): unknown => {
|
|
218
|
+
const receiver = evaluateNode(
|
|
219
|
+
requireNode((callee as unknown as { object?: AstNode }).object),
|
|
220
|
+
env
|
|
221
|
+
);
|
|
222
|
+
const propertyName = getPropertyName(
|
|
223
|
+
(callee as unknown as { property?: AstNode }).property
|
|
224
|
+
);
|
|
225
|
+
if (!propertyName) {
|
|
226
|
+
throw new UnsupportedContourEvaluationError(
|
|
227
|
+
'Unsupported member property in contour evaluation.'
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const method = (receiver as Record<string, unknown>)[propertyName];
|
|
232
|
+
if (typeof method !== 'function') {
|
|
233
|
+
throw new UnsupportedContourEvaluationError(
|
|
234
|
+
`Contour evaluator could not call "${propertyName}".`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return Reflect.apply(method, receiver, args);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const evaluateCallExpression: ContourNodeEvaluator = (
|
|
242
|
+
node: AstNode,
|
|
243
|
+
env: ContourEvaluationEnvironment
|
|
244
|
+
): unknown => {
|
|
245
|
+
const args = evaluateCallArguments(node, env);
|
|
246
|
+
const callee = requireNode(node['callee'] as AstNode | undefined);
|
|
247
|
+
|
|
248
|
+
if (callee.type === 'Identifier') {
|
|
249
|
+
const calleeName = identifierName(callee);
|
|
250
|
+
if (calleeName !== 'contour') {
|
|
251
|
+
throw new UnsupportedContourEvaluationError(
|
|
252
|
+
`Unsupported contour evaluator call "${calleeName ?? '<unknown>'}".`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return evaluateContourCall(args);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
callee.type !== 'MemberExpression' &&
|
|
261
|
+
callee.type !== 'StaticMemberExpression'
|
|
262
|
+
) {
|
|
263
|
+
throw new UnsupportedContourEvaluationError(
|
|
264
|
+
`Unsupported callee type "${callee.type}".`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return evaluateMemberCall(callee, args, env);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const EVALUATOR_REGISTRATIONS: readonly (readonly [
|
|
272
|
+
string,
|
|
273
|
+
ContourNodeEvaluator,
|
|
274
|
+
])[] = [
|
|
275
|
+
['ArrayExpression', evaluateArrayExpression],
|
|
276
|
+
['BooleanLiteral', evaluateLiteralExpression],
|
|
277
|
+
['Literal', evaluateLiteralExpression],
|
|
278
|
+
['NumericLiteral', evaluateLiteralExpression],
|
|
279
|
+
['StringLiteral', evaluateLiteralExpression],
|
|
280
|
+
['CallExpression', evaluateCallExpression],
|
|
281
|
+
['Identifier', evaluateIdentifierExpression],
|
|
282
|
+
['NullLiteral', evaluateNullExpression],
|
|
283
|
+
['ObjectExpression', evaluateObjectExpression],
|
|
284
|
+
['ParenthesizedExpression', evaluateWrappedExpression],
|
|
285
|
+
['TSAsExpression', evaluateWrappedExpression],
|
|
286
|
+
['TSSatisfiesExpression', evaluateWrappedExpression],
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
for (const [type, evaluator] of EVALUATOR_REGISTRATIONS) {
|
|
290
|
+
contourNodeEvaluators.set(type, evaluator);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const hasExamples = (definition: ContourDefinition): boolean =>
|
|
294
|
+
definition.options !== null &&
|
|
295
|
+
findConfigProperty(definition.options, 'examples') !== null;
|
|
296
|
+
|
|
297
|
+
const evaluateContourDefinition = (
|
|
298
|
+
definition: ContourDefinition,
|
|
299
|
+
env: ContourEvaluationEnvironment
|
|
300
|
+
): unknown => evaluateNode(definition.call, env);
|
|
301
|
+
|
|
302
|
+
const buildContourExampleErrorDiagnostic = (
|
|
303
|
+
definition: ContourDefinition,
|
|
304
|
+
error: unknown,
|
|
305
|
+
sourceCode: string,
|
|
306
|
+
filePath: string
|
|
307
|
+
): WardenDiagnostic | null => {
|
|
308
|
+
if (
|
|
309
|
+
error instanceof UnsupportedContourEvaluationError ||
|
|
310
|
+
!hasExamples(definition) ||
|
|
311
|
+
!(error instanceof Error)
|
|
312
|
+
) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return buildInvalidExampleDiagnostic(
|
|
317
|
+
definition.name,
|
|
318
|
+
error.message,
|
|
319
|
+
filePath,
|
|
320
|
+
offsetToLine(sourceCode, definition.start)
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const evaluateContourExamples = (
|
|
325
|
+
definition: ContourDefinition,
|
|
326
|
+
env: Map<string, unknown>,
|
|
327
|
+
sourceCode: string,
|
|
328
|
+
filePath: string
|
|
329
|
+
): WardenDiagnostic | null => {
|
|
330
|
+
try {
|
|
331
|
+
const value = evaluateContourDefinition(definition, env);
|
|
332
|
+
if (definition.bindingName) {
|
|
333
|
+
env.set(definition.bindingName, value);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return null;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return buildContourExampleErrorDiagnostic(
|
|
339
|
+
definition,
|
|
340
|
+
error,
|
|
341
|
+
sourceCode,
|
|
342
|
+
filePath
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const collectContourExampleDiagnostics = (
|
|
348
|
+
definitions: readonly ContourDefinition[],
|
|
349
|
+
sourceCode: string,
|
|
350
|
+
filePath: string
|
|
351
|
+
): readonly WardenDiagnostic[] => {
|
|
352
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
353
|
+
const env = new Map<string, unknown>();
|
|
354
|
+
|
|
355
|
+
for (const definition of definitions) {
|
|
356
|
+
const diagnostic = evaluateContourExamples(
|
|
357
|
+
definition,
|
|
358
|
+
env,
|
|
359
|
+
sourceCode,
|
|
360
|
+
filePath
|
|
361
|
+
);
|
|
362
|
+
if (diagnostic) {
|
|
363
|
+
diagnostics.push(diagnostic);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return diagnostics;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const checkContourExamples = (
|
|
371
|
+
sourceCode: string,
|
|
372
|
+
filePath: string
|
|
373
|
+
): readonly WardenDiagnostic[] => {
|
|
374
|
+
if (isTestFile(filePath)) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const ast = parse(filePath, sourceCode);
|
|
379
|
+
if (!ast) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return collectContourExampleDiagnostics(
|
|
384
|
+
findContourDefinitions(ast),
|
|
385
|
+
sourceCode,
|
|
386
|
+
filePath
|
|
387
|
+
);
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Checks that contour examples validate against their schema.
|
|
392
|
+
*/
|
|
393
|
+
export const exampleValid: WardenRule = {
|
|
394
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
395
|
+
return checkContourExamples(sourceCode, filePath);
|
|
396
|
+
},
|
|
397
|
+
description:
|
|
398
|
+
'Ensure every contour example validates against the declared contour schema.',
|
|
399
|
+
name: 'example-valid',
|
|
400
|
+
severity: 'error',
|
|
401
|
+
};
|