@ontrails/warden 1.0.0-beta.24 → 1.0.0-beta.29
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 +102 -0
- package/README.md +35 -1
- package/package.json +10 -9
- package/src/ast.ts +109 -0
- package/src/cli.ts +44 -4
- package/src/command.ts +24 -34
- package/src/drift.ts +24 -8
- package/src/index.ts +9 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +8 -7
- package/src/rules/ast.ts +837 -6
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +210 -77
- package/src/rules/context-no-surface-types.ts +15 -12
- package/src/rules/contour-exists.ts +7 -7
- package/src/rules/dead-internal-trail.ts +11 -4
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/duplicate-public-contract.ts +91 -0
- package/src/rules/error-mapping-completeness.ts +5 -3
- package/src/rules/example-valid.ts +6 -12
- package/src/rules/fires-declarations.ts +41 -64
- package/src/rules/implementation-returns-result.ts +208 -76
- package/src/rules/incomplete-crud.ts +20 -19
- package/src/rules/index.ts +15 -0
- package/src/rules/layer-field-name-drift.ts +12 -6
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +102 -1
- package/src/rules/no-destructured-compose.ts +16 -14
- package/src/rules/no-native-error-result.ts +15 -8
- package/src/rules/no-redundant-result-error-wrap.ts +82 -29
- package/src/rules/no-sync-result-assumption.ts +70 -68
- package/src/rules/no-top-level-surface.ts +46 -64
- package/src/rules/on-references-exist.ts +1 -1
- package/src/rules/owner-projection-parity.ts +10 -13
- package/src/rules/public-export-example-coverage.ts +29 -20
- package/src/rules/public-internal-deep-imports.ts +2 -2
- package/src/rules/read-intent-fires.ts +8 -8
- package/src/rules/registry-names.ts +10 -0
- package/src/rules/resource-declarations.ts +20 -31
- package/src/rules/resource-exists.ts +2 -2
- package/src/rules/resource-id-grammar.ts +2 -2
- package/src/rules/resource-mock-coverage.ts +2 -2
- package/src/rules/static-resource-accessor-preference.ts +26 -34
- package/src/rules/surface-facet-coherence.ts +21 -29
- package/src/rules/trail-fork-coaching.ts +616 -0
- package/src/rules/trail-versioning-source.ts +56 -78
- package/src/rules/types.ts +2 -0
- package/src/rules/unreachable-detour-shadowing.ts +16 -21
- package/src/rules/valid-describe-refs.ts +14 -12
- package/src/rules/warden-export-symmetry.ts +42 -35
- package/src/rules/warden-rules-use-ast.ts +168 -50
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/error-mapping-completeness.trail.ts +1 -0
- package/src/trails/index.ts +5 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/schema.ts +4 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/warden-rules-use-ast.trail.ts +19 -0
- package/src/trails/wrap-rule.ts +1 -0
|
@@ -13,14 +13,26 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
buildSignalIdentifierResolver,
|
|
16
|
+
deriveConstString,
|
|
16
17
|
extractStringLiteral,
|
|
17
|
-
findConfigProperty,
|
|
18
18
|
findBlazeBodies,
|
|
19
|
+
findConfigProperty,
|
|
19
20
|
findTrailDefinitions,
|
|
21
|
+
getNodeBodyNode,
|
|
22
|
+
getNodeBodyStatements,
|
|
23
|
+
getNodeDeclarations,
|
|
24
|
+
getNodeId,
|
|
25
|
+
getNodeInit,
|
|
26
|
+
getNodeKey,
|
|
27
|
+
getNodeKind,
|
|
28
|
+
getNodeLeft,
|
|
29
|
+
getNodeObject,
|
|
30
|
+
getNodeProperties,
|
|
31
|
+
getNodeProperty,
|
|
32
|
+
getNodeValueNode,
|
|
20
33
|
identifierName,
|
|
21
34
|
offsetToLine,
|
|
22
35
|
parse,
|
|
23
|
-
deriveConstString,
|
|
24
36
|
walkScope,
|
|
25
37
|
} from './ast.js';
|
|
26
38
|
import type { AstNode, SignalIdentifierResolver } from './ast.js';
|
|
@@ -34,11 +46,9 @@ import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
|
34
46
|
/**
|
|
35
47
|
* Resolve an array element to a static signal ID when possible.
|
|
36
48
|
*
|
|
37
|
-
* Returns null for entries the rule can't statically resolve
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `orderPlaced` is a `Signal` imported from elsewhere) resolve via runtime
|
|
41
|
-
* normalization in `trail()`, not at lint time.
|
|
49
|
+
* Returns null for entries the rule can't statically resolve. Unresolved entries
|
|
50
|
+
* are not proof of missingness, but they also do not suppress diagnostics for
|
|
51
|
+
* statically resolved `ctx.fire()` calls that target different signals.
|
|
42
52
|
*/
|
|
43
53
|
const resolveFireElementId = (
|
|
44
54
|
element: AstNode,
|
|
@@ -99,10 +109,10 @@ interface DeclaredFires {
|
|
|
99
109
|
/**
|
|
100
110
|
* Extract declared fires from a `fires: [...]` array.
|
|
101
111
|
*
|
|
102
|
-
* Object-form entries (`fires: [someSignal]`)
|
|
103
|
-
* they
|
|
104
|
-
*
|
|
105
|
-
*
|
|
112
|
+
* Object-form entries (`fires: [someSignal]`) can be unresolved at lint time
|
|
113
|
+
* when they come from imports the rule cannot prove. Those unresolved entries
|
|
114
|
+
* do not soften concrete undeclared `ctx.fire()` calls; this mirrors
|
|
115
|
+
* `composes-declarations` and keeps real drift hard-failing.
|
|
106
116
|
*/
|
|
107
117
|
const resolveDeclaredFiresElements = (
|
|
108
118
|
elements: readonly AstNode[],
|
|
@@ -147,12 +157,8 @@ const extractMemberPair = (
|
|
|
147
157
|
return null;
|
|
148
158
|
}
|
|
149
159
|
|
|
150
|
-
const objName = identifierName(
|
|
151
|
-
|
|
152
|
-
);
|
|
153
|
-
const propName = identifierName(
|
|
154
|
-
(callee as unknown as { property?: AstNode }).property
|
|
155
|
-
);
|
|
160
|
+
const objName = identifierName(getNodeObject(callee));
|
|
161
|
+
const propName = identifierName(getNodeProperty(callee));
|
|
156
162
|
|
|
157
163
|
return objName && propName ? { objName, propName } : null;
|
|
158
164
|
};
|
|
@@ -177,8 +183,8 @@ const extractFireLocalName = (prop: AstNode): string | null => {
|
|
|
177
183
|
if (prop.type !== 'Property') {
|
|
178
184
|
return null;
|
|
179
185
|
}
|
|
180
|
-
const
|
|
181
|
-
const
|
|
186
|
+
const key = getNodeKey(prop);
|
|
187
|
+
const value = getNodeValueNode(prop);
|
|
182
188
|
const keyName = identifierName(key);
|
|
183
189
|
if (keyName !== 'fire') {
|
|
184
190
|
return null;
|
|
@@ -193,9 +199,7 @@ const collectFireNamesFromPattern = (
|
|
|
193
199
|
pattern: AstNode,
|
|
194
200
|
names: Set<string>
|
|
195
201
|
): void => {
|
|
196
|
-
const
|
|
197
|
-
properties?: readonly AstNode[];
|
|
198
|
-
};
|
|
202
|
+
const properties = getNodeProperties(pattern);
|
|
199
203
|
if (!properties) {
|
|
200
204
|
return;
|
|
201
205
|
}
|
|
@@ -224,7 +228,7 @@ const extractContextParamName = (blazeBody: AstNode): string | null => {
|
|
|
224
228
|
return null;
|
|
225
229
|
}
|
|
226
230
|
if (param.type === 'AssignmentPattern') {
|
|
227
|
-
const
|
|
231
|
+
const left = getNodeLeft(param);
|
|
228
232
|
return identifierName(left);
|
|
229
233
|
}
|
|
230
234
|
return identifierName(param);
|
|
@@ -364,10 +368,8 @@ const getCtxDestructurePattern = (
|
|
|
364
368
|
if (node.type !== 'VariableDeclarator') {
|
|
365
369
|
return null;
|
|
366
370
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
readonly init?: AstNode;
|
|
370
|
-
};
|
|
371
|
+
const id = getNodeId(node);
|
|
372
|
+
const init = getNodeInit(node);
|
|
371
373
|
if (!id || id.type !== 'ObjectPattern' || !init) {
|
|
372
374
|
return null;
|
|
373
375
|
}
|
|
@@ -398,11 +400,11 @@ const getCtxDestructurePattern = (
|
|
|
398
400
|
*/
|
|
399
401
|
/** Get the top-level statements of a blaze function's BlockStatement body. */
|
|
400
402
|
const getTopLevelStatements = (body: AstNode): readonly AstNode[] => {
|
|
401
|
-
const blockBody = (body
|
|
403
|
+
const blockBody = getNodeBodyNode(body);
|
|
402
404
|
if (!blockBody || blockBody.type !== 'BlockStatement') {
|
|
403
405
|
return [];
|
|
404
406
|
}
|
|
405
|
-
return (blockBody
|
|
407
|
+
return getNodeBodyStatements(blockBody);
|
|
406
408
|
};
|
|
407
409
|
|
|
408
410
|
/** Collect fire-local names from a single top-level VariableDeclaration. */
|
|
@@ -419,13 +421,11 @@ const collectFireNamesFromDeclaration = (
|
|
|
419
421
|
// fire('x')` would otherwise be a false positive. Skipping non-const is a
|
|
420
422
|
// small precision loss (see TSDoc on `collectDestructuredFireNames`) in
|
|
421
423
|
// exchange for eliminating that class of false positives.
|
|
422
|
-
const
|
|
424
|
+
const kind = getNodeKind(stmt);
|
|
423
425
|
if (kind !== 'const') {
|
|
424
426
|
return;
|
|
425
427
|
}
|
|
426
|
-
const declarations =
|
|
427
|
-
(stmt as unknown as { declarations?: readonly AstNode[] }).declarations ??
|
|
428
|
-
[];
|
|
428
|
+
const declarations = getNodeDeclarations(stmt) ?? [];
|
|
429
429
|
for (const decl of declarations) {
|
|
430
430
|
const pattern = getCtxDestructurePattern(decl, ctxNames);
|
|
431
431
|
if (pattern) {
|
|
@@ -576,16 +576,13 @@ const buildUndeclaredDiagnostic = (
|
|
|
576
576
|
trailId: string,
|
|
577
577
|
signalId: string,
|
|
578
578
|
filePath: string,
|
|
579
|
-
line: number
|
|
580
|
-
softened = false
|
|
579
|
+
line: number
|
|
581
580
|
): WardenDiagnostic => ({
|
|
582
581
|
filePath,
|
|
583
582
|
line,
|
|
584
|
-
message:
|
|
585
|
-
? `Trail "${trailId}": ctx.fire('${signalId}') called but '${signalId}' is not declared in fires (may be declared via object-form fires entries)`
|
|
586
|
-
: `Trail "${trailId}": ctx.fire('${signalId}') called but '${signalId}' is not declared in fires`,
|
|
583
|
+
message: `Trail "${trailId}": ctx.fire('${signalId}') called but '${signalId}' is not declared in fires`,
|
|
587
584
|
rule: 'fires-declarations',
|
|
588
|
-
severity:
|
|
585
|
+
severity: 'error',
|
|
589
586
|
});
|
|
590
587
|
|
|
591
588
|
const buildStringFireDiagnostic = (
|
|
@@ -626,20 +623,13 @@ const reportUndeclared = (
|
|
|
626
623
|
trailId: string;
|
|
627
624
|
filePath: string;
|
|
628
625
|
line: number;
|
|
629
|
-
softened?: boolean;
|
|
630
626
|
},
|
|
631
627
|
diagnostics: WardenDiagnostic[]
|
|
632
628
|
): void => {
|
|
633
629
|
for (const id of called) {
|
|
634
630
|
if (!declared.has(id)) {
|
|
635
631
|
diagnostics.push(
|
|
636
|
-
buildUndeclaredDiagnostic(
|
|
637
|
-
ctx.trailId,
|
|
638
|
-
id,
|
|
639
|
-
ctx.filePath,
|
|
640
|
-
ctx.line,
|
|
641
|
-
ctx.softened
|
|
642
|
-
)
|
|
632
|
+
buildUndeclaredDiagnostic(ctx.trailId, id, ctx.filePath, ctx.line)
|
|
643
633
|
);
|
|
644
634
|
}
|
|
645
635
|
}
|
|
@@ -648,12 +638,9 @@ const reportUndeclared = (
|
|
|
648
638
|
/**
|
|
649
639
|
* Emit warning for each declared ID not present in called set.
|
|
650
640
|
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
* Signal-value entries might cover an unknown set of called IDs. In the
|
|
655
|
-
* unused direction, a declared string-literal that is never called is
|
|
656
|
-
* genuinely unused regardless of whether other entries are unresolved.
|
|
641
|
+
* Unused string-literal declarations remain warnings: a declared string that is
|
|
642
|
+
* never called is visible cleanup debt even when other object-form declarations
|
|
643
|
+
* are unresolved.
|
|
657
644
|
*/
|
|
658
645
|
const reportUnused = (
|
|
659
646
|
declared: ReadonlySet<string>,
|
|
@@ -708,17 +695,7 @@ const checkTrailDefinition = (
|
|
|
708
695
|
);
|
|
709
696
|
|
|
710
697
|
reportStringFireCalls(called.stringIds, ctx, diagnostics);
|
|
711
|
-
|
|
712
|
-
// downgrade "undeclared" diagnostics from error to warn with a disclaimer
|
|
713
|
-
// instead of suppressing entirely. The developer still sees genuinely
|
|
714
|
-
// undeclared calls, but we can't statically prove the call isn't covered by
|
|
715
|
-
// a Signal-value entry the runtime will normalize.
|
|
716
|
-
reportUndeclared(
|
|
717
|
-
signalValueCalledIds,
|
|
718
|
-
declared.ids,
|
|
719
|
-
{ ...ctx, softened: declared.hasUnresolved },
|
|
720
|
-
diagnostics
|
|
721
|
-
);
|
|
698
|
+
reportUndeclared(signalValueCalledIds, declared.ids, ctx, diagnostics);
|
|
722
699
|
reportUnused(declared.ids, called.ids, ctx, diagnostics);
|
|
723
700
|
};
|
|
724
701
|
|