@ontrails/warden 1.0.0-beta.12 → 1.0.0-beta.13
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 +51 -31
- package/README.md +17 -17
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -10
- package/dist/cli.js.map +1 -1
- package/dist/drift.d.ts +6 -6
- package/dist/drift.d.ts.map +1 -1
- package/dist/drift.js +8 -8
- package/dist/drift.js.map +1 -1
- package/dist/formatters.js +2 -2
- package/dist/formatters.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/rules/ast.d.ts +15 -11
- package/dist/rules/ast.d.ts.map +1 -1
- package/dist/rules/ast.js +34 -30
- package/dist/rules/ast.js.map +1 -1
- package/dist/rules/context-no-trailhead-types.d.ts +12 -0
- package/dist/rules/context-no-trailhead-types.d.ts.map +1 -0
- package/dist/rules/context-no-trailhead-types.js +96 -0
- package/dist/rules/context-no-trailhead-types.js.map +1 -0
- package/dist/rules/cross-declarations.d.ts +13 -0
- package/dist/rules/cross-declarations.d.ts.map +1 -0
- package/dist/rules/cross-declarations.js +264 -0
- package/dist/rules/cross-declarations.js.map +1 -0
- package/dist/rules/follow-declarations.d.ts +1 -1
- package/dist/rules/follow-declarations.js +5 -5
- package/dist/rules/follow-declarations.js.map +1 -1
- package/dist/rules/implementation-returns-result.d.ts +2 -2
- package/dist/rules/implementation-returns-result.js +6 -6
- package/dist/rules/implementation-returns-result.js.map +1 -1
- package/dist/rules/index.d.ts +4 -4
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +12 -12
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/no-direct-impl-in-route.d.ts +4 -4
- package/dist/rules/no-direct-impl-in-route.js +14 -14
- package/dist/rules/no-direct-impl-in-route.js.map +1 -1
- package/dist/rules/no-direct-implementation-call.d.ts +3 -3
- package/dist/rules/no-direct-implementation-call.js +7 -7
- package/dist/rules/no-direct-implementation-call.js.map +1 -1
- package/dist/rules/no-sync-result-assumption.d.ts +1 -1
- package/dist/rules/no-sync-result-assumption.js +5 -5
- package/dist/rules/no-sync-result-assumption.js.map +1 -1
- package/dist/rules/no-throw-in-detour-target.js +2 -2
- package/dist/rules/no-throw-in-detour-target.js.map +1 -1
- package/dist/rules/no-throw-in-implementation.d.ts +1 -1
- package/dist/rules/no-throw-in-implementation.js +3 -3
- package/dist/rules/no-throw-in-implementation.js.map +1 -1
- package/dist/rules/provision-declarations.d.ts +14 -0
- package/dist/rules/provision-declarations.d.ts.map +1 -0
- package/dist/rules/provision-declarations.js +344 -0
- package/dist/rules/provision-declarations.js.map +1 -0
- package/dist/rules/provision-exists.d.ts +6 -0
- package/dist/rules/provision-exists.d.ts.map +1 -0
- package/dist/rules/provision-exists.js +89 -0
- package/dist/rules/provision-exists.js.map +1 -0
- package/dist/rules/service-declarations.d.ts +7 -5
- package/dist/rules/service-declarations.d.ts.map +1 -1
- package/dist/rules/service-declarations.js +106 -103
- package/dist/rules/service-declarations.js.map +1 -1
- package/dist/rules/service-exists.d.ts +3 -1
- package/dist/rules/service-exists.d.ts.map +1 -1
- package/dist/rules/service-exists.js +35 -33
- package/dist/rules/service-exists.js.map +1 -1
- package/dist/rules/specs.d.ts +1 -1
- package/dist/rules/specs.d.ts.map +1 -1
- package/dist/rules/specs.js +1 -1
- package/dist/rules/specs.js.map +1 -1
- package/dist/rules/types.d.ts +2 -2
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/trails/context-no-surface-types.trail.js +1 -1
- package/dist/trails/context-no-trailhead-types.trail.d.ts +13 -0
- package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -0
- package/dist/trails/context-no-trailhead-types.trail.js +21 -0
- package/dist/trails/context-no-trailhead-types.trail.js.map +1 -0
- package/dist/trails/cross-declarations.trail.d.ts +13 -0
- package/dist/trails/cross-declarations.trail.d.ts.map +1 -0
- package/dist/trails/cross-declarations.trail.js +22 -0
- package/dist/trails/cross-declarations.trail.js.map +1 -0
- package/dist/trails/follow-declarations.trail.js +1 -1
- package/dist/trails/implementation-returns-result.trail.js +1 -1
- package/dist/trails/index.d.ts +4 -4
- package/dist/trails/index.d.ts.map +1 -1
- package/dist/trails/index.js +4 -4
- package/dist/trails/index.js.map +1 -1
- package/dist/trails/no-direct-impl-in-route.trail.js +4 -4
- package/dist/trails/no-direct-impl-in-route.trail.js.map +1 -1
- package/dist/trails/no-direct-implementation-call.trail.js +2 -2
- package/dist/trails/no-direct-implementation-call.trail.js.map +1 -1
- package/dist/trails/no-sync-result-assumption.trail.js +2 -2
- package/dist/trails/no-sync-result-assumption.trail.js.map +1 -1
- package/dist/trails/no-throw-in-detour-target.trail.d.ts +1 -1
- package/dist/trails/no-throw-in-detour-target.trail.js +1 -1
- package/dist/trails/no-throw-in-implementation.trail.js +1 -1
- package/dist/trails/prefer-schema-inference.trail.js +1 -1
- package/dist/trails/provision-declarations.trail.d.ts +13 -0
- package/dist/trails/provision-declarations.trail.d.ts.map +1 -0
- package/dist/trails/provision-declarations.trail.js +25 -0
- package/dist/trails/provision-declarations.trail.js.map +1 -0
- package/dist/trails/provision-exists.trail.d.ts +15 -0
- package/dist/trails/provision-exists.trail.d.ts.map +1 -0
- package/dist/trails/provision-exists.trail.js +27 -0
- package/dist/trails/provision-exists.trail.js.map +1 -0
- package/dist/trails/run.d.ts +2 -2
- package/dist/trails/run.d.ts.map +1 -1
- package/dist/trails/run.js +6 -6
- package/dist/trails/run.js.map +1 -1
- package/dist/trails/schema.d.ts +1 -1
- package/dist/trails/schema.js +2 -2
- package/dist/trails/schema.js.map +1 -1
- package/dist/trails/service-declarations.trail.d.ts +13 -0
- package/dist/trails/service-declarations.trail.d.ts.map +1 -1
- package/dist/trails/service-declarations.trail.js +9 -7
- package/dist/trails/service-declarations.trail.js.map +1 -1
- package/dist/trails/service-exists.trail.d.ts +17 -0
- package/dist/trails/service-exists.trail.d.ts.map +1 -1
- package/dist/trails/service-exists.trail.js +10 -8
- package/dist/trails/service-exists.trail.js.map +1 -1
- package/dist/trails/valid-describe-refs.trail.d.ts +1 -1
- package/dist/trails/valid-detour-refs.trail.d.ts +1 -1
- package/dist/trails/valid-detour-refs.trail.js +2 -2
- package/dist/trails/wrap-rule.js +14 -14
- package/dist/trails/wrap-rule.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +8 -8
- package/src/__tests__/{follow-declarations.test.ts → cross-declarations.test.ts} +78 -78
- package/src/__tests__/drift.test.ts +5 -5
- package/src/__tests__/formatters.test.ts +2 -2
- package/src/__tests__/implementation-returns-result.test.ts +11 -11
- package/src/__tests__/no-direct-implementation-call.test.ts +10 -10
- package/src/__tests__/no-sync-result-assumption.test.ts +6 -6
- package/src/__tests__/no-throw-in-detour-target.test.ts +6 -6
- package/src/__tests__/prefer-schema-inference.test.ts +4 -4
- package/src/__tests__/provision-declarations.test.ts +318 -0
- package/src/__tests__/provision-exists.test.ts +122 -0
- package/src/__tests__/rules.test.ts +38 -38
- package/src/__tests__/valid-describe-refs.test.ts +4 -4
- package/src/__tests__/wrap-rule.test.ts +4 -4
- package/src/cli.ts +17 -13
- package/src/drift.ts +12 -12
- package/src/formatters.ts +2 -2
- package/src/index.ts +8 -8
- package/src/rules/ast.ts +36 -31
- package/src/rules/{context-no-surface-types.ts → context-no-trailhead-types.ts} +8 -8
- package/src/rules/{follow-declarations.ts → cross-declarations.ts} +63 -56
- package/src/rules/implementation-returns-result.ts +6 -6
- package/src/rules/index.ts +12 -12
- package/src/rules/no-direct-impl-in-route.ts +17 -17
- package/src/rules/no-direct-implementation-call.ts +7 -7
- package/src/rules/no-sync-result-assumption.ts +5 -5
- package/src/rules/no-throw-in-detour-target.ts +2 -2
- package/src/rules/no-throw-in-implementation.ts +3 -3
- package/src/rules/{service-declarations.ts → provision-declarations.ts} +145 -129
- package/src/rules/{service-exists.ts → provision-exists.ts} +51 -46
- package/src/rules/specs.ts +4 -4
- package/src/rules/types.ts +2 -2
- package/src/trails/{context-no-surface-types.trail.ts → context-no-trailhead-types.trail.ts} +5 -5
- package/src/trails/cross-declarations.trail.ts +22 -0
- package/src/trails/implementation-returns-result.trail.ts +1 -1
- package/src/trails/index.ts +4 -4
- package/src/trails/no-direct-impl-in-route.trail.ts +4 -4
- package/src/trails/no-direct-implementation-call.trail.ts +2 -2
- package/src/trails/no-sync-result-assumption.trail.ts +2 -2
- package/src/trails/no-throw-in-detour-target.trail.ts +1 -1
- package/src/trails/no-throw-in-implementation.trail.ts +1 -1
- package/src/trails/prefer-schema-inference.trail.ts +1 -1
- package/src/trails/provision-declarations.trail.ts +25 -0
- package/src/trails/provision-exists.trail.ts +27 -0
- package/src/trails/run.ts +7 -7
- package/src/trails/schema.ts +2 -2
- package/src/trails/valid-detour-refs.trail.ts +2 -2
- package/src/trails/wrap-rule.ts +17 -17
- package/tsconfig.tsbuildinfo +1 -1
- package/src/__tests__/service-declarations.test.ts +0 -318
- package/src/__tests__/service-exists.test.ts +0 -122
- package/src/trails/follow-declarations.trail.ts +0 -22
- package/src/trails/service-declarations.trail.ts +0 -25
- package/src/trails/service-exists.trail.ts +0 -27
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
collectNamedProvisionIds,
|
|
3
|
+
collectProvisionDefinitionIds,
|
|
4
4
|
extractFirstStringArg,
|
|
5
5
|
findConfigProperty,
|
|
6
6
|
findTrailDefinitions,
|
|
@@ -18,18 +18,18 @@ import type {
|
|
|
18
18
|
WardenDiagnostic,
|
|
19
19
|
} from './types.js';
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const isProvisionCall = (node: AstNode): boolean =>
|
|
22
22
|
node.type === 'CallExpression' &&
|
|
23
23
|
identifierName((node as unknown as { callee?: AstNode }).callee) ===
|
|
24
|
-
'
|
|
24
|
+
'provision';
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
if (!
|
|
26
|
+
const getProvisionElements = (config: AstNode): readonly AstNode[] => {
|
|
27
|
+
const provisionsProp = findConfigProperty(config, 'provisions');
|
|
28
|
+
if (!provisionsProp) {
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const arrayNode =
|
|
32
|
+
const arrayNode = provisionsProp.value;
|
|
33
33
|
if (!arrayNode || (arrayNode as AstNode).type !== 'ArrayExpression') {
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
@@ -40,93 +40,93 @@ const getServiceElements = (config: AstNode): readonly AstNode[] => {
|
|
|
40
40
|
return elements ?? [];
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const extractDeclaredProvisionId = (
|
|
44
44
|
element: AstNode,
|
|
45
|
-
|
|
45
|
+
provisionIdsByName: ReadonlyMap<string, string>
|
|
46
46
|
): string | null => {
|
|
47
47
|
if (element.type === 'Identifier') {
|
|
48
48
|
const name = identifierName(element);
|
|
49
|
-
return name ? (
|
|
49
|
+
return name ? (provisionIdsByName.get(name) ?? null) : null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
if (isStringLiteral(element)) {
|
|
53
53
|
return getStringValue(element);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
return
|
|
56
|
+
return isProvisionCall(element) ? extractFirstStringArg(element) : null;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const
|
|
59
|
+
const extractDeclaredProvisionIds = (
|
|
60
60
|
config: AstNode,
|
|
61
|
-
|
|
61
|
+
provisionIdsByName: ReadonlyMap<string, string>
|
|
62
62
|
): readonly string[] => [
|
|
63
63
|
...new Set(
|
|
64
|
-
|
|
65
|
-
const id =
|
|
64
|
+
getProvisionElements(config).flatMap((element) => {
|
|
65
|
+
const id = extractDeclaredProvisionId(element, provisionIdsByName);
|
|
66
66
|
return id ? [id] : [];
|
|
67
67
|
})
|
|
68
68
|
),
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
-
const
|
|
71
|
+
const buildMissingProvisionDiagnostic = (
|
|
72
72
|
trailId: string,
|
|
73
|
-
|
|
73
|
+
provisionId: string,
|
|
74
74
|
filePath: string,
|
|
75
75
|
line: number
|
|
76
76
|
): WardenDiagnostic => ({
|
|
77
77
|
filePath,
|
|
78
78
|
line,
|
|
79
|
-
message: `Trail "${trailId}" declares
|
|
80
|
-
rule: '
|
|
79
|
+
message: `Trail "${trailId}" declares provision "${provisionId}" which is not defined in the project.`,
|
|
80
|
+
rule: 'provision-exists',
|
|
81
81
|
severity: 'error',
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const reportMissingProvisions = (
|
|
85
85
|
def: { id: string; config: AstNode; start: number },
|
|
86
86
|
sourceCode: string,
|
|
87
|
-
|
|
87
|
+
provisionIdsByName: ReadonlyMap<string, string>,
|
|
88
88
|
filePath: string,
|
|
89
|
-
|
|
89
|
+
knownProvisionIds: ReadonlySet<string>,
|
|
90
90
|
diagnostics: WardenDiagnostic[]
|
|
91
91
|
): void => {
|
|
92
92
|
const line = offsetToLine(sourceCode, def.start);
|
|
93
|
-
for (const
|
|
93
|
+
for (const provisionId of extractDeclaredProvisionIds(
|
|
94
94
|
def.config,
|
|
95
|
-
|
|
95
|
+
provisionIdsByName
|
|
96
96
|
)) {
|
|
97
|
-
if (!
|
|
97
|
+
if (!knownProvisionIds.has(provisionId)) {
|
|
98
98
|
diagnostics.push(
|
|
99
|
-
|
|
99
|
+
buildMissingProvisionDiagnostic(def.id, provisionId, filePath, line)
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
-
const
|
|
105
|
+
const buildProvisionDiagnostics = (
|
|
106
106
|
ast: AstNode,
|
|
107
107
|
sourceCode: string,
|
|
108
108
|
filePath: string,
|
|
109
|
-
|
|
109
|
+
knownProvisionIds: ReadonlySet<string>
|
|
110
110
|
): readonly WardenDiagnostic[] => {
|
|
111
111
|
const diagnostics: WardenDiagnostic[] = [];
|
|
112
|
-
const
|
|
112
|
+
const provisionIdsByName = collectNamedProvisionIds(ast);
|
|
113
113
|
for (const def of findTrailDefinitions(ast)) {
|
|
114
|
-
|
|
114
|
+
reportMissingProvisions(
|
|
115
115
|
def,
|
|
116
116
|
sourceCode,
|
|
117
|
-
|
|
117
|
+
provisionIdsByName,
|
|
118
118
|
filePath,
|
|
119
|
-
|
|
119
|
+
knownProvisionIds,
|
|
120
120
|
diagnostics
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
return diagnostics;
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
const
|
|
126
|
+
const checkProvisionsExist = (
|
|
127
127
|
sourceCode: string,
|
|
128
128
|
filePath: string,
|
|
129
|
-
|
|
129
|
+
knownProvisionIds: ReadonlySet<string>
|
|
130
130
|
): readonly WardenDiagnostic[] => {
|
|
131
131
|
if (isTestFile(filePath)) {
|
|
132
132
|
return [];
|
|
@@ -137,22 +137,27 @@ const checkServicesExist = (
|
|
|
137
137
|
return [];
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
return
|
|
140
|
+
return buildProvisionDiagnostics(
|
|
141
|
+
ast,
|
|
142
|
+
sourceCode,
|
|
143
|
+
filePath,
|
|
144
|
+
knownProvisionIds
|
|
145
|
+
);
|
|
141
146
|
};
|
|
142
147
|
|
|
143
148
|
/**
|
|
144
|
-
* Checks that all declared
|
|
149
|
+
* Checks that all declared provisions resolve to known provision definitions.
|
|
145
150
|
*/
|
|
146
|
-
export const
|
|
151
|
+
export const provisionExists: ProjectAwareWardenRule = {
|
|
147
152
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
148
153
|
const ast = parse(filePath, sourceCode);
|
|
149
154
|
if (!ast) {
|
|
150
155
|
return [];
|
|
151
156
|
}
|
|
152
|
-
return
|
|
157
|
+
return checkProvisionsExist(
|
|
153
158
|
sourceCode,
|
|
154
159
|
filePath,
|
|
155
|
-
|
|
160
|
+
collectProvisionDefinitionIds(ast)
|
|
156
161
|
);
|
|
157
162
|
},
|
|
158
163
|
checkWithContext(
|
|
@@ -161,17 +166,17 @@ export const serviceExists: ProjectAwareWardenRule = {
|
|
|
161
166
|
context: ProjectContext
|
|
162
167
|
): readonly WardenDiagnostic[] {
|
|
163
168
|
const ast = parse(filePath, sourceCode);
|
|
164
|
-
const
|
|
165
|
-
?
|
|
169
|
+
const localProvisionIds = ast
|
|
170
|
+
? collectProvisionDefinitionIds(ast)
|
|
166
171
|
: new Set<string>();
|
|
167
|
-
return
|
|
172
|
+
return checkProvisionsExist(
|
|
168
173
|
sourceCode,
|
|
169
174
|
filePath,
|
|
170
|
-
context.
|
|
175
|
+
context.knownProvisionIds ?? localProvisionIds
|
|
171
176
|
);
|
|
172
177
|
},
|
|
173
178
|
description:
|
|
174
|
-
'Ensure every
|
|
175
|
-
name: '
|
|
179
|
+
'Ensure every provision declared on a trail resolves to a known provision definition.',
|
|
180
|
+
name: 'provision-exists',
|
|
176
181
|
severity: 'error',
|
|
177
182
|
};
|
package/src/rules/specs.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ObjectProperty extends ParsedEntry {
|
|
|
18
18
|
|
|
19
19
|
export interface TrailLikeSpec {
|
|
20
20
|
readonly id: string;
|
|
21
|
-
readonly kind: '
|
|
21
|
+
readonly kind: 'signal' | 'trail';
|
|
22
22
|
readonly line: number;
|
|
23
23
|
readonly properties: ReadonlyMap<string, ObjectProperty>;
|
|
24
24
|
readonly specText: string;
|
|
@@ -214,7 +214,7 @@ const resolveSpecId = (
|
|
|
214
214
|
|
|
215
215
|
const buildTrailLikeSpec = (
|
|
216
216
|
sourceCode: string,
|
|
217
|
-
kind: '
|
|
217
|
+
kind: 'signal' | 'trail',
|
|
218
218
|
specArg: SplitEntry,
|
|
219
219
|
specStart: number,
|
|
220
220
|
id: string,
|
|
@@ -276,7 +276,7 @@ const resolveTrailLikeSpec = (
|
|
|
276
276
|
|
|
277
277
|
const parseTrailLikeMatch = (
|
|
278
278
|
sourceCode: string,
|
|
279
|
-
kind: '
|
|
279
|
+
kind: 'signal' | 'trail',
|
|
280
280
|
callStart: number
|
|
281
281
|
): TrailLikeSpec | null => {
|
|
282
282
|
const resolved = resolveTrailLikeSpec(sourceCode, callStart);
|
|
@@ -352,7 +352,7 @@ export const findTrailLikeSpecs = (
|
|
|
352
352
|
continue;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
const kind = match[1] === '
|
|
355
|
+
const kind = match[1] === 'signal' ? 'signal' : 'trail';
|
|
356
356
|
const spec = parseTrailLikeMatch(sourceCode, kind, callStart);
|
|
357
357
|
if (spec !== null) {
|
|
358
358
|
specs.push(spec);
|
package/src/rules/types.ts
CHANGED
|
@@ -45,8 +45,8 @@ export interface WardenRule {
|
|
|
45
45
|
export interface ProjectContext {
|
|
46
46
|
/** All known trail IDs in the project */
|
|
47
47
|
readonly knownTrailIds: ReadonlySet<string>;
|
|
48
|
-
/** All known
|
|
49
|
-
readonly
|
|
48
|
+
/** All known provision IDs in the project */
|
|
49
|
+
readonly knownProvisionIds?: ReadonlySet<string>;
|
|
50
50
|
/** All trail IDs referenced as detour targets across the project */
|
|
51
51
|
readonly detourTargetTrailIds?: ReadonlySet<string>;
|
|
52
52
|
}
|
package/src/trails/{context-no-surface-types.trail.ts → context-no-trailhead-types.trail.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { contextNoTrailheadTypes } from '../rules/context-no-trailhead-types.js';
|
|
2
2
|
import { wrapRule } from './wrap-rule.js';
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const contextNoTrailheadTypesTrail = wrapRule({
|
|
5
5
|
examples: [
|
|
6
6
|
{
|
|
7
7
|
expected: { diagnostics: [] },
|
|
@@ -9,13 +9,13 @@ export const contextNoSurfaceTypesTrail = wrapRule({
|
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
10
|
sourceCode: `import { trail, Result } from "@ontrails/core";
|
|
11
11
|
trail("entity.show", {
|
|
12
|
-
|
|
12
|
+
blaze: async (input, ctx) => {
|
|
13
13
|
return Result.ok({ name: "test" });
|
|
14
14
|
}
|
|
15
15
|
})`,
|
|
16
16
|
},
|
|
17
|
-
name: 'Clean trail without
|
|
17
|
+
name: 'Clean trail without trailhead imports',
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
|
-
rule:
|
|
20
|
+
rule: contextNoTrailheadTypes,
|
|
21
21
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { crossDeclarations } from '../rules/cross-declarations.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const crossDeclarationsTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `trail("entity.onboard", {
|
|
11
|
+
crosses: ["entity.create"],
|
|
12
|
+
blaze: async (input, ctx) => {
|
|
13
|
+
const result = await ctx.cross("entity.create", input);
|
|
14
|
+
return Result.ok(result);
|
|
15
|
+
}
|
|
16
|
+
})`,
|
|
17
|
+
},
|
|
18
|
+
name: 'Matched crossing declarations and calls',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
rule: crossDeclarations,
|
|
22
|
+
});
|
package/src/trails/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { contextNoTrailheadTypesTrail } from './context-no-trailhead-types.trail.js';
|
|
2
|
+
export { crossDeclarationsTrail } from './cross-declarations.trail.js';
|
|
3
3
|
export { implementationReturnsResultTrail } from './implementation-returns-result.trail.js';
|
|
4
4
|
export { noDirectImplInRouteTrail } from './no-direct-impl-in-route.trail.js';
|
|
5
5
|
export { noDirectImplementationCallTrail } from './no-direct-implementation-call.trail.js';
|
|
@@ -7,8 +7,8 @@ export { noSyncResultAssumptionTrail } from './no-sync-result-assumption.trail.j
|
|
|
7
7
|
export { noThrowInDetourTargetTrail } from './no-throw-in-detour-target.trail.js';
|
|
8
8
|
export { noThrowInImplementationTrail } from './no-throw-in-implementation.trail.js';
|
|
9
9
|
export { preferSchemaInferenceTrail } from './prefer-schema-inference.trail.js';
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
10
|
+
export { provisionDeclarationsTrail } from './provision-declarations.trail.js';
|
|
11
|
+
export { provisionExistsTrail } from './provision-exists.trail.js';
|
|
12
12
|
export { validDescribeRefsTrail } from './valid-describe-refs.trail.js';
|
|
13
13
|
export { validDetourRefsTrail } from './valid-detour-refs.trail.js';
|
|
14
14
|
|
|
@@ -8,14 +8,14 @@ export const noDirectImplInRouteTrail = wrapRule({
|
|
|
8
8
|
input: {
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
10
|
sourceCode: `trail("entity.onboard", {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const result = await ctx.
|
|
11
|
+
crosses: ["entity.create"],
|
|
12
|
+
blaze: async (input, ctx) => {
|
|
13
|
+
const result = await ctx.cross("entity.create", input);
|
|
14
14
|
return Result.ok(result);
|
|
15
15
|
}
|
|
16
16
|
})`,
|
|
17
17
|
},
|
|
18
|
-
name: 'Trail with
|
|
18
|
+
name: 'Trail with crossings using ctx.cross()',
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
rule: noDirectImplInRoute,
|
|
@@ -7,9 +7,9 @@ export const noDirectImplementationCallTrail = wrapRule({
|
|
|
7
7
|
expected: { diagnostics: [] },
|
|
8
8
|
input: {
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
|
-
sourceCode: `const data = await ctx.
|
|
10
|
+
sourceCode: `const data = await ctx.cross("entity.show", { id: "1" });`,
|
|
11
11
|
},
|
|
12
|
-
name: 'Clean code using ctx.
|
|
12
|
+
name: 'Clean code using ctx.cross instead of .blaze()',
|
|
13
13
|
},
|
|
14
14
|
],
|
|
15
15
|
rule: noDirectImplementationCall,
|
|
@@ -7,12 +7,12 @@ export const noSyncResultAssumptionTrail = wrapRule({
|
|
|
7
7
|
expected: { diagnostics: [] },
|
|
8
8
|
input: {
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
|
-
sourceCode: `const result = await myTrail.
|
|
10
|
+
sourceCode: `const result = await myTrail.blaze(input, ctx);
|
|
11
11
|
if (result.isOk()) {
|
|
12
12
|
console.log(result.value);
|
|
13
13
|
}`,
|
|
14
14
|
},
|
|
15
|
-
name: 'Properly awaited .
|
|
15
|
+
name: 'Properly awaited .blaze() call',
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
18
|
rule: noSyncResultAssumption,
|
|
@@ -9,7 +9,7 @@ export const preferSchemaInferenceTrail = wrapRule({
|
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
10
|
sourceCode: `trail("entity.show", {
|
|
11
11
|
input: z.object({ name: z.string() }),
|
|
12
|
-
|
|
12
|
+
blaze: async (input, ctx) => {
|
|
13
13
|
return Result.ok({ name: input.name });
|
|
14
14
|
}
|
|
15
15
|
})`,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { provisionDeclarations } from '../rules/provision-declarations.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const provisionDeclarationsTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
sourceCode: `const db = provision("db.main", {
|
|
11
|
+
create: () => Result.ok({ source: "factory" }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
trail("entity.show", {
|
|
15
|
+
provisions: [db],
|
|
16
|
+
blaze: async (_input, ctx) => {
|
|
17
|
+
return Result.ok(db.from(ctx));
|
|
18
|
+
}
|
|
19
|
+
})`,
|
|
20
|
+
},
|
|
21
|
+
name: 'Matched provision declarations and usage',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
rule: provisionDeclarations,
|
|
25
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { provisionExists } from '../rules/provision-exists.js';
|
|
2
|
+
import { wrapRule } from './wrap-rule.js';
|
|
3
|
+
|
|
4
|
+
export const provisionExistsTrail = wrapRule({
|
|
5
|
+
examples: [
|
|
6
|
+
{
|
|
7
|
+
expected: { diagnostics: [] },
|
|
8
|
+
input: {
|
|
9
|
+
filePath: 'clean.ts',
|
|
10
|
+
knownProvisionIds: ['db.main'],
|
|
11
|
+
knownTrailIds: ['entity.show'],
|
|
12
|
+
sourceCode: `const db = provision("db.main", {
|
|
13
|
+
create: () => Result.ok({ source: "factory" }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
trail("entity.show", {
|
|
17
|
+
provisions: [db],
|
|
18
|
+
blaze: async (_input, ctx) => {
|
|
19
|
+
return Result.ok(db.from(ctx));
|
|
20
|
+
}
|
|
21
|
+
})`,
|
|
22
|
+
},
|
|
23
|
+
name: 'Declared provisions resolve to known project provisions',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
rule: provisionExists,
|
|
27
|
+
});
|
package/src/trails/run.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Returns a flat array of diagnostics from every rule.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { run } from '@ontrails/core';
|
|
8
8
|
|
|
9
9
|
import type { WardenDiagnostic } from '../rules/types.js';
|
|
10
10
|
import type { RuleOutput } from './schema.js';
|
|
11
11
|
import { wardenTopo } from './topo.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Run all warden rule trails for a given file and collect diagnostics.
|
|
15
15
|
*
|
|
16
16
|
* Each rule trail runs independently. Errors from individual trails are
|
|
17
17
|
* silently skipped so that one broken rule does not block the rest.
|
|
@@ -20,7 +20,7 @@ export const runWardenTrails = async (
|
|
|
20
20
|
filePath: string,
|
|
21
21
|
sourceCode: string,
|
|
22
22
|
options?: {
|
|
23
|
-
readonly
|
|
23
|
+
readonly knownProvisionIds?: readonly string[];
|
|
24
24
|
readonly knownTrailIds?: readonly string[];
|
|
25
25
|
}
|
|
26
26
|
): Promise<readonly WardenDiagnostic[]> => {
|
|
@@ -28,11 +28,11 @@ export const runWardenTrails = async (
|
|
|
28
28
|
|
|
29
29
|
for (const id of wardenTopo.ids()) {
|
|
30
30
|
const input =
|
|
31
|
-
options?.knownTrailIds || options?.
|
|
31
|
+
options?.knownTrailIds || options?.knownProvisionIds
|
|
32
32
|
? {
|
|
33
33
|
filePath,
|
|
34
|
-
...(options?.
|
|
35
|
-
? {
|
|
34
|
+
...(options?.knownProvisionIds
|
|
35
|
+
? { knownProvisionIds: options.knownProvisionIds }
|
|
36
36
|
: {}),
|
|
37
37
|
...(options?.knownTrailIds
|
|
38
38
|
? { knownTrailIds: options.knownTrailIds }
|
|
@@ -40,7 +40,7 @@ export const runWardenTrails = async (
|
|
|
40
40
|
sourceCode,
|
|
41
41
|
}
|
|
42
42
|
: { filePath, sourceCode };
|
|
43
|
-
const result = await
|
|
43
|
+
const result = await run(wardenTopo, id, input);
|
|
44
44
|
if (result.isOk()) {
|
|
45
45
|
const { diagnostics } = result.value as RuleOutput;
|
|
46
46
|
for (const d of diagnostics) {
|
package/src/trails/schema.ts
CHANGED
|
@@ -30,10 +30,10 @@ export const ruleInput = z.object({
|
|
|
30
30
|
* files.
|
|
31
31
|
*/
|
|
32
32
|
export const projectAwareRuleInput = ruleInput.extend({
|
|
33
|
-
|
|
33
|
+
knownProvisionIds: z
|
|
34
34
|
.array(z.string())
|
|
35
35
|
.optional()
|
|
36
|
-
.describe('
|
|
36
|
+
.describe('Provision IDs known across the project'),
|
|
37
37
|
knownTrailIds: z
|
|
38
38
|
.array(z.string())
|
|
39
39
|
.optional()
|
|
@@ -9,12 +9,12 @@ export const validDetourRefsTrail = wrapRule({
|
|
|
9
9
|
filePath: 'clean.ts',
|
|
10
10
|
knownTrailIds: ['entity.fallback', 'entity.show'],
|
|
11
11
|
sourceCode: `trail("entity.fallback", {
|
|
12
|
-
|
|
12
|
+
blaze: async (input, ctx) => Result.ok(data)
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
trail("entity.show", {
|
|
16
16
|
detours: [{ target: "entity.fallback" }],
|
|
17
|
-
|
|
17
|
+
blaze: async (input, ctx) => Result.ok(data)
|
|
18
18
|
})`,
|
|
19
19
|
},
|
|
20
20
|
name: 'Valid detour target reference',
|
package/src/trails/wrap-rule.ts
CHANGED
|
@@ -49,19 +49,10 @@ export function wrapRule(
|
|
|
49
49
|
if (isProjectAware) {
|
|
50
50
|
const projectAwareRule = rule as ProjectAwareWardenRule;
|
|
51
51
|
return trail(`warden.rule.${rule.name}`, {
|
|
52
|
-
|
|
53
|
-
examples: examples as Trail<
|
|
54
|
-
ProjectAwareRuleInput,
|
|
55
|
-
RuleOutput
|
|
56
|
-
>['examples'],
|
|
57
|
-
input: projectAwareRuleInput,
|
|
58
|
-
intent: 'read',
|
|
59
|
-
metadata: { category: 'governance', severity: rule.severity },
|
|
60
|
-
output: ruleOutput,
|
|
61
|
-
run: (input: ProjectAwareRuleInput) => {
|
|
52
|
+
blaze: (input: ProjectAwareRuleInput) => {
|
|
62
53
|
const context = {
|
|
63
|
-
|
|
64
|
-
? new Set(input.
|
|
54
|
+
knownProvisionIds: input.knownProvisionIds
|
|
55
|
+
? new Set(input.knownProvisionIds)
|
|
65
56
|
: undefined,
|
|
66
57
|
knownTrailIds: input.knownTrailIds
|
|
67
58
|
? new Set(input.knownTrailIds)
|
|
@@ -74,19 +65,28 @@ export function wrapRule(
|
|
|
74
65
|
);
|
|
75
66
|
return Result.ok({ diagnostics: [...diagnostics] });
|
|
76
67
|
},
|
|
68
|
+
description: rule.description,
|
|
69
|
+
examples: examples as Trail<
|
|
70
|
+
ProjectAwareRuleInput,
|
|
71
|
+
RuleOutput
|
|
72
|
+
>['examples'],
|
|
73
|
+
input: projectAwareRuleInput,
|
|
74
|
+
intent: 'read',
|
|
75
|
+
meta: { category: 'governance', severity: rule.severity },
|
|
76
|
+
output: ruleOutput,
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
return trail(`warden.rule.${rule.name}`, {
|
|
81
|
+
blaze: (input: RuleInput) => {
|
|
82
|
+
const diagnostics = rule.check(input.sourceCode, input.filePath);
|
|
83
|
+
return Result.ok({ diagnostics: [...diagnostics] });
|
|
84
|
+
},
|
|
81
85
|
description: rule.description,
|
|
82
86
|
examples: examples as Trail<RuleInput, RuleOutput>['examples'],
|
|
83
87
|
input: ruleInput,
|
|
84
88
|
intent: 'read',
|
|
85
|
-
|
|
89
|
+
meta: { category: 'governance', severity: rule.severity },
|
|
86
90
|
output: ruleOutput,
|
|
87
|
-
run: (input: RuleInput) => {
|
|
88
|
-
const diagnostics = rule.check(input.sourceCode, input.filePath);
|
|
89
|
-
return Result.ok({ diagnostics: [...diagnostics] });
|
|
90
|
-
},
|
|
91
91
|
});
|
|
92
92
|
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/cli.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-
|
|
1
|
+
{"root":["./src/cli.ts","./src/drift.ts","./src/formatters.ts","./src/index.ts","./src/rules/ast.ts","./src/rules/context-no-trailhead-types.ts","./src/rules/cross-declarations.ts","./src/rules/implementation-returns-result.ts","./src/rules/index.ts","./src/rules/no-direct-impl-in-route.ts","./src/rules/no-direct-implementation-call.ts","./src/rules/no-sync-result-assumption.ts","./src/rules/no-throw-in-detour-target.ts","./src/rules/no-throw-in-implementation.ts","./src/rules/prefer-schema-inference.ts","./src/rules/provision-declarations.ts","./src/rules/provision-exists.ts","./src/rules/scan.ts","./src/rules/specs.ts","./src/rules/structure.ts","./src/rules/types.ts","./src/rules/valid-describe-refs.ts","./src/rules/valid-detour-refs.ts","./src/trails/context-no-trailhead-types.trail.ts","./src/trails/cross-declarations.trail.ts","./src/trails/implementation-returns-result.trail.ts","./src/trails/index.ts","./src/trails/no-direct-impl-in-route.trail.ts","./src/trails/no-direct-implementation-call.trail.ts","./src/trails/no-sync-result-assumption.trail.ts","./src/trails/no-throw-in-detour-target.trail.ts","./src/trails/no-throw-in-implementation.trail.ts","./src/trails/prefer-schema-inference.trail.ts","./src/trails/provision-declarations.trail.ts","./src/trails/provision-exists.trail.ts","./src/trails/run.ts","./src/trails/schema.ts","./src/trails/topo.ts","./src/trails/valid-describe-refs.trail.ts","./src/trails/valid-detour-refs.trail.ts","./src/trails/wrap-rule.ts"],"version":"5.9.3"}
|