@ontrails/warden 1.0.0-beta.11 → 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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +59 -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 +4 -4
- 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,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validates that `ctx.
|
|
2
|
+
* Validates that `ctx.cross()` calls match the declared `crosses` array.
|
|
3
3
|
*
|
|
4
|
-
* Statically analyzes trail
|
|
5
|
-
* calls and compares them against the `
|
|
6
|
-
* config. Reports errors for undeclared
|
|
4
|
+
* Statically analyzes trail `blaze` functions to find `ctx.cross('trailId', ...)`
|
|
5
|
+
* calls and compares them against the `crosses: [...]` declaration in the trail
|
|
6
|
+
* config. Reports errors for undeclared crossings and warnings for unused ones.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
findConfigProperty,
|
|
11
|
-
|
|
11
|
+
findBlazeBodies,
|
|
12
12
|
findTrailDefinitions,
|
|
13
13
|
offsetToLine,
|
|
14
14
|
parse,
|
|
@@ -87,18 +87,34 @@ const resolveIdentifierElement = (
|
|
|
87
87
|
return resolveConstString(name, sourceCode);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
/** Resolve an array element to a static trail ID when possible. */
|
|
91
|
+
const resolveCrossElementId = (
|
|
92
|
+
element: AstNode,
|
|
93
|
+
sourceCode: string
|
|
94
|
+
): string | null => {
|
|
95
|
+
if (isStringLiteral(element)) {
|
|
96
|
+
return getStringValue(element);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (element.type === 'Identifier') {
|
|
100
|
+
return resolveIdentifierElement(element, sourceCode);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
};
|
|
105
|
+
|
|
90
106
|
// ---------------------------------------------------------------------------
|
|
91
|
-
// Declared
|
|
107
|
+
// Declared crossing extraction
|
|
92
108
|
// ---------------------------------------------------------------------------
|
|
93
109
|
|
|
94
|
-
/** Extract the ArrayExpression elements from a config's `
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
110
|
+
/** Extract the ArrayExpression elements from a config's `crosses` property. */
|
|
111
|
+
const getCrossElements = (config: AstNode): readonly AstNode[] | null => {
|
|
112
|
+
const crossesProp = findConfigProperty(config, 'crosses');
|
|
113
|
+
if (!crossesProp) {
|
|
98
114
|
return null;
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
const arrayNode =
|
|
117
|
+
const arrayNode = crossesProp.value;
|
|
102
118
|
if (!arrayNode || (arrayNode as AstNode).type !== 'ArrayExpression') {
|
|
103
119
|
return null;
|
|
104
120
|
}
|
|
@@ -115,33 +131,26 @@ const collectStringIds = (
|
|
|
115
131
|
sourceCode: string
|
|
116
132
|
): Set<string> => {
|
|
117
133
|
const ids = new Set<string>();
|
|
118
|
-
for (const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
ids.add(val);
|
|
123
|
-
}
|
|
124
|
-
} else if (el.type === 'Identifier') {
|
|
125
|
-
const resolved = resolveIdentifierElement(el, sourceCode);
|
|
126
|
-
if (resolved) {
|
|
127
|
-
ids.add(resolved);
|
|
128
|
-
}
|
|
134
|
+
for (const element of elements) {
|
|
135
|
+
const resolved = resolveCrossElementId(element, sourceCode);
|
|
136
|
+
if (resolved) {
|
|
137
|
+
ids.add(resolved);
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
return ids;
|
|
132
141
|
};
|
|
133
142
|
|
|
134
|
-
/** Extract string literal elements from a `
|
|
135
|
-
const
|
|
143
|
+
/** Extract string literal elements from a `crosses: [...]` array property. */
|
|
144
|
+
const extractDeclaredCrosses = (
|
|
136
145
|
config: AstNode,
|
|
137
146
|
sourceCode: string
|
|
138
147
|
): ReadonlySet<string> => {
|
|
139
|
-
const elements =
|
|
148
|
+
const elements = getCrossElements(config);
|
|
140
149
|
return elements ? collectStringIds(elements, sourceCode) : new Set();
|
|
141
150
|
};
|
|
142
151
|
|
|
143
152
|
// ---------------------------------------------------------------------------
|
|
144
|
-
// Called
|
|
153
|
+
// Called crossing extraction — member expression helpers
|
|
145
154
|
// ---------------------------------------------------------------------------
|
|
146
155
|
|
|
147
156
|
const MEMBER_TYPES = new Set(['StaticMemberExpression', 'MemberExpression']);
|
|
@@ -173,7 +182,6 @@ const extractFirstStringArg = (node: AstNode): string | null => {
|
|
|
173
182
|
|
|
174
183
|
const [firstArg] = args;
|
|
175
184
|
if (!firstArg || !isStringLiteral(firstArg)) {
|
|
176
|
-
// Dynamic ID — cannot resolve statically
|
|
177
185
|
return null;
|
|
178
186
|
}
|
|
179
187
|
|
|
@@ -181,34 +189,34 @@ const extractFirstStringArg = (node: AstNode): string | null => {
|
|
|
181
189
|
};
|
|
182
190
|
|
|
183
191
|
/**
|
|
184
|
-
* Extract the second parameter name from a
|
|
192
|
+
* Extract the second parameter name from a blaze function node.
|
|
185
193
|
*
|
|
186
|
-
* Handles `(input, ctx) =>
|
|
194
|
+
* Handles `(input, ctx) => ...`, `async (input, context) => ...`, and
|
|
187
195
|
* `function(input, ctx) { ... }` forms.
|
|
188
196
|
*/
|
|
189
|
-
const extractContextParamName = (
|
|
190
|
-
const params =
|
|
197
|
+
const extractContextParamName = (blazeBody: AstNode): string | null => {
|
|
198
|
+
const params = blazeBody['params'] as readonly AstNode[] | undefined;
|
|
191
199
|
if (!params || params.length < 2) {
|
|
192
200
|
return null;
|
|
193
201
|
}
|
|
194
202
|
return identifierName(params[1]);
|
|
195
203
|
};
|
|
196
204
|
|
|
197
|
-
/** Check if a callee is a member-style
|
|
198
|
-
const
|
|
205
|
+
/** Check if a callee is a member-style cross call: <ctxName>.cross(...). */
|
|
206
|
+
const isMemberCrossCall = (
|
|
199
207
|
callee: AstNode,
|
|
200
208
|
ctxNames: ReadonlySet<string>
|
|
201
209
|
): boolean => {
|
|
202
210
|
const pair = extractMemberPair(callee);
|
|
203
|
-
return !!pair && ctxNames.has(pair.objName) && pair.propName === '
|
|
211
|
+
return !!pair && ctxNames.has(pair.objName) && pair.propName === 'cross';
|
|
204
212
|
};
|
|
205
213
|
|
|
206
214
|
/**
|
|
207
|
-
* Check if a node is a `<ctxName>.
|
|
215
|
+
* Check if a node is a `<ctxName>.cross(...)` call and return the string trail ID.
|
|
208
216
|
*
|
|
209
|
-
* Also matches bare `
|
|
217
|
+
* Also matches bare `cross(...)` calls from destructuring.
|
|
210
218
|
*/
|
|
211
|
-
const
|
|
219
|
+
const extractCrossCallId = (
|
|
212
220
|
node: AstNode,
|
|
213
221
|
ctxNames: ReadonlySet<string>
|
|
214
222
|
): string | null => {
|
|
@@ -221,12 +229,11 @@ const extractFollowCallId = (
|
|
|
221
229
|
return null;
|
|
222
230
|
}
|
|
223
231
|
|
|
224
|
-
if (
|
|
232
|
+
if (isMemberCrossCall(callee, ctxNames)) {
|
|
225
233
|
return extractFirstStringArg(node);
|
|
226
234
|
}
|
|
227
235
|
|
|
228
|
-
|
|
229
|
-
if (identifierName(callee) === 'follow') {
|
|
236
|
+
if (identifierName(callee) === 'cross') {
|
|
230
237
|
return extractFirstStringArg(node);
|
|
231
238
|
}
|
|
232
239
|
|
|
@@ -243,15 +250,15 @@ const buildCtxNames = (body: AstNode): ReadonlySet<string> => {
|
|
|
243
250
|
return ctxNames;
|
|
244
251
|
};
|
|
245
252
|
|
|
246
|
-
/** Walk
|
|
247
|
-
const
|
|
253
|
+
/** Walk blaze bodies and collect all statically resolvable ctx.cross() trail IDs. */
|
|
254
|
+
const extractCalledCrosses = (config: AstNode): ReadonlySet<string> => {
|
|
248
255
|
const ids = new Set<string>();
|
|
249
256
|
|
|
250
|
-
for (const body of
|
|
257
|
+
for (const body of findBlazeBodies(config)) {
|
|
251
258
|
const ctxNames = buildCtxNames(body);
|
|
252
259
|
|
|
253
260
|
walk(body, (node) => {
|
|
254
|
-
const id =
|
|
261
|
+
const id = extractCrossCallId(node, ctxNames);
|
|
255
262
|
if (id) {
|
|
256
263
|
ids.add(id);
|
|
257
264
|
}
|
|
@@ -267,27 +274,27 @@ const extractCalledFollows = (config: AstNode): ReadonlySet<string> => {
|
|
|
267
274
|
|
|
268
275
|
const buildUndeclaredDiagnostic = (
|
|
269
276
|
trailId: string,
|
|
270
|
-
|
|
277
|
+
crossedId: string,
|
|
271
278
|
filePath: string,
|
|
272
279
|
line: number
|
|
273
280
|
): WardenDiagnostic => ({
|
|
274
281
|
filePath,
|
|
275
282
|
line,
|
|
276
|
-
message: `Trail "${trailId}": ctx.
|
|
277
|
-
rule: '
|
|
283
|
+
message: `Trail "${trailId}": ctx.cross('${crossedId}') called but '${crossedId}' is not declared in crosses`,
|
|
284
|
+
rule: 'cross-declarations',
|
|
278
285
|
severity: 'error',
|
|
279
286
|
});
|
|
280
287
|
|
|
281
288
|
const buildUnusedDiagnostic = (
|
|
282
289
|
trailId: string,
|
|
283
|
-
|
|
290
|
+
crossedId: string,
|
|
284
291
|
filePath: string,
|
|
285
292
|
line: number
|
|
286
293
|
): WardenDiagnostic => ({
|
|
287
294
|
filePath,
|
|
288
295
|
line,
|
|
289
|
-
message: `Trail "${trailId}": '${
|
|
290
|
-
rule: '
|
|
296
|
+
message: `Trail "${trailId}": '${crossedId}' declared in crosses but ctx.cross('${crossedId}') never called`,
|
|
297
|
+
rule: 'cross-declarations',
|
|
291
298
|
severity: 'warn',
|
|
292
299
|
});
|
|
293
300
|
|
|
@@ -333,8 +340,8 @@ const checkTrailDefinition = (
|
|
|
333
340
|
sourceCode: string,
|
|
334
341
|
diagnostics: WardenDiagnostic[]
|
|
335
342
|
): void => {
|
|
336
|
-
const declared =
|
|
337
|
-
const called =
|
|
343
|
+
const declared = extractDeclaredCrosses(def.config, sourceCode);
|
|
344
|
+
const called = extractCalledCrosses(def.config);
|
|
338
345
|
|
|
339
346
|
if (declared.size === 0 && called.size === 0) {
|
|
340
347
|
return;
|
|
@@ -352,9 +359,9 @@ const checkTrailDefinition = (
|
|
|
352
359
|
// ---------------------------------------------------------------------------
|
|
353
360
|
|
|
354
361
|
/**
|
|
355
|
-
* Validates that `ctx.
|
|
362
|
+
* Validates that `ctx.cross()` calls align with declared `crosses` arrays.
|
|
356
363
|
*/
|
|
357
|
-
export const
|
|
364
|
+
export const crossDeclarations: WardenRule = {
|
|
358
365
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
359
366
|
if (isTestFile(filePath)) {
|
|
360
367
|
return [];
|
|
@@ -374,7 +381,7 @@ export const followDeclarations: WardenRule = {
|
|
|
374
381
|
return diagnostics;
|
|
375
382
|
},
|
|
376
383
|
description:
|
|
377
|
-
'Ensure ctx.
|
|
378
|
-
name: '
|
|
384
|
+
'Ensure ctx.cross() calls match the declared crosses array in trail definitions.',
|
|
385
|
+
name: 'cross-declarations',
|
|
379
386
|
severity: 'error',
|
|
380
387
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finds implementations that return raw values instead of `Result`.
|
|
3
3
|
*
|
|
4
|
-
* Uses AST parsing to find `
|
|
5
|
-
* every return statement returns Result.ok(), Result.err(), ctx.
|
|
4
|
+
* Uses AST parsing to find `blaze:` bodies and check that
|
|
5
|
+
* every return statement returns Result.ok(), Result.err(), ctx.cross(),
|
|
6
6
|
* or a tracked Result-typed variable.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
findBlazeBodies,
|
|
11
11
|
findTrailDefinitions,
|
|
12
12
|
offsetToLine,
|
|
13
13
|
parse,
|
|
@@ -60,10 +60,10 @@ const isResultMemberCall = (callee: AstNode): boolean => {
|
|
|
60
60
|
if (objName === 'Result' && (propName === 'ok' || propName === 'err')) {
|
|
61
61
|
return true;
|
|
62
62
|
}
|
|
63
|
-
if (objName === 'ctx' && propName === '
|
|
63
|
+
if (objName === 'ctx' && propName === 'cross') {
|
|
64
64
|
return true;
|
|
65
65
|
}
|
|
66
|
-
return propName === '
|
|
66
|
+
return propName === 'blaze';
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
@@ -362,7 +362,7 @@ const checkAllDefinitions = (
|
|
|
362
362
|
|
|
363
363
|
for (const def of findTrailDefinitions(ast)) {
|
|
364
364
|
const info = { id: def.id, label: 'Trail' };
|
|
365
|
-
for (const implValue of
|
|
365
|
+
for (const implValue of findBlazeBodies(def.config as AstNode)) {
|
|
366
366
|
checkImplementation(
|
|
367
367
|
implValue,
|
|
368
368
|
info,
|
package/src/rules/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
|
|
2
|
+
import { crossDeclarations } from './cross-declarations.js';
|
|
3
3
|
import { implementationReturnsResult } from './implementation-returns-result.js';
|
|
4
4
|
import { noDirectImplInRoute } from './no-direct-impl-in-route.js';
|
|
5
5
|
import { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
@@ -7,8 +7,8 @@ import { noSyncResultAssumption } from './no-sync-result-assumption.js';
|
|
|
7
7
|
import { noThrowInDetourTarget } from './no-throw-in-detour-target.js';
|
|
8
8
|
import { noThrowInImplementation } from './no-throw-in-implementation.js';
|
|
9
9
|
import { preferSchemaInference } from './prefer-schema-inference.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { provisionDeclarations } from './provision-declarations.js';
|
|
11
|
+
import { provisionExists } from './provision-exists.js';
|
|
12
12
|
import type { WardenRule } from './types.js';
|
|
13
13
|
import { validDescribeRefs } from './valid-describe-refs.js';
|
|
14
14
|
import { validDetourRefs } from './valid-detour-refs.js';
|
|
@@ -22,8 +22,8 @@ export type {
|
|
|
22
22
|
} from './types.js';
|
|
23
23
|
|
|
24
24
|
export { noThrowInImplementation } from './no-throw-in-implementation.js';
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
25
|
+
export { contextNoTrailheadTypes } from './context-no-trailhead-types.js';
|
|
26
|
+
export { crossDeclarations } from './cross-declarations.js';
|
|
27
27
|
export { validDetourRefs } from './valid-detour-refs.js';
|
|
28
28
|
export { noDirectImplInRoute } from './no-direct-impl-in-route.js';
|
|
29
29
|
export { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
@@ -31,8 +31,8 @@ export { noSyncResultAssumption } from './no-sync-result-assumption.js';
|
|
|
31
31
|
export { implementationReturnsResult } from './implementation-returns-result.js';
|
|
32
32
|
export { noThrowInDetourTarget } from './no-throw-in-detour-target.js';
|
|
33
33
|
export { preferSchemaInference } from './prefer-schema-inference.js';
|
|
34
|
-
export {
|
|
35
|
-
export {
|
|
34
|
+
export { provisionDeclarations } from './provision-declarations.js';
|
|
35
|
+
export { provisionExists } from './provision-exists.js';
|
|
36
36
|
export { validDescribeRefs } from './valid-describe-refs.js';
|
|
37
37
|
|
|
38
38
|
/** All built-in warden rules, keyed by rule name. */
|
|
@@ -41,10 +41,10 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
41
41
|
WardenRule
|
|
42
42
|
>([
|
|
43
43
|
[noThrowInImplementation.name, noThrowInImplementation],
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
44
|
+
[contextNoTrailheadTypes.name, contextNoTrailheadTypes],
|
|
45
|
+
[crossDeclarations.name, crossDeclarations],
|
|
46
|
+
[provisionDeclarations.name, provisionDeclarations],
|
|
47
|
+
[provisionExists.name, provisionExists],
|
|
48
48
|
[preferSchemaInference.name, preferSchemaInference],
|
|
49
49
|
[validDescribeRefs.name, validDescribeRefs],
|
|
50
50
|
[validDetourRefs.name, validDetourRefs],
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Detects trail implementations with `
|
|
2
|
+
* Detects trail implementations with `crosses` that call `.blaze()` directly.
|
|
3
3
|
*
|
|
4
|
-
* Uses AST parsing to find trail definitions that declare `
|
|
5
|
-
* `.
|
|
4
|
+
* Uses AST parsing to find trail definitions that declare `crosses` and check for
|
|
5
|
+
* `.blaze()` call expressions in their bodies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
findConfigProperty,
|
|
10
|
-
|
|
10
|
+
findBlazeBodies,
|
|
11
11
|
findTrailDefinitions,
|
|
12
|
-
|
|
12
|
+
isBlazeCall,
|
|
13
13
|
offsetToLine,
|
|
14
14
|
parse,
|
|
15
15
|
walk,
|
|
@@ -23,20 +23,20 @@ interface AstNode {
|
|
|
23
23
|
readonly [key: string]: unknown;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const findImplCallsInTrailWithCrosses = (
|
|
27
27
|
def: { readonly config: AstNode },
|
|
28
28
|
filePath: string,
|
|
29
29
|
sourceCode: string,
|
|
30
30
|
diagnostics: WardenDiagnostic[]
|
|
31
31
|
): void => {
|
|
32
|
-
for (const body of
|
|
32
|
+
for (const body of findBlazeBodies(def.config as AstNode)) {
|
|
33
33
|
walk(body, (node) => {
|
|
34
|
-
if (
|
|
34
|
+
if (isBlazeCall(node as AstNode)) {
|
|
35
35
|
diagnostics.push({
|
|
36
36
|
filePath,
|
|
37
37
|
line: offsetToLine(sourceCode, node.start),
|
|
38
38
|
message:
|
|
39
|
-
'Use ctx.
|
|
39
|
+
'Use ctx.cross("trailId", input) instead of direct .blaze() calls. ctx.cross() validates input and propagates tracing.',
|
|
40
40
|
rule: 'no-direct-impl-in-route',
|
|
41
41
|
severity: 'warn',
|
|
42
42
|
});
|
|
@@ -45,11 +45,11 @@ const findImplCallsInTrailWithFollow = (
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
const
|
|
49
|
-
findConfigProperty(config as AstNode, '
|
|
48
|
+
const hasCrossesProperty = (config: AstNode): boolean =>
|
|
49
|
+
findConfigProperty(config as AstNode, 'crosses') !== null;
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Detects trails with `
|
|
52
|
+
* Detects trails with `crosses` that call another trail's `.blaze()` directly.
|
|
53
53
|
*/
|
|
54
54
|
export const noDirectImplInRoute: WardenRule = {
|
|
55
55
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
@@ -63,18 +63,18 @@ export const noDirectImplInRoute: WardenRule = {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const diagnostics: WardenDiagnostic[] = [];
|
|
66
|
-
const
|
|
67
|
-
|
|
66
|
+
const crossDefs = findTrailDefinitions(ast as AstNode).filter((d) =>
|
|
67
|
+
hasCrossesProperty(d.config as AstNode)
|
|
68
68
|
);
|
|
69
69
|
|
|
70
|
-
for (const def of
|
|
71
|
-
|
|
70
|
+
for (const def of crossDefs) {
|
|
71
|
+
findImplCallsInTrailWithCrosses(def, filePath, sourceCode, diagnostics);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
return diagnostics;
|
|
75
75
|
},
|
|
76
76
|
description:
|
|
77
|
-
'Prefer ctx.
|
|
77
|
+
'Prefer ctx.cross() over direct .blaze() calls in trail bodies with crossings.',
|
|
78
78
|
name: 'no-direct-impl-in-route',
|
|
79
79
|
|
|
80
80
|
severity: 'warn',
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Flags direct `.
|
|
2
|
+
* Flags direct `.blaze()` calls in application code.
|
|
3
3
|
*
|
|
4
|
-
* Uses AST parsing to find `.
|
|
4
|
+
* Uses AST parsing to find `.blaze()` call expressions,
|
|
5
5
|
* ignoring occurrences in strings and comments.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { isBlazeCall, offsetToLine, parse, walk } from './ast.js';
|
|
9
9
|
import { isFrameworkInternalFile, isTestFile } from './scan.js';
|
|
10
10
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Flags direct `.
|
|
13
|
+
* Flags direct `.blaze()` calls in application code.
|
|
14
14
|
*/
|
|
15
15
|
export const noDirectImplementationCall: WardenRule = {
|
|
16
16
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
@@ -26,12 +26,12 @@ export const noDirectImplementationCall: WardenRule = {
|
|
|
26
26
|
const diagnostics: WardenDiagnostic[] = [];
|
|
27
27
|
|
|
28
28
|
walk(ast, (node) => {
|
|
29
|
-
if (
|
|
29
|
+
if (isBlazeCall(node)) {
|
|
30
30
|
diagnostics.push({
|
|
31
31
|
filePath,
|
|
32
32
|
line: offsetToLine(sourceCode, node.start),
|
|
33
33
|
message:
|
|
34
|
-
'Use ctx.
|
|
34
|
+
'Use ctx.cross("trailId", input) instead of direct .blaze() calls. Direct implementation access bypasses validation, tracing, and gates.',
|
|
35
35
|
rule: 'no-direct-implementation-call',
|
|
36
36
|
severity: 'warn',
|
|
37
37
|
});
|
|
@@ -41,7 +41,7 @@ export const noDirectImplementationCall: WardenRule = {
|
|
|
41
41
|
return diagnostics;
|
|
42
42
|
},
|
|
43
43
|
description:
|
|
44
|
-
'Disallow direct .
|
|
44
|
+
'Disallow direct .blaze() calls in application code. Use ctx.cross() instead.',
|
|
45
45
|
name: 'no-direct-implementation-call',
|
|
46
46
|
severity: 'warn',
|
|
47
47
|
};
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
|
|
8
8
|
const RESULT_ACCESS_PATTERN =
|
|
9
9
|
/\.(?:isOk|isErr|match|map)\s*\(|\.(?:value|error)\b/;
|
|
10
|
-
const IMPLEMENTATION_CALL_PATTERN = /\.
|
|
10
|
+
const IMPLEMENTATION_CALL_PATTERN = /\.blaze\s*\(/;
|
|
11
11
|
|
|
12
12
|
const isAwaitedImplementationCall = (line: string): boolean => {
|
|
13
|
-
const callIndex = line.indexOf('.
|
|
13
|
+
const callIndex = line.indexOf('.blaze(');
|
|
14
14
|
if (callIndex === -1) {
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
@@ -39,7 +39,7 @@ interface PendingCall {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const MISSING_AWAIT_MESSAGE =
|
|
42
|
-
'Missing await: .
|
|
42
|
+
'Missing await: .blaze() returns Promise<Result> after normalization. Use `const result = await trail.blaze(input, ctx)`.';
|
|
43
43
|
|
|
44
44
|
const createMissingAwaitDiagnostic = (
|
|
45
45
|
filePath: string,
|
|
@@ -140,7 +140,7 @@ const scanSourceCode = (
|
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
* Flags code that assumes `.
|
|
143
|
+
* Flags code that assumes `.blaze()` returns a synchronous result.
|
|
144
144
|
*/
|
|
145
145
|
export const noSyncResultAssumption: WardenRule = {
|
|
146
146
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
@@ -150,7 +150,7 @@ export const noSyncResultAssumption: WardenRule = {
|
|
|
150
150
|
return scanSourceCode(stripQuotedContent(sourceCode), filePath);
|
|
151
151
|
},
|
|
152
152
|
description:
|
|
153
|
-
'Disallow treating .
|
|
153
|
+
'Disallow treating .blaze() as synchronous after normalization. Always await the returned Promise<Result>.',
|
|
154
154
|
name: 'no-sync-result-assumption',
|
|
155
155
|
severity: 'error',
|
|
156
156
|
};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
findBlazeBodies,
|
|
10
10
|
findTrailDefinitions,
|
|
11
11
|
offsetToLine,
|
|
12
12
|
parse,
|
|
@@ -66,7 +66,7 @@ const findThrowsInTargetedTrails = (
|
|
|
66
66
|
continue;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
for (const body of
|
|
69
|
+
for (const body of findBlazeBodies(def.config as AstNode)) {
|
|
70
70
|
walk(body, (node) => {
|
|
71
71
|
if (node.type === 'ThrowStatement') {
|
|
72
72
|
diagnostics.push({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Finds `throw` statements inside `
|
|
2
|
+
* Finds `throw` statements inside `blaze:` function bodies.
|
|
3
3
|
*
|
|
4
4
|
* Uses AST parsing for accurate detection — no false positives from
|
|
5
5
|
* throw in comments, strings, or nested non-implementation functions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { findBlazeBodies, offsetToLine, parse, walk } from './ast.js';
|
|
9
9
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
10
10
|
|
|
11
11
|
export const noThrowInImplementation: WardenRule = {
|
|
@@ -17,7 +17,7 @@ export const noThrowInImplementation: WardenRule = {
|
|
|
17
17
|
|
|
18
18
|
const diagnostics: WardenDiagnostic[] = [];
|
|
19
19
|
|
|
20
|
-
for (const body of
|
|
20
|
+
for (const body of findBlazeBodies(ast)) {
|
|
21
21
|
walk(body, (node) => {
|
|
22
22
|
if (node.type === 'ThrowStatement') {
|
|
23
23
|
diagnostics.push({
|