@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
|
@@ -9,7 +9,7 @@ trail("entity.show", {
|
|
|
9
9
|
input: z.object({
|
|
10
10
|
query: z.string().describe("Search query. @see entity.search"),
|
|
11
11
|
}),
|
|
12
|
-
|
|
12
|
+
blaze: (input) => Result.ok(input),
|
|
13
13
|
})`;
|
|
14
14
|
|
|
15
15
|
const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
|
|
@@ -23,14 +23,14 @@ trail("entity.show", {
|
|
|
23
23
|
const code = `
|
|
24
24
|
trail("entity.search", {
|
|
25
25
|
input: z.object({ query: z.string() }),
|
|
26
|
-
|
|
26
|
+
blaze: (input) => Result.ok(input),
|
|
27
27
|
})
|
|
28
28
|
|
|
29
29
|
trail("entity.show", {
|
|
30
30
|
input: z.object({
|
|
31
31
|
query: z.string().describe("Search query. @see entity.search"),
|
|
32
32
|
}),
|
|
33
|
-
|
|
33
|
+
blaze: (input) => Result.ok(input),
|
|
34
34
|
})`;
|
|
35
35
|
|
|
36
36
|
const diagnostics = validDescribeRefs.check(code, 'src/entity.ts');
|
|
@@ -44,7 +44,7 @@ trail("entity.show", {
|
|
|
44
44
|
input: z.object({
|
|
45
45
|
query: z.string().describe("Search query. @see entity.search"),
|
|
46
46
|
}),
|
|
47
|
-
|
|
47
|
+
blaze: (input) => Result.ok(input),
|
|
48
48
|
})`;
|
|
49
49
|
|
|
50
50
|
const diagnostics = validDescribeRefs.checkWithContext(
|
|
@@ -6,10 +6,10 @@ import { wrapRule } from '../trails/wrap-rule.js';
|
|
|
6
6
|
import type { ProjectAwareWardenRule } from '../rules/types.js';
|
|
7
7
|
|
|
8
8
|
describe('wrapRule', () => {
|
|
9
|
-
test('preserves undefined
|
|
9
|
+
test('preserves undefined knownProvisionIds and defaults knownTrailIds to empty set', async () => {
|
|
10
10
|
let capturedContext:
|
|
11
11
|
| {
|
|
12
|
-
readonly
|
|
12
|
+
readonly knownProvisionIds?: ReadonlySet<string>;
|
|
13
13
|
readonly knownTrailIds?: ReadonlySet<string>;
|
|
14
14
|
}
|
|
15
15
|
| undefined;
|
|
@@ -26,7 +26,7 @@ describe('wrapRule', () => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const wrapped = wrapRule({ examples: [], rule });
|
|
29
|
-
const result = await wrapped.
|
|
29
|
+
const result = await wrapped.blaze(
|
|
30
30
|
{ filePath: 'entity.ts', sourceCode: '' },
|
|
31
31
|
createTrailContext()
|
|
32
32
|
);
|
|
@@ -34,7 +34,7 @@ describe('wrapRule', () => {
|
|
|
34
34
|
expect(result.isOk()).toBe(true);
|
|
35
35
|
expect(result.unwrap()).toEqual({ diagnostics: [] });
|
|
36
36
|
expect(capturedContext).toEqual({
|
|
37
|
-
|
|
37
|
+
knownProvisionIds: undefined,
|
|
38
38
|
knownTrailIds: new Set<string>(),
|
|
39
39
|
});
|
|
40
40
|
});
|
package/src/cli.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { Topo } from '@ontrails/core';
|
|
|
12
12
|
import type { DriftResult } from './drift.js';
|
|
13
13
|
import { checkDrift } from './drift.js';
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
collectProvisionDefinitionIds,
|
|
16
16
|
findConfigProperty,
|
|
17
17
|
findTrailDefinitions,
|
|
18
18
|
parse,
|
|
@@ -36,7 +36,7 @@ export interface WardenOptions {
|
|
|
36
36
|
readonly lintOnly?: boolean | undefined;
|
|
37
37
|
/** Only run drift detection, skip lint rules */
|
|
38
38
|
readonly driftOnly?: boolean | undefined;
|
|
39
|
-
/** App topology for drift detection. When provided, enables real
|
|
39
|
+
/** App topology for drift detection. When provided, enables real trailhead lock comparison. */
|
|
40
40
|
readonly topo?: Topo | undefined;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -133,17 +133,17 @@ const collectDetourTargetTrailIds = (
|
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
135
|
|
|
136
|
-
const
|
|
136
|
+
const collectKnownProvisionIds = (
|
|
137
137
|
sourceCode: string,
|
|
138
138
|
filePath: string,
|
|
139
|
-
|
|
139
|
+
knownProvisionIds: Set<string>
|
|
140
140
|
): void => {
|
|
141
141
|
const ast = parse(filePath, sourceCode);
|
|
142
142
|
if (!ast) {
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
|
-
for (const id of
|
|
146
|
-
|
|
145
|
+
for (const id of collectProvisionDefinitionIds(ast)) {
|
|
146
|
+
knownProvisionIds.add(id);
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
149
|
|
|
@@ -190,17 +190,21 @@ const collectTopoDetourTargetTrailIds = (
|
|
|
190
190
|
|
|
191
191
|
const buildProjectContextFromTopo = (appTopo: Topo): ProjectContext => {
|
|
192
192
|
const knownTrailIds = new Set<string>(appTopo.trails.keys());
|
|
193
|
-
const
|
|
193
|
+
const knownProvisionIds = new Set<string>(appTopo.provisions.keys());
|
|
194
194
|
const detourTargetTrailIds = collectTopoDetourTargetTrailIds(appTopo);
|
|
195
195
|
|
|
196
|
-
return {
|
|
196
|
+
return {
|
|
197
|
+
detourTargetTrailIds,
|
|
198
|
+
knownProvisionIds,
|
|
199
|
+
knownTrailIds,
|
|
200
|
+
};
|
|
197
201
|
};
|
|
198
202
|
|
|
199
203
|
const buildProjectContextFromFiles = (
|
|
200
204
|
sourceFiles: readonly SourceFile[]
|
|
201
205
|
): ProjectContext => {
|
|
202
206
|
const knownTrailIds = new Set<string>();
|
|
203
|
-
const
|
|
207
|
+
const knownProvisionIds = new Set<string>();
|
|
204
208
|
const detourTargetTrailIds = new Set<string>();
|
|
205
209
|
|
|
206
210
|
for (const sourceFile of sourceFiles) {
|
|
@@ -209,10 +213,10 @@ const buildProjectContextFromFiles = (
|
|
|
209
213
|
sourceFile.filePath,
|
|
210
214
|
knownTrailIds
|
|
211
215
|
);
|
|
212
|
-
|
|
216
|
+
collectKnownProvisionIds(
|
|
213
217
|
sourceFile.sourceCode,
|
|
214
218
|
sourceFile.filePath,
|
|
215
|
-
|
|
219
|
+
knownProvisionIds
|
|
216
220
|
);
|
|
217
221
|
collectDetourTargetTrailIds(
|
|
218
222
|
sourceFile.sourceCode,
|
|
@@ -223,7 +227,7 @@ const buildProjectContextFromFiles = (
|
|
|
223
227
|
|
|
224
228
|
return {
|
|
225
229
|
detourTargetTrailIds,
|
|
226
|
-
|
|
230
|
+
knownProvisionIds,
|
|
227
231
|
knownTrailIds,
|
|
228
232
|
};
|
|
229
233
|
};
|
|
@@ -324,7 +328,7 @@ const formatDriftSection = (drift: DriftResult | null): string[] => {
|
|
|
324
328
|
return [];
|
|
325
329
|
}
|
|
326
330
|
const label = drift.stale
|
|
327
|
-
? 'Drift:
|
|
331
|
+
? 'Drift: trailhead.lock is stale (regenerate with `trails survey generate`)'
|
|
328
332
|
: 'Drift: clean';
|
|
329
333
|
return [label, ''];
|
|
330
334
|
};
|
package/src/drift.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Trailhead lock drift detection.
|
|
3
3
|
*
|
|
4
|
-
* Compares the committed `
|
|
5
|
-
*
|
|
4
|
+
* Compares the committed `trailhead.lock` hash against a freshly generated
|
|
5
|
+
* trailhead map hash to detect when the trail topology has changed without
|
|
6
6
|
* updating the lock file.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Topo } from '@ontrails/core';
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
generateTrailheadMap,
|
|
12
|
+
hashTrailheadMap,
|
|
13
|
+
readTrailheadLock,
|
|
14
14
|
} from '@ontrails/schema';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Result of a drift check comparing committed
|
|
17
|
+
* Result of a drift check comparing committed trailhead.lock against the current state.
|
|
18
18
|
*/
|
|
19
19
|
export interface DriftResult {
|
|
20
20
|
/** Whether the committed lock is out of date */
|
|
21
21
|
readonly stale: boolean;
|
|
22
|
-
/** Hash from the committed
|
|
22
|
+
/** Hash from the committed trailhead.lock file, or null if not found */
|
|
23
23
|
readonly committedHash: string | null;
|
|
24
24
|
/** Hash computed from the current trail topology */
|
|
25
25
|
readonly currentHash: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Check whether the committed
|
|
29
|
+
* Check whether the committed trailhead.lock is stale compared to the current topology.
|
|
30
30
|
*
|
|
31
31
|
* When no topo is provided, returns a clean result (no drift detectable without runtime info).
|
|
32
32
|
*/
|
|
@@ -38,9 +38,9 @@ export const checkDrift = async (
|
|
|
38
38
|
return { committedHash: null, currentHash: 'unknown', stale: false };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const
|
|
42
|
-
const currentHash =
|
|
43
|
-
const committedHash = await
|
|
41
|
+
const trailheadMap = generateTrailheadMap(topo);
|
|
42
|
+
const currentHash = hashTrailheadMap(trailheadMap);
|
|
43
|
+
const committedHash = await readTrailheadLock({ dir: rootDir });
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
46
|
committedHash,
|
package/src/formatters.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const formatGitHubAnnotations = (report: WardenReport): string => {
|
|
|
33
33
|
|
|
34
34
|
if (report.drift?.stale) {
|
|
35
35
|
lines.push(
|
|
36
|
-
'::error::drift:
|
|
36
|
+
'::error::drift: trailhead.lock is stale (regenerate with `trails survey generate`)'
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -88,7 +88,7 @@ const driftSection = (drift: WardenReport['drift']): readonly string[] => {
|
|
|
88
88
|
return [
|
|
89
89
|
'',
|
|
90
90
|
'### Drift',
|
|
91
|
-
'-
|
|
91
|
+
'- trailhead.lock is stale (regenerate with `trails survey generate`)',
|
|
92
92
|
];
|
|
93
93
|
};
|
|
94
94
|
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type {
|
|
|
18
18
|
|
|
19
19
|
// Individual rules
|
|
20
20
|
export { noThrowInImplementation } from './rules/no-throw-in-implementation.js';
|
|
21
|
-
export {
|
|
21
|
+
export { contextNoTrailheadTypes } from './rules/context-no-trailhead-types.js';
|
|
22
22
|
export { validDetourRefs } from './rules/valid-detour-refs.js';
|
|
23
23
|
export { noDirectImplInRoute } from './rules/no-direct-impl-in-route.js';
|
|
24
24
|
export { noDirectImplementationCall } from './rules/no-direct-implementation-call.js';
|
|
@@ -26,8 +26,8 @@ export { noSyncResultAssumption } from './rules/no-sync-result-assumption.js';
|
|
|
26
26
|
export { implementationReturnsResult } from './rules/implementation-returns-result.js';
|
|
27
27
|
export { noThrowInDetourTarget } from './rules/no-throw-in-detour-target.js';
|
|
28
28
|
export { preferSchemaInference } from './rules/prefer-schema-inference.js';
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
29
|
+
export { provisionDeclarations } from './rules/provision-declarations.js';
|
|
30
|
+
export { provisionExists } from './rules/provision-exists.js';
|
|
31
31
|
export { validDescribeRefs } from './rules/valid-describe-refs.js';
|
|
32
32
|
|
|
33
33
|
// Rule registry
|
|
@@ -48,13 +48,13 @@ export {
|
|
|
48
48
|
export type { DriftResult } from './drift.js';
|
|
49
49
|
export { checkDrift } from './drift.js';
|
|
50
50
|
|
|
51
|
-
// Trail
|
|
51
|
+
// Trail gate
|
|
52
52
|
export { wardenTopo } from './trails/topo.js';
|
|
53
53
|
export { runWardenTrails } from './trails/run.js';
|
|
54
54
|
export {
|
|
55
|
-
|
|
55
|
+
contextNoTrailheadTypesTrail,
|
|
56
|
+
crossDeclarationsTrail,
|
|
56
57
|
diagnosticSchema,
|
|
57
|
-
followDeclarationsTrail,
|
|
58
58
|
implementationReturnsResultTrail,
|
|
59
59
|
noDirectImplInRouteTrail,
|
|
60
60
|
noDirectImplementationCallTrail,
|
|
@@ -64,8 +64,8 @@ export {
|
|
|
64
64
|
preferSchemaInferenceTrail,
|
|
65
65
|
ruleInput,
|
|
66
66
|
ruleOutput,
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
provisionDeclarationsTrail,
|
|
68
|
+
provisionExistsTrail,
|
|
69
69
|
validDescribeRefsTrail,
|
|
70
70
|
validDetourRefsTrail,
|
|
71
71
|
} from './trails/index.js';
|
package/src/rules/ast.ts
CHANGED
|
@@ -92,7 +92,7 @@ const walkScopeInner: WalkFn = (node, visit) => {
|
|
|
92
92
|
/**
|
|
93
93
|
* Walk an AST node tree without descending into nested function scopes.
|
|
94
94
|
* The root node is always traversed; only inner function boundaries are skipped.
|
|
95
|
-
* Useful for
|
|
95
|
+
* Useful for provision-access analysis where inner functions may shadow
|
|
96
96
|
* the trail context parameter name.
|
|
97
97
|
*/
|
|
98
98
|
export const walkScope: WalkFn = (node, visit) => {
|
|
@@ -166,11 +166,11 @@ export const extractFirstStringArg = (node: AstNode): string | null => {
|
|
|
166
166
|
return extractStringLiteral(firstArg);
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
-
const
|
|
169
|
+
const isProvisionCall = (node: AstNode | undefined): boolean =>
|
|
170
170
|
!!node &&
|
|
171
171
|
node.type === 'CallExpression' &&
|
|
172
172
|
identifierName((node as unknown as { callee?: AstNode }).callee) ===
|
|
173
|
-
'
|
|
173
|
+
'provision';
|
|
174
174
|
|
|
175
175
|
const extractBindingName = (node: AstNode | undefined): string | null => {
|
|
176
176
|
if (!node) {
|
|
@@ -185,8 +185,8 @@ const extractBindingName = (node: AstNode | undefined): string | null => {
|
|
|
185
185
|
return null;
|
|
186
186
|
};
|
|
187
187
|
|
|
188
|
-
/** Collect `const foo =
|
|
189
|
-
export const
|
|
188
|
+
/** Collect `const foo = provision('id', ...)` bindings from a parsed file. */
|
|
189
|
+
export const collectNamedProvisionIds = (
|
|
190
190
|
ast: AstNode
|
|
191
191
|
): ReadonlyMap<string, string> => {
|
|
192
192
|
const ids = new Map<string, string>();
|
|
@@ -200,28 +200,28 @@ export const collectNamedServiceIds = (
|
|
|
200
200
|
readonly id?: AstNode;
|
|
201
201
|
readonly init?: AstNode;
|
|
202
202
|
};
|
|
203
|
-
if (!
|
|
203
|
+
if (!isProvisionCall(init)) {
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
const name = extractBindingName(id);
|
|
208
|
-
const
|
|
209
|
-
if (name &&
|
|
210
|
-
ids.set(name,
|
|
208
|
+
const provisionId = init ? extractFirstStringArg(init) : null;
|
|
209
|
+
if (name && provisionId) {
|
|
210
|
+
ids.set(name, provisionId);
|
|
211
211
|
}
|
|
212
212
|
});
|
|
213
213
|
|
|
214
214
|
return ids;
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
-
/** Collect all inline `
|
|
218
|
-
export const
|
|
217
|
+
/** Collect all inline `provision('id', ...)` definition IDs from a parsed file. */
|
|
218
|
+
export const collectProvisionDefinitionIds = (
|
|
219
219
|
ast: AstNode
|
|
220
220
|
): ReadonlySet<string> => {
|
|
221
221
|
const ids = new Set<string>();
|
|
222
222
|
|
|
223
223
|
walk(ast, (node) => {
|
|
224
|
-
if (!
|
|
224
|
+
if (!isProvisionCall(node)) {
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -234,6 +234,11 @@ export const collectServiceDefinitionIds = (
|
|
|
234
234
|
return ids;
|
|
235
235
|
};
|
|
236
236
|
|
|
237
|
+
/** Backward-compatible aliases while the migration is in flight. */
|
|
238
|
+
export const collectNamedServiceIds = collectNamedProvisionIds;
|
|
239
|
+
/** Backward-compatible aliases while the migration is in flight. */
|
|
240
|
+
export const collectServiceDefinitionIds = collectProvisionDefinitionIds;
|
|
241
|
+
|
|
237
242
|
// ---------------------------------------------------------------------------
|
|
238
243
|
// Config property extraction helpers
|
|
239
244
|
// ---------------------------------------------------------------------------
|
|
@@ -265,7 +270,7 @@ export const findConfigProperty = (
|
|
|
265
270
|
export interface TrailDefinition {
|
|
266
271
|
/** Trail ID string, e.g. "entity.show" */
|
|
267
272
|
readonly id: string;
|
|
268
|
-
/** "trail" or "
|
|
273
|
+
/** "trail" or "signal" */
|
|
269
274
|
readonly kind: string;
|
|
270
275
|
/** The config object argument (second arg to trail() call) */
|
|
271
276
|
readonly config: AstNode;
|
|
@@ -275,11 +280,11 @@ export interface TrailDefinition {
|
|
|
275
280
|
|
|
276
281
|
/**
|
|
277
282
|
* Find all `trail("id", { ... })`, `trail({ id: "x", ... })`, and
|
|
278
|
-
* `
|
|
283
|
+
* `signal("id", { ... })` call sites.
|
|
279
284
|
*
|
|
280
285
|
* Returns the trail ID, kind, and config object node for each definition.
|
|
281
286
|
*/
|
|
282
|
-
const TRAIL_CALLEE_NAMES = new Set(['trail', '
|
|
287
|
+
const TRAIL_CALLEE_NAMES = new Set(['trail', 'signal']);
|
|
283
288
|
|
|
284
289
|
const getTrailCalleeName = (node: AstNode): string | null => {
|
|
285
290
|
if (node.type !== 'CallExpression') {
|
|
@@ -376,22 +381,22 @@ export const findTrailDefinitions = (ast: AstNode): TrailDefinition[] => {
|
|
|
376
381
|
};
|
|
377
382
|
|
|
378
383
|
// ---------------------------------------------------------------------------
|
|
379
|
-
//
|
|
384
|
+
// Blaze body extraction
|
|
380
385
|
// ---------------------------------------------------------------------------
|
|
381
386
|
|
|
382
387
|
/**
|
|
383
|
-
* Extract top-level `
|
|
388
|
+
* Extract top-level `blaze:` property values from an ObjectExpression's direct properties.
|
|
384
389
|
*
|
|
385
|
-
* Does not recurse into nested objects, so `
|
|
390
|
+
* Does not recurse into nested objects, so `meta: { blaze: ... }` is ignored.
|
|
386
391
|
*/
|
|
387
|
-
const
|
|
392
|
+
const extractBlazeFromConfig = (config: AstNode): AstNode[] => {
|
|
388
393
|
const bodies: AstNode[] = [];
|
|
389
394
|
const properties = config['properties'] as readonly AstNode[] | undefined;
|
|
390
395
|
if (!properties) {
|
|
391
396
|
return bodies;
|
|
392
397
|
}
|
|
393
398
|
for (const prop of properties) {
|
|
394
|
-
if (prop.type === 'Property' && prop.key?.name === '
|
|
399
|
+
if (prop.type === 'Property' && prop.key?.name === 'blaze' && prop.value) {
|
|
395
400
|
bodies.push(prop.value);
|
|
396
401
|
}
|
|
397
402
|
}
|
|
@@ -399,22 +404,22 @@ const extractRunFromConfig = (config: AstNode): AstNode[] => {
|
|
|
399
404
|
};
|
|
400
405
|
|
|
401
406
|
/**
|
|
402
|
-
* Find `
|
|
407
|
+
* Find `blaze:` property values.
|
|
403
408
|
*
|
|
404
|
-
* When given an ObjectExpression (trail config), returns only its direct `
|
|
409
|
+
* When given an ObjectExpression (trail config), returns only its direct `blaze:`
|
|
405
410
|
* properties. When given a full AST, finds trail definitions first and extracts
|
|
406
|
-
* `
|
|
407
|
-
* (e.g. `
|
|
411
|
+
* `blaze:` from each config — in both cases ignoring nested `blaze:` properties
|
|
412
|
+
* (e.g. `meta: { blaze: ... }`).
|
|
408
413
|
*/
|
|
409
|
-
export const
|
|
414
|
+
export const findBlazeBodies = (node: AstNode): AstNode[] => {
|
|
410
415
|
if (node.type === 'ObjectExpression') {
|
|
411
|
-
return
|
|
416
|
+
return extractBlazeFromConfig(node);
|
|
412
417
|
}
|
|
413
418
|
|
|
414
|
-
// Full AST — find trail definitions and extract
|
|
419
|
+
// Full AST — find trail definitions and extract blaze from their configs
|
|
415
420
|
const bodies: AstNode[] = [];
|
|
416
421
|
for (const def of findTrailDefinitions(node)) {
|
|
417
|
-
bodies.push(...
|
|
422
|
+
bodies.push(...extractBlazeFromConfig(def.config));
|
|
418
423
|
}
|
|
419
424
|
return bodies;
|
|
420
425
|
};
|
|
@@ -423,8 +428,8 @@ export const findRunBodies = (node: AstNode): AstNode[] => {
|
|
|
423
428
|
// Misc helpers
|
|
424
429
|
// ---------------------------------------------------------------------------
|
|
425
430
|
|
|
426
|
-
/** Check if a node is a call to `.
|
|
427
|
-
export const
|
|
431
|
+
/** Check if a node is a call to `.blaze()` on some object. */
|
|
432
|
+
export const isBlazeCall = (node: AstNode): boolean => {
|
|
428
433
|
if (node.type !== 'CallExpression') {
|
|
429
434
|
return false;
|
|
430
435
|
}
|
|
@@ -441,6 +446,6 @@ export const isRunCall = (node: AstNode): boolean => {
|
|
|
441
446
|
const prop = (callee as unknown as { property?: AstNode }).property;
|
|
442
447
|
return (
|
|
443
448
|
prop?.type === 'Identifier' &&
|
|
444
|
-
(prop as unknown as { name: string }).name === '
|
|
449
|
+
(prop as unknown as { name: string }).name === 'blaze'
|
|
445
450
|
);
|
|
446
451
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Detects imports of
|
|
2
|
+
* Detects imports of trailhead-specific modules and types in trail files.
|
|
3
3
|
*
|
|
4
4
|
* Uses AST parsing for accurate detection — no false positives from
|
|
5
5
|
* imports in comments or strings.
|
|
@@ -49,7 +49,7 @@ const makeDiag = (
|
|
|
49
49
|
filePath,
|
|
50
50
|
line: offsetToLine(sourceCode, node.start),
|
|
51
51
|
message,
|
|
52
|
-
rule: 'context-no-
|
|
52
|
+
rule: 'context-no-trailhead-types',
|
|
53
53
|
severity: 'error',
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -92,7 +92,7 @@ const checkSpecifiersForSurfaceTypes = (
|
|
|
92
92
|
filePath,
|
|
93
93
|
sourceCode,
|
|
94
94
|
node,
|
|
95
|
-
`Do not import
|
|
95
|
+
`Do not import trailhead type "${typeName}" in trail implementation files.`
|
|
96
96
|
);
|
|
97
97
|
};
|
|
98
98
|
|
|
@@ -111,7 +111,7 @@ const classifyImport = (
|
|
|
111
111
|
filePath,
|
|
112
112
|
sourceCode,
|
|
113
113
|
node,
|
|
114
|
-
`Do not import from
|
|
114
|
+
`Do not import from trailhead module "${moduleName}" in trail implementation files.`
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -119,9 +119,9 @@ const classifyImport = (
|
|
|
119
119
|
};
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
|
-
* Detects imports of
|
|
122
|
+
* Detects imports of trailhead-specific types in trail implementation files.
|
|
123
123
|
*/
|
|
124
|
-
export const
|
|
124
|
+
export const contextNoTrailheadTypes: WardenRule = {
|
|
125
125
|
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
126
126
|
if (!/\btrail\s*\(/.test(sourceCode)) {
|
|
127
127
|
return [];
|
|
@@ -143,8 +143,8 @@ export const contextNoSurfaceTypes: WardenRule = {
|
|
|
143
143
|
return diagnostics;
|
|
144
144
|
},
|
|
145
145
|
description:
|
|
146
|
-
'Disallow
|
|
147
|
-
name: 'context-no-
|
|
146
|
+
'Disallow trailhead-specific type imports (Request, Response, McpSession, etc.) in trail implementation files.',
|
|
147
|
+
name: 'context-no-trailhead-types',
|
|
148
148
|
|
|
149
149
|
severity: 'error',
|
|
150
150
|
};
|