@ontrails/warden 1.0.0-beta.13 → 1.0.0-beta.15
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 +30 -0
- package/README.md +31 -20
- package/dist/cli.d.ts +19 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +261 -64
- package/dist/cli.js.map +1 -1
- package/dist/draft.d.ts +5 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +16 -0
- package/dist/draft.js.map +1 -0
- package/dist/drift.d.ts +10 -7
- package/dist/drift.d.ts.map +1 -1
- package/dist/drift.js +50 -16
- package/dist/drift.js.map +1 -1
- package/dist/formatters.d.ts +2 -1
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +15 -4
- package/dist/formatters.js.map +1 -1
- package/dist/index.d.ts +9 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -17
- package/dist/index.js.map +1 -1
- package/dist/rules/ast.d.ts +412 -7
- package/dist/rules/ast.d.ts.map +1 -1
- package/dist/rules/ast.js +1847 -102
- package/dist/rules/ast.js.map +1 -1
- package/dist/rules/circular-refs.d.ts +6 -0
- package/dist/rules/circular-refs.d.ts.map +1 -0
- package/dist/rules/circular-refs.js +83 -0
- package/dist/rules/circular-refs.js.map +1 -0
- package/dist/rules/context-no-surface-types.d.ts.map +1 -1
- package/dist/rules/context-no-surface-types.js +59 -3
- package/dist/rules/context-no-surface-types.js.map +1 -1
- package/dist/rules/contour-exists.d.ts +7 -0
- package/dist/rules/contour-exists.d.ts.map +1 -0
- package/dist/rules/contour-exists.js +113 -0
- package/dist/rules/contour-exists.js.map +1 -0
- package/dist/rules/contour-ids.d.ts +10 -0
- package/dist/rules/contour-ids.d.ts.map +1 -0
- package/dist/rules/contour-ids.js +12 -0
- package/dist/rules/contour-ids.js.map +1 -0
- package/dist/rules/cross-declarations.d.ts.map +1 -1
- package/dist/rules/cross-declarations.js +171 -57
- package/dist/rules/cross-declarations.js.map +1 -1
- package/dist/rules/dead-internal-trail.d.ts +3 -0
- package/dist/rules/dead-internal-trail.d.ts.map +1 -0
- package/dist/rules/dead-internal-trail.js +80 -0
- package/dist/rules/dead-internal-trail.js.map +1 -0
- package/dist/rules/draft-file-marking.d.ts +6 -0
- package/dist/rules/draft-file-marking.d.ts.map +1 -0
- package/dist/rules/draft-file-marking.js +87 -0
- package/dist/rules/draft-file-marking.js.map +1 -0
- package/dist/rules/draft-visible-debt.d.ts +12 -0
- package/dist/rules/draft-visible-debt.d.ts.map +1 -0
- package/dist/rules/draft-visible-debt.js +50 -0
- package/dist/rules/draft-visible-debt.js.map +1 -0
- package/dist/rules/error-mapping-completeness.d.ts +13 -0
- package/dist/rules/error-mapping-completeness.d.ts.map +1 -0
- package/dist/rules/error-mapping-completeness.js +160 -0
- package/dist/rules/error-mapping-completeness.js.map +1 -0
- package/dist/rules/example-valid.d.ts +6 -0
- package/dist/rules/example-valid.d.ts.map +1 -0
- package/dist/rules/example-valid.js +203 -0
- package/dist/rules/example-valid.js.map +1 -0
- package/dist/rules/fires-declarations.d.ts +16 -0
- package/dist/rules/fires-declarations.d.ts.map +1 -0
- package/dist/rules/fires-declarations.js +444 -0
- package/dist/rules/fires-declarations.js.map +1 -0
- package/dist/rules/implementation-returns-result.d.ts +9 -0
- package/dist/rules/implementation-returns-result.d.ts.map +1 -1
- package/dist/rules/implementation-returns-result.js +638 -76
- package/dist/rules/implementation-returns-result.js.map +1 -1
- package/dist/rules/incomplete-accessor-for-standard-op.d.ts +30 -0
- package/dist/rules/incomplete-accessor-for-standard-op.d.ts.map +1 -0
- package/dist/rules/incomplete-accessor-for-standard-op.js +226 -0
- package/dist/rules/incomplete-accessor-for-standard-op.js.map +1 -0
- package/dist/rules/incomplete-crud.d.ts +21 -0
- package/dist/rules/incomplete-crud.d.ts.map +1 -0
- package/dist/rules/incomplete-crud.js +368 -0
- package/dist/rules/incomplete-crud.js.map +1 -0
- package/dist/rules/index.d.ts +40 -7
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +91 -15
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/intent-propagation.d.ts +3 -0
- package/dist/rules/intent-propagation.d.ts.map +1 -0
- package/dist/rules/intent-propagation.js +57 -0
- package/dist/rules/intent-propagation.js.map +1 -0
- package/dist/rules/missing-reconcile.d.ts +3 -0
- package/dist/rules/missing-reconcile.d.ts.map +1 -0
- package/dist/rules/missing-reconcile.js +44 -0
- package/dist/rules/missing-reconcile.js.map +1 -0
- package/dist/rules/missing-visibility.d.ts +3 -0
- package/dist/rules/missing-visibility.d.ts.map +1 -0
- package/dist/rules/missing-visibility.js +63 -0
- package/dist/rules/missing-visibility.js.map +1 -0
- package/dist/rules/no-direct-impl-in-route.d.ts.map +1 -1
- package/dist/rules/no-direct-impl-in-route.js +0 -3
- package/dist/rules/no-direct-impl-in-route.js.map +1 -1
- package/dist/rules/no-direct-implementation-call.js +1 -1
- package/dist/rules/no-direct-implementation-call.js.map +1 -1
- package/dist/rules/no-sync-result-assumption.d.ts.map +1 -1
- package/dist/rules/no-sync-result-assumption.js +870 -61
- package/dist/rules/no-sync-result-assumption.js.map +1 -1
- package/dist/rules/no-throw-in-detour-recover.d.ts +3 -0
- package/dist/rules/no-throw-in-detour-recover.d.ts.map +1 -0
- package/dist/rules/no-throw-in-detour-recover.js +147 -0
- package/dist/rules/no-throw-in-detour-recover.js.map +1 -0
- package/dist/rules/no-throw-in-detour-target.d.ts +4 -1
- package/dist/rules/no-throw-in-detour-target.d.ts.map +1 -1
- package/dist/rules/no-throw-in-detour-target.js +6 -3
- package/dist/rules/no-throw-in-detour-target.js.map +1 -1
- package/dist/rules/no-throw-in-implementation.d.ts +4 -2
- package/dist/rules/no-throw-in-implementation.d.ts.map +1 -1
- package/dist/rules/no-throw-in-implementation.js +6 -4
- package/dist/rules/no-throw-in-implementation.js.map +1 -1
- package/dist/rules/on-references-exist.d.ts +14 -0
- package/dist/rules/on-references-exist.d.ts.map +1 -0
- package/dist/rules/on-references-exist.js +109 -0
- package/dist/rules/on-references-exist.js.map +1 -0
- package/dist/rules/orphaned-signal.d.ts +3 -0
- package/dist/rules/orphaned-signal.d.ts.map +1 -0
- package/dist/rules/orphaned-signal.js +67 -0
- package/dist/rules/orphaned-signal.js.map +1 -0
- package/dist/rules/permit-governance.d.ts +3 -0
- package/dist/rules/permit-governance.d.ts.map +1 -0
- package/dist/rules/permit-governance.js +15 -0
- package/dist/rules/permit-governance.js.map +1 -0
- package/dist/rules/reference-exists.d.ts +6 -0
- package/dist/rules/reference-exists.d.ts.map +1 -0
- package/dist/rules/reference-exists.js +47 -0
- package/dist/rules/reference-exists.js.map +1 -0
- package/dist/rules/registry-names.d.ts +8 -0
- package/dist/rules/registry-names.d.ts.map +1 -0
- package/dist/rules/registry-names.js +83 -0
- package/dist/rules/registry-names.js.map +1 -0
- package/dist/rules/resource-declarations.d.ts +14 -0
- package/dist/rules/resource-declarations.d.ts.map +1 -0
- package/dist/rules/resource-declarations.js +413 -0
- package/dist/rules/resource-declarations.js.map +1 -0
- package/dist/rules/resource-exists.d.ts +6 -0
- package/dist/rules/resource-exists.d.ts.map +1 -0
- package/dist/rules/resource-exists.js +90 -0
- package/dist/rules/resource-exists.js.map +1 -0
- package/dist/rules/resource-id-grammar.d.ts +3 -0
- package/dist/rules/resource-id-grammar.d.ts.map +1 -0
- package/dist/rules/resource-id-grammar.js +39 -0
- package/dist/rules/resource-id-grammar.js.map +1 -0
- package/dist/rules/specs.d.ts.map +1 -1
- package/dist/rules/specs.js +5 -1
- package/dist/rules/specs.js.map +1 -1
- package/dist/rules/types.d.ts +53 -4
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/unreachable-detour-shadowing.d.ts +3 -0
- package/dist/rules/unreachable-detour-shadowing.d.ts.map +1 -0
- package/dist/rules/unreachable-detour-shadowing.js +202 -0
- package/dist/rules/unreachable-detour-shadowing.js.map +1 -0
- package/dist/rules/valid-describe-refs.d.ts.map +1 -1
- package/dist/rules/valid-describe-refs.js +132 -16
- package/dist/rules/valid-describe-refs.js.map +1 -1
- package/dist/rules/valid-detour-contract.d.ts +3 -0
- package/dist/rules/valid-detour-contract.d.ts.map +1 -0
- package/dist/rules/valid-detour-contract.js +47 -0
- package/dist/rules/valid-detour-contract.js.map +1 -0
- package/dist/rules/valid-detour-refs.d.ts.map +1 -1
- package/dist/rules/valid-detour-refs.js +73 -82
- package/dist/rules/valid-detour-refs.js.map +1 -1
- package/dist/rules/warden-export-symmetry.d.ts +7 -0
- package/dist/rules/warden-export-symmetry.d.ts.map +1 -0
- package/dist/rules/warden-export-symmetry.js +352 -0
- package/dist/rules/warden-export-symmetry.js.map +1 -0
- package/dist/rules/warden-rules-use-ast.d.ts +17 -0
- package/dist/rules/warden-rules-use-ast.d.ts.map +1 -0
- package/dist/rules/warden-rules-use-ast.js +778 -0
- package/dist/rules/warden-rules-use-ast.js.map +1 -0
- package/dist/trails/circular-refs.trail.d.ts +24 -0
- package/dist/trails/circular-refs.trail.d.ts.map +1 -0
- package/dist/trails/circular-refs.trail.js +29 -0
- package/dist/trails/circular-refs.trail.js.map +1 -0
- package/dist/trails/context-no-surface-types.trail.d.ts +2 -2
- package/dist/trails/context-no-surface-types.trail.d.ts.map +1 -1
- package/dist/trails/context-no-trailhead-types.trail.d.ts +2 -2
- package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -1
- package/dist/trails/contour-exists.trail.d.ts +24 -0
- package/dist/trails/contour-exists.trail.d.ts.map +1 -0
- package/dist/trails/contour-exists.trail.js +21 -0
- package/dist/trails/contour-exists.trail.js.map +1 -0
- package/dist/trails/cross-declarations.trail.d.ts +2 -2
- package/dist/trails/cross-declarations.trail.d.ts.map +1 -1
- package/dist/trails/dead-internal-trail.trail.d.ts +24 -0
- package/dist/trails/dead-internal-trail.trail.d.ts.map +1 -0
- package/dist/trails/dead-internal-trail.trail.js +26 -0
- package/dist/trails/dead-internal-trail.trail.js.map +1 -0
- package/dist/trails/{provision-declarations.trail.d.ts → draft-file-marking.trail.d.ts} +3 -3
- package/dist/trails/draft-file-marking.trail.d.ts.map +1 -0
- package/dist/trails/draft-file-marking.trail.js +16 -0
- package/dist/trails/draft-file-marking.trail.js.map +1 -0
- package/dist/trails/draft-visible-debt.trail.d.ts +13 -0
- package/dist/trails/draft-visible-debt.trail.d.ts.map +1 -0
- package/dist/trails/draft-visible-debt.trail.js +16 -0
- package/dist/trails/draft-visible-debt.trail.js.map +1 -0
- package/dist/trails/error-mapping-completeness.trail.d.ts +13 -0
- package/dist/trails/error-mapping-completeness.trail.d.ts.map +1 -0
- package/dist/trails/error-mapping-completeness.trail.js +29 -0
- package/dist/trails/error-mapping-completeness.trail.js.map +1 -0
- package/dist/trails/{follow-declarations.trail.d.ts → example-valid.trail.d.ts} +3 -3
- package/dist/trails/example-valid.trail.d.ts.map +1 -0
- package/dist/trails/example-valid.trail.js +25 -0
- package/dist/trails/example-valid.trail.js.map +1 -0
- package/dist/trails/fires-declarations.trail.d.ts +13 -0
- package/dist/trails/fires-declarations.trail.d.ts.map +1 -0
- package/dist/trails/fires-declarations.trail.js +22 -0
- package/dist/trails/fires-declarations.trail.js.map +1 -0
- package/dist/trails/implementation-returns-result.trail.d.ts +2 -2
- package/dist/trails/implementation-returns-result.trail.d.ts.map +1 -1
- package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts +12 -0
- package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts.map +1 -0
- package/dist/trails/incomplete-accessor-for-standard-op.trail.js +60 -0
- package/dist/trails/incomplete-accessor-for-standard-op.trail.js.map +1 -0
- package/dist/trails/incomplete-crud.trail.d.ts +24 -0
- package/dist/trails/incomplete-crud.trail.d.ts.map +1 -0
- package/dist/trails/incomplete-crud.trail.js +39 -0
- package/dist/trails/incomplete-crud.trail.js.map +1 -0
- package/dist/trails/index.d.ts +29 -7
- package/dist/trails/index.d.ts.map +1 -1
- package/dist/trails/index.js +28 -6
- package/dist/trails/index.js.map +1 -1
- package/dist/trails/intent-propagation.trail.d.ts +24 -0
- package/dist/trails/intent-propagation.trail.d.ts.map +1 -0
- package/dist/trails/intent-propagation.trail.js +30 -0
- package/dist/trails/intent-propagation.trail.js.map +1 -0
- package/dist/trails/missing-reconcile.trail.d.ts +24 -0
- package/dist/trails/missing-reconcile.trail.d.ts.map +1 -0
- package/dist/trails/missing-reconcile.trail.js +33 -0
- package/dist/trails/missing-reconcile.trail.js.map +1 -0
- package/dist/trails/missing-visibility.trail.d.ts +24 -0
- package/dist/trails/missing-visibility.trail.d.ts.map +1 -0
- package/dist/trails/missing-visibility.trail.js +22 -0
- package/dist/trails/missing-visibility.trail.js.map +1 -0
- package/dist/trails/no-direct-impl-in-route.trail.d.ts +2 -2
- package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +1 -1
- package/dist/trails/no-direct-implementation-call.trail.d.ts +2 -2
- package/dist/trails/no-direct-implementation-call.trail.d.ts.map +1 -1
- package/dist/trails/no-sync-result-assumption.trail.d.ts +2 -2
- package/dist/trails/no-sync-result-assumption.trail.d.ts.map +1 -1
- package/dist/trails/no-throw-in-detour-recover.trail.d.ts +13 -0
- package/dist/trails/no-throw-in-detour-recover.trail.d.ts.map +1 -0
- package/dist/trails/no-throw-in-detour-recover.trail.js +24 -0
- package/dist/trails/no-throw-in-detour-recover.trail.js.map +1 -0
- package/dist/trails/no-throw-in-detour-target.trail.d.ts +13 -3
- package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +1 -1
- package/dist/trails/no-throw-in-implementation.trail.d.ts +2 -2
- package/dist/trails/no-throw-in-implementation.trail.d.ts.map +1 -1
- package/dist/trails/on-references-exist.trail.d.ts +24 -0
- package/dist/trails/on-references-exist.trail.d.ts.map +1 -0
- package/dist/trails/on-references-exist.trail.js +21 -0
- package/dist/trails/on-references-exist.trail.js.map +1 -0
- package/dist/trails/orphaned-signal.trail.d.ts +24 -0
- package/dist/trails/orphaned-signal.trail.d.ts.map +1 -0
- package/dist/trails/orphaned-signal.trail.js +36 -0
- package/dist/trails/orphaned-signal.trail.js.map +1 -0
- package/dist/trails/permit-governance.trail.d.ts +12 -0
- package/dist/trails/permit-governance.trail.d.ts.map +1 -0
- package/dist/trails/permit-governance.trail.js +47 -0
- package/dist/trails/permit-governance.trail.js.map +1 -0
- package/dist/trails/prefer-schema-inference.trail.d.ts +2 -2
- package/dist/trails/prefer-schema-inference.trail.d.ts.map +1 -1
- package/dist/trails/reference-exists.trail.d.ts +24 -0
- package/dist/trails/reference-exists.trail.d.ts.map +1 -0
- package/dist/trails/reference-exists.trail.js +25 -0
- package/dist/trails/reference-exists.trail.js.map +1 -0
- package/dist/trails/resource-declarations.trail.d.ts +13 -0
- package/dist/trails/resource-declarations.trail.d.ts.map +1 -0
- package/dist/trails/{provision-declarations.trail.js → resource-declarations.trail.js} +7 -7
- package/dist/trails/resource-declarations.trail.js.map +1 -0
- package/dist/trails/resource-exists.trail.d.ts +24 -0
- package/dist/trails/resource-exists.trail.d.ts.map +1 -0
- package/dist/trails/{provision-exists.trail.js → resource-exists.trail.js} +8 -8
- package/dist/trails/resource-exists.trail.js.map +1 -0
- package/dist/trails/resource-id-grammar.trail.d.ts +13 -0
- package/dist/trails/resource-id-grammar.trail.d.ts.map +1 -0
- package/dist/trails/resource-id-grammar.trail.js +38 -0
- package/dist/trails/resource-id-grammar.trail.js.map +1 -0
- package/dist/trails/run.d.ts +25 -9
- package/dist/trails/run.d.ts.map +1 -1
- package/dist/trails/run.js +63 -19
- package/dist/trails/run.js.map +1 -1
- package/dist/trails/schema.d.ts +28 -3
- package/dist/trails/schema.d.ts.map +1 -1
- package/dist/trails/schema.js +57 -4
- package/dist/trails/schema.js.map +1 -1
- package/dist/trails/unreachable-detour-shadowing.trail.d.ts +13 -0
- package/dist/trails/unreachable-detour-shadowing.trail.d.ts.map +1 -0
- package/dist/trails/unreachable-detour-shadowing.trail.js +44 -0
- package/dist/trails/unreachable-detour-shadowing.trail.js.map +1 -0
- package/dist/trails/valid-describe-refs.trail.d.ts +12 -3
- package/dist/trails/valid-describe-refs.trail.d.ts.map +1 -1
- package/dist/trails/valid-detour-contract.trail.d.ts +12 -0
- package/dist/trails/valid-detour-contract.trail.d.ts.map +1 -0
- package/dist/trails/valid-detour-contract.trail.js +66 -0
- package/dist/trails/valid-detour-contract.trail.js.map +1 -0
- package/dist/trails/valid-detour-refs.trail.d.ts +13 -3
- package/dist/trails/valid-detour-refs.trail.d.ts.map +1 -1
- package/dist/trails/warden-export-symmetry.trail.d.ts +13 -0
- package/dist/trails/warden-export-symmetry.trail.d.ts.map +1 -0
- package/dist/trails/warden-export-symmetry.trail.js +16 -0
- package/dist/trails/warden-export-symmetry.trail.js.map +1 -0
- package/dist/trails/warden-rules-use-ast.trail.d.ts +13 -0
- package/dist/trails/warden-rules-use-ast.trail.d.ts.map +1 -0
- package/dist/trails/warden-rules-use-ast.trail.js +41 -0
- package/dist/trails/warden-rules-use-ast.trail.js.map +1 -0
- package/dist/trails/wrap-rule.d.ts +16 -2
- package/dist/trails/wrap-rule.d.ts.map +1 -1
- package/dist/trails/wrap-rule.js +71 -11
- package/dist/trails/wrap-rule.js.map +1 -1
- package/package.json +7 -4
- package/src/__tests__/ast.test.ts +613 -0
- package/src/__tests__/circular-refs.test.ts +121 -0
- package/src/__tests__/cli.test.ts +360 -32
- package/src/__tests__/contour-exists.test.ts +203 -0
- package/src/__tests__/cross-declarations.test.ts +245 -0
- package/src/__tests__/dead-internal-trail.test.ts +81 -0
- package/src/__tests__/draft-rules-context.test.ts +150 -0
- package/src/__tests__/drift.test.ts +75 -5
- package/src/__tests__/error-mapping-completeness.test.ts +56 -0
- package/src/__tests__/example-valid.test.ts +101 -0
- package/src/__tests__/fires-declarations-param-destructure.test.ts +54 -0
- package/src/__tests__/fires-declarations.test.ts +652 -0
- package/src/__tests__/formatters.test.ts +2 -2
- package/src/__tests__/implementation-returns-result.test.ts +1016 -2
- package/src/__tests__/incomplete-accessor-for-standard-op.test.ts +337 -0
- package/src/__tests__/incomplete-crud.test.ts +498 -0
- package/src/__tests__/intent-propagation.test.ts +116 -0
- package/src/__tests__/missing-reconcile.test.ts +154 -0
- package/src/__tests__/missing-visibility.test.ts +108 -0
- package/src/__tests__/no-sync-result-assumption.test.ts +870 -39
- package/src/__tests__/no-throw-in-detour-recover.test.ts +93 -0
- package/src/__tests__/no-throw-in-implementation.test.ts +88 -0
- package/src/__tests__/on-references-exist.test.ts +151 -0
- package/src/__tests__/orphaned-signal.test.ts +137 -0
- package/src/__tests__/permit-governance.test.ts +66 -0
- package/src/__tests__/reference-exists.test.ts +281 -0
- package/src/__tests__/resource-declarations.test.ts +448 -0
- package/src/__tests__/resource-exists.test.ts +122 -0
- package/src/__tests__/resource-id-grammar.test.ts +50 -0
- package/src/__tests__/rules.test.ts +17 -77
- package/src/__tests__/topo-aware-rule.test.ts +257 -0
- package/src/__tests__/trails.test.ts +2 -2
- package/src/__tests__/unreachable-detour-shadowing.test.ts +128 -0
- package/src/__tests__/valid-describe-refs.test.ts +183 -0
- package/src/__tests__/valid-detour-contract.test.ts +86 -0
- package/src/__tests__/warden-export-symmetry.test.ts +251 -0
- package/src/__tests__/warden-rules-use-ast.test.ts +468 -0
- package/src/__tests__/wrap-rule.test.ts +3 -3
- package/src/cli.ts +458 -91
- package/src/draft.ts +22 -0
- package/src/drift.ts +63 -21
- package/src/formatters.ts +15 -4
- package/src/index.ts +62 -23
- package/src/rules/ast.ts +2715 -119
- package/src/rules/circular-refs.ts +154 -0
- package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
- package/src/rules/contour-exists.ts +251 -0
- package/src/rules/contour-ids.ts +15 -0
- package/src/rules/cross-declarations.ts +277 -69
- package/src/rules/dead-internal-trail.ts +141 -0
- package/src/rules/draft-file-marking.ts +160 -0
- package/src/rules/draft-visible-debt.ts +87 -0
- package/src/rules/error-mapping-completeness.ts +273 -0
- package/src/rules/example-valid.ts +401 -0
- package/src/rules/fires-declarations.ts +609 -0
- package/src/rules/implementation-returns-result.ts +1042 -122
- package/src/rules/incomplete-accessor-for-standard-op.ts +315 -0
- package/src/rules/incomplete-crud.ts +579 -0
- package/src/rules/index.ts +95 -16
- package/src/rules/intent-propagation.ts +142 -0
- package/src/rules/missing-reconcile.ts +98 -0
- package/src/rules/missing-visibility.ts +110 -0
- package/src/rules/no-direct-impl-in-route.ts +0 -4
- package/src/rules/no-direct-implementation-call.ts +1 -1
- package/src/rules/no-sync-result-assumption.ts +1134 -96
- package/src/rules/no-throw-in-detour-recover.ts +225 -0
- package/src/rules/no-throw-in-implementation.ts +6 -4
- package/src/rules/on-references-exist.ts +194 -0
- package/src/rules/orphaned-signal.ts +150 -0
- package/src/rules/permit-governance.ts +25 -0
- package/src/rules/reference-exists.ts +98 -0
- package/src/rules/registry-names.ts +83 -0
- package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
- package/src/rules/{provision-exists.ts → resource-exists.ts} +48 -51
- package/src/rules/resource-id-grammar.ts +65 -0
- package/src/rules/specs.ts +5 -1
- package/src/rules/types.ts +57 -4
- package/src/rules/unreachable-detour-shadowing.ts +375 -0
- package/src/rules/valid-describe-refs.ts +160 -32
- package/src/rules/valid-detour-contract.ts +78 -0
- package/src/rules/warden-export-symmetry.ts +533 -0
- package/src/rules/warden-rules-use-ast.ts +996 -0
- package/src/trails/circular-refs.trail.ts +29 -0
- package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
- package/src/trails/contour-exists.trail.ts +21 -0
- package/src/trails/dead-internal-trail.trail.ts +26 -0
- package/src/trails/draft-file-marking.trail.ts +16 -0
- package/src/trails/draft-visible-debt.trail.ts +16 -0
- package/src/trails/error-mapping-completeness.trail.ts +29 -0
- package/src/trails/example-valid.trail.ts +25 -0
- package/src/trails/fires-declarations.trail.ts +22 -0
- package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
- package/src/trails/incomplete-crud.trail.ts +39 -0
- package/src/trails/index.ts +40 -7
- package/src/trails/intent-propagation.trail.ts +30 -0
- package/src/trails/missing-reconcile.trail.ts +33 -0
- package/src/trails/missing-visibility.trail.ts +22 -0
- package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
- package/src/trails/on-references-exist.trail.ts +21 -0
- package/src/trails/orphaned-signal.trail.ts +36 -0
- package/src/trails/permit-governance.trail.ts +51 -0
- package/src/trails/reference-exists.trail.ts +25 -0
- package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
- package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
- package/src/trails/resource-id-grammar.trail.ts +39 -0
- package/src/trails/run.ts +121 -24
- package/src/trails/schema.ts +66 -4
- package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
- package/src/trails/valid-detour-contract.trail.ts +71 -0
- package/src/trails/warden-export-symmetry.trail.ts +16 -0
- package/src/trails/warden-rules-use-ast.trail.ts +45 -0
- package/src/trails/wrap-rule.ts +104 -12
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/rules/follow-declarations.d.ts +0 -13
- package/dist/rules/follow-declarations.d.ts.map +0 -1
- package/dist/rules/follow-declarations.js +0 -264
- package/dist/rules/follow-declarations.js.map +0 -1
- package/dist/rules/provision-declarations.d.ts +0 -14
- package/dist/rules/provision-declarations.d.ts.map +0 -1
- package/dist/rules/provision-declarations.js +0 -344
- package/dist/rules/provision-declarations.js.map +0 -1
- package/dist/rules/provision-exists.d.ts +0 -6
- package/dist/rules/provision-exists.d.ts.map +0 -1
- package/dist/rules/provision-exists.js +0 -89
- package/dist/rules/provision-exists.js.map +0 -1
- package/dist/rules/service-declarations.d.ts +0 -16
- package/dist/rules/service-declarations.d.ts.map +0 -1
- package/dist/rules/service-declarations.js +0 -346
- package/dist/rules/service-declarations.js.map +0 -1
- package/dist/rules/service-exists.d.ts +0 -8
- package/dist/rules/service-exists.d.ts.map +0 -1
- package/dist/rules/service-exists.js +0 -91
- package/dist/rules/service-exists.js.map +0 -1
- package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
- package/dist/trails/follow-declarations.trail.js +0 -22
- package/dist/trails/follow-declarations.trail.js.map +0 -1
- package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
- package/dist/trails/provision-declarations.trail.js.map +0 -1
- package/dist/trails/provision-exists.trail.d.ts +0 -15
- package/dist/trails/provision-exists.trail.d.ts.map +0 -1
- package/dist/trails/provision-exists.trail.js.map +0 -1
- package/dist/trails/service-declarations.trail.d.ts +0 -26
- package/dist/trails/service-declarations.trail.d.ts.map +0 -1
- package/dist/trails/service-declarations.trail.js +0 -27
- package/dist/trails/service-declarations.trail.js.map +0 -1
- package/dist/trails/service-exists.trail.d.ts +0 -32
- package/dist/trails/service-exists.trail.d.ts.map +0 -1
- package/dist/trails/service-exists.trail.js +0 -29
- package/dist/trails/service-exists.trail.js.map +0 -1
- package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
- package/src/__tests__/provision-declarations.test.ts +0 -318
- package/src/__tests__/provision-exists.test.ts +0 -122
- package/src/rules/no-throw-in-detour-target.ts +0 -150
- package/src/rules/valid-detour-refs.ts +0 -187
- package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
- package/src/trails/valid-detour-refs.trail.ts +0 -24
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { contourExists } from '../rules/contour-exists.js';
|
|
4
|
+
|
|
5
|
+
const TEST_FILE = 'entity.ts';
|
|
6
|
+
|
|
7
|
+
describe('contour-exists', () => {
|
|
8
|
+
describe('local declarations', () => {
|
|
9
|
+
test('passes when a locally declared contour exists', () => {
|
|
10
|
+
const code = `
|
|
11
|
+
import { Result, contour, trail } from '@ontrails/core';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
|
|
14
|
+
const user = contour('user', {
|
|
15
|
+
id: z.string().uuid(),
|
|
16
|
+
}, { identity: 'id' });
|
|
17
|
+
|
|
18
|
+
trail('user.create', {
|
|
19
|
+
contours: [user],
|
|
20
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
21
|
+
});
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
expect(contourExists.check(code, TEST_FILE)).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('keeps local contour declarations when project context is present', () => {
|
|
28
|
+
const code = `
|
|
29
|
+
import { Result, contour, trail } from '@ontrails/core';
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
|
|
32
|
+
const user = contour('user', {
|
|
33
|
+
id: z.string().uuid(),
|
|
34
|
+
}, { identity: 'id' });
|
|
35
|
+
|
|
36
|
+
trail('user.create', {
|
|
37
|
+
contours: [user],
|
|
38
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
39
|
+
});
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
contourExists.checkWithContext(code, TEST_FILE, {
|
|
44
|
+
knownContourIds: new Set<string>(),
|
|
45
|
+
knownTrailIds: new Set(['user.create']),
|
|
46
|
+
})
|
|
47
|
+
).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('named imports', () => {
|
|
52
|
+
test('flags a missing contour declaration', () => {
|
|
53
|
+
const code = `
|
|
54
|
+
import { Result, trail } from '@ontrails/core';
|
|
55
|
+
import { user } from './contours';
|
|
56
|
+
|
|
57
|
+
trail('user.create', {
|
|
58
|
+
contours: [user],
|
|
59
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
60
|
+
});
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
|
|
64
|
+
knownContourIds: new Set<string>(),
|
|
65
|
+
knownTrailIds: new Set(['user.create']),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(diagnostics).toHaveLength(1);
|
|
69
|
+
expect(diagnostics[0]?.rule).toBe('contour-exists');
|
|
70
|
+
expect(diagnostics[0]?.message).toContain('user');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('resolves aliased imports to the original contour name', () => {
|
|
74
|
+
const code = `
|
|
75
|
+
import { Result, trail } from '@ontrails/core';
|
|
76
|
+
import { user as userModel } from './contours';
|
|
77
|
+
|
|
78
|
+
trail('user.create', {
|
|
79
|
+
contours: [userModel],
|
|
80
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
81
|
+
});
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
expect(
|
|
85
|
+
contourExists.checkWithContext(code, TEST_FILE, {
|
|
86
|
+
knownContourIds: new Set(['user']),
|
|
87
|
+
knownTrailIds: new Set(['user.create']),
|
|
88
|
+
})
|
|
89
|
+
).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('passes when project context includes an imported contour', () => {
|
|
93
|
+
const code = `
|
|
94
|
+
import { Result, trail } from '@ontrails/core';
|
|
95
|
+
import { user } from './contours';
|
|
96
|
+
|
|
97
|
+
trail('user.create', {
|
|
98
|
+
contours: [user],
|
|
99
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
100
|
+
});
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
expect(
|
|
104
|
+
contourExists.checkWithContext(code, TEST_FILE, {
|
|
105
|
+
knownContourIds: new Set(['user']),
|
|
106
|
+
knownTrailIds: new Set(['user.create']),
|
|
107
|
+
})
|
|
108
|
+
).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('default imports', () => {
|
|
113
|
+
test('flags missing default-imported contour declarations', () => {
|
|
114
|
+
const code = `
|
|
115
|
+
import { Result, trail } from '@ontrails/core';
|
|
116
|
+
import userModel from './contours';
|
|
117
|
+
|
|
118
|
+
trail('user.create', {
|
|
119
|
+
contours: [userModel],
|
|
120
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
121
|
+
});
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
|
|
125
|
+
knownContourIds: new Set<string>(),
|
|
126
|
+
knownTrailIds: new Set(['user.create']),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(diagnostics).toHaveLength(1);
|
|
130
|
+
expect(diagnostics[0]?.rule).toBe('contour-exists');
|
|
131
|
+
expect(diagnostics[0]?.message).toContain('userModel');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('namespace imports', () => {
|
|
136
|
+
test('flags missing namespace-imported contour declarations', () => {
|
|
137
|
+
const code = `
|
|
138
|
+
import { Result, trail } from '@ontrails/core';
|
|
139
|
+
import * as contours from './contours';
|
|
140
|
+
|
|
141
|
+
trail('user.create', {
|
|
142
|
+
contours: [contours.user],
|
|
143
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
144
|
+
});
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
const diagnostics = contourExists.checkWithContext(code, TEST_FILE, {
|
|
148
|
+
knownContourIds: new Set<string>(),
|
|
149
|
+
knownTrailIds: new Set(['user.create']),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(diagnostics).toHaveLength(1);
|
|
153
|
+
expect(diagnostics[0]?.rule).toBe('contour-exists');
|
|
154
|
+
expect(diagnostics[0]?.message).toContain('user');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('resolves namespace-imported contour declarations when known', () => {
|
|
158
|
+
const code = `
|
|
159
|
+
import { Result, trail } from '@ontrails/core';
|
|
160
|
+
import * as contours from './contours';
|
|
161
|
+
|
|
162
|
+
trail('user.create', {
|
|
163
|
+
contours: [contours.user],
|
|
164
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
165
|
+
});
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
expect(
|
|
169
|
+
contourExists.checkWithContext(code, TEST_FILE, {
|
|
170
|
+
knownContourIds: new Set(['user']),
|
|
171
|
+
knownTrailIds: new Set(['user.create']),
|
|
172
|
+
})
|
|
173
|
+
).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('ignores namespace member access when receiver is shadowed by a local binding', () => {
|
|
177
|
+
const code = `
|
|
178
|
+
import { Result, trail } from '@ontrails/core';
|
|
179
|
+
import * as contours from './contours';
|
|
180
|
+
|
|
181
|
+
function makeTrail() {
|
|
182
|
+
const contours = { user: 'not-a-contour' };
|
|
183
|
+
return trail('user.create', {
|
|
184
|
+
contours: [contours.user],
|
|
185
|
+
blaze: async () => Result.ok({ ok: true }),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
makeTrail();
|
|
190
|
+
`;
|
|
191
|
+
|
|
192
|
+
// The trail's \`contours: [contours.user]\` refers to a local
|
|
193
|
+
// \`const contours = ...\`, not the namespace import, so no
|
|
194
|
+
// missing-contour diagnostic should be produced.
|
|
195
|
+
expect(
|
|
196
|
+
contourExists.checkWithContext(code, TEST_FILE, {
|
|
197
|
+
knownContourIds: new Set<string>(),
|
|
198
|
+
knownTrailIds: new Set(['user.create']),
|
|
199
|
+
})
|
|
200
|
+
).toEqual([]);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -25,6 +25,26 @@ const t = trail('onboard', {
|
|
|
25
25
|
expect(diagnostics.length).toBe(0);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
test('resolves batch ctx.cross() calls with string literals', () => {
|
|
29
|
+
const code = `
|
|
30
|
+
trail('onboard', {
|
|
31
|
+
crosses: ['entity.add', 'search'],
|
|
32
|
+
input: z.object({ name: z.string() }),
|
|
33
|
+
blaze: async (input, ctx) => {
|
|
34
|
+
await ctx.cross([
|
|
35
|
+
['entity.add', { name: input.name }],
|
|
36
|
+
['search', { query: input.name }],
|
|
37
|
+
]);
|
|
38
|
+
return Result.ok({});
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
44
|
+
|
|
45
|
+
expect(diagnostics.length).toBe(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
28
48
|
test('no crosses declaration and no ctx.cross() calls', () => {
|
|
29
49
|
const code = `
|
|
30
50
|
trail('simple', {
|
|
@@ -61,6 +81,28 @@ trail('onboard', {
|
|
|
61
81
|
expect(diagnostics[0]?.message).toContain("ctx.cross('entity.add')");
|
|
62
82
|
expect(diagnostics[0]?.message).toContain('not declared in crosses');
|
|
63
83
|
});
|
|
84
|
+
|
|
85
|
+
test('undeclared batch crossings still report an error', () => {
|
|
86
|
+
const code = `
|
|
87
|
+
trail('onboard', {
|
|
88
|
+
crosses: ['entity.add'],
|
|
89
|
+
input: z.object({ name: z.string() }),
|
|
90
|
+
blaze: async (input, ctx) => {
|
|
91
|
+
await ctx.cross([
|
|
92
|
+
['entity.add', { name: input.name }],
|
|
93
|
+
['search', { query: input.name }],
|
|
94
|
+
]);
|
|
95
|
+
return Result.ok({});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
101
|
+
|
|
102
|
+
expect(diagnostics.length).toBe(1);
|
|
103
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
104
|
+
expect(diagnostics[0]?.message).toContain("ctx.cross('search')");
|
|
105
|
+
});
|
|
64
106
|
});
|
|
65
107
|
|
|
66
108
|
describe('warn cases', () => {
|
|
@@ -176,6 +218,87 @@ trail('onboard', {
|
|
|
176
218
|
|
|
177
219
|
expect(diagnostics.length).toBe(0);
|
|
178
220
|
});
|
|
221
|
+
|
|
222
|
+
test('blaze with no second parameter: unrelated closure ctx.cross is not tracked', () => {
|
|
223
|
+
const code = `
|
|
224
|
+
import { trail, Result } from '@ontrails/core';
|
|
225
|
+
|
|
226
|
+
const ctx = { cross: () => ({}) };
|
|
227
|
+
|
|
228
|
+
trail('demo', {
|
|
229
|
+
blaze: async () => {
|
|
230
|
+
ctx.cross('entity.add');
|
|
231
|
+
return Result.ok({ ok: true });
|
|
232
|
+
},
|
|
233
|
+
crosses: [],
|
|
234
|
+
});
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
238
|
+
// The blaze has no context parameter, so `ctx` in the body is an
|
|
239
|
+
// unrelated closure-scoped binding, not the trail context. It must
|
|
240
|
+
// not be tracked — no diagnostics.
|
|
241
|
+
expect(diagnostics.length).toBe(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('blaze with no second parameter: unrelated closure context.cross is not tracked', () => {
|
|
245
|
+
const code = `
|
|
246
|
+
import { trail, Result } from '@ontrails/core';
|
|
247
|
+
|
|
248
|
+
const context = { cross: () => ({}) };
|
|
249
|
+
|
|
250
|
+
trail('demo', {
|
|
251
|
+
blaze: async () => {
|
|
252
|
+
context.cross('entity.add');
|
|
253
|
+
return Result.ok({ ok: true });
|
|
254
|
+
},
|
|
255
|
+
crosses: [],
|
|
256
|
+
});
|
|
257
|
+
`;
|
|
258
|
+
|
|
259
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
260
|
+
expect(diagnostics.length).toBe(0);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('real blaze ctx.cross to undeclared target is still flagged', () => {
|
|
264
|
+
const code = `
|
|
265
|
+
import { trail, Result } from '@ontrails/core';
|
|
266
|
+
|
|
267
|
+
trail('demo', {
|
|
268
|
+
blaze: async (_, ctx) => {
|
|
269
|
+
await ctx.cross('undeclared');
|
|
270
|
+
return Result.ok({ ok: true });
|
|
271
|
+
},
|
|
272
|
+
crosses: [],
|
|
273
|
+
});
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
277
|
+
expect(diagnostics.length).toBe(1);
|
|
278
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
279
|
+
expect(diagnostics[0]?.message).toContain("ctx.cross('undeclared')");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('defaulted context param is detected (AssignmentPattern)', () => {
|
|
283
|
+
const code = `
|
|
284
|
+
import { trail, Result } from '@ontrails/core';
|
|
285
|
+
|
|
286
|
+
const fallbackCtx = { cross: async () => Result.ok({}) };
|
|
287
|
+
|
|
288
|
+
trail('demo', {
|
|
289
|
+
blaze: async (_input, ctx = fallbackCtx) => {
|
|
290
|
+
await ctx.cross('undeclared');
|
|
291
|
+
return Result.ok({ ok: true });
|
|
292
|
+
},
|
|
293
|
+
crosses: [],
|
|
294
|
+
});
|
|
295
|
+
`;
|
|
296
|
+
|
|
297
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
298
|
+
expect(diagnostics.length).toBe(1);
|
|
299
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
300
|
+
expect(diagnostics[0]?.message).toContain("ctx.cross('undeclared')");
|
|
301
|
+
});
|
|
179
302
|
});
|
|
180
303
|
|
|
181
304
|
describe('nested run false positives', () => {
|
|
@@ -237,6 +360,128 @@ trail('onboard', {
|
|
|
237
360
|
});
|
|
238
361
|
});
|
|
239
362
|
|
|
363
|
+
describe('trail object references in crosses', () => {
|
|
364
|
+
test('unresolvable identifier in crosses softens undeclared to warn', () => {
|
|
365
|
+
const code = `
|
|
366
|
+
import { showGist } from '../gist/show';
|
|
367
|
+
trail('gist.fork', {
|
|
368
|
+
crosses: [showGist],
|
|
369
|
+
input: z.object({ id: z.string() }),
|
|
370
|
+
blaze: async (input, ctx) => {
|
|
371
|
+
await ctx.cross('gist.create', { id: input.id });
|
|
372
|
+
return Result.ok({});
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
378
|
+
|
|
379
|
+
// 'gist.create' called but can't prove showGist doesn't cover it
|
|
380
|
+
expect(diagnostics.length).toBe(1);
|
|
381
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
382
|
+
expect(diagnostics[0]?.message).toContain('trail object references');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('mixed string and trail object references: resolved string still validated', () => {
|
|
386
|
+
const code = `
|
|
387
|
+
import { showGist } from '../gist/show';
|
|
388
|
+
trail('gist.fork', {
|
|
389
|
+
crosses: ['gist.create', showGist],
|
|
390
|
+
input: z.object({ id: z.string() }),
|
|
391
|
+
blaze: async (input, ctx) => {
|
|
392
|
+
await ctx.cross('gist.create', { id: input.id });
|
|
393
|
+
return Result.ok({});
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
`;
|
|
397
|
+
|
|
398
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
399
|
+
|
|
400
|
+
// gist.create declared and called — clean. showGist unresolved but declared, not called by string — no unused warning for unresolved entries.
|
|
401
|
+
expect(diagnostics.length).toBe(0);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('trail object only in crosses with no string cross calls is clean', () => {
|
|
405
|
+
const code = `
|
|
406
|
+
import { showGist } from '../gist/show';
|
|
407
|
+
trail('gist.fork', {
|
|
408
|
+
crosses: [showGist],
|
|
409
|
+
input: z.object({ id: z.string() }),
|
|
410
|
+
blaze: async (input, ctx) => {
|
|
411
|
+
return Result.ok({});
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
`;
|
|
415
|
+
|
|
416
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
417
|
+
|
|
418
|
+
// No string-resolved IDs and no string cross calls — clean
|
|
419
|
+
expect(diagnostics.length).toBe(0);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('typed ctx.cross(trailObj) calls', () => {
|
|
424
|
+
test('typed cross call with trail object does not produce undeclared error', () => {
|
|
425
|
+
const code = `
|
|
426
|
+
import { showGist } from '../gist/show';
|
|
427
|
+
trail('gist.fork', {
|
|
428
|
+
crosses: [showGist],
|
|
429
|
+
input: z.object({ id: z.string() }),
|
|
430
|
+
blaze: async (input, ctx) => {
|
|
431
|
+
await ctx.cross(showGist, { id: input.id });
|
|
432
|
+
return Result.ok({});
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
`;
|
|
436
|
+
|
|
437
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
438
|
+
|
|
439
|
+
expect(diagnostics.length).toBe(0);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('typed cross call suppresses unused-declaration warning for matching entry', () => {
|
|
443
|
+
const code = `
|
|
444
|
+
import { showGist } from '../gist/show';
|
|
445
|
+
trail('gist.fork', {
|
|
446
|
+
crosses: ['gist.create', showGist],
|
|
447
|
+
input: z.object({ id: z.string() }),
|
|
448
|
+
blaze: async (input, ctx) => {
|
|
449
|
+
await ctx.cross('gist.create', { id: input.id });
|
|
450
|
+
await ctx.cross(showGist, { id: input.id });
|
|
451
|
+
return Result.ok({});
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
`;
|
|
455
|
+
|
|
456
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
457
|
+
|
|
458
|
+
// showGist is unresolvable in crosses but the typed cross call covers it
|
|
459
|
+
expect(diagnostics.length).toBe(0);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('undeclared string cross alongside typed cross still reports error (softened)', () => {
|
|
463
|
+
const code = `
|
|
464
|
+
import { showGist } from '../gist/show';
|
|
465
|
+
trail('gist.fork', {
|
|
466
|
+
crosses: [showGist],
|
|
467
|
+
input: z.object({ id: z.string() }),
|
|
468
|
+
blaze: async (input, ctx) => {
|
|
469
|
+
await ctx.cross(showGist, { id: input.id });
|
|
470
|
+
await ctx.cross('undeclared.trail', { id: input.id });
|
|
471
|
+
return Result.ok({});
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
`;
|
|
475
|
+
|
|
476
|
+
const diagnostics = crossDeclarations.check(code, TEST_FILE);
|
|
477
|
+
|
|
478
|
+
// 'undeclared.trail' not declared — softened because showGist is unresolvable
|
|
479
|
+
expect(diagnostics.length).toBe(1);
|
|
480
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
481
|
+
expect(diagnostics[0]?.message).toContain('undeclared.trail');
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
240
485
|
describe('edge cases', () => {
|
|
241
486
|
test('dynamic cross IDs are skipped', () => {
|
|
242
487
|
const code = `
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { deadInternalTrail } from '../rules/dead-internal-trail.js';
|
|
4
|
+
|
|
5
|
+
const TEST_FILE = 'entity.ts';
|
|
6
|
+
|
|
7
|
+
describe('dead-internal-trail', () => {
|
|
8
|
+
test('warns when an internal trail is never crossed and has no on: activation', () => {
|
|
9
|
+
const code = `
|
|
10
|
+
trail('entity.sync', {
|
|
11
|
+
visibility: 'internal',
|
|
12
|
+
blaze: async () => Result.ok({}),
|
|
13
|
+
});
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const diagnostics = deadInternalTrail.check(code, TEST_FILE);
|
|
17
|
+
|
|
18
|
+
expect(diagnostics).toHaveLength(1);
|
|
19
|
+
expect(diagnostics[0]?.rule).toBe('dead-internal-trail');
|
|
20
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
21
|
+
expect(diagnostics[0]?.message).toContain('entity.sync');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('stays quiet when another trail crosses the internal trail in the same file', () => {
|
|
25
|
+
const code = `
|
|
26
|
+
trail('entity.public', {
|
|
27
|
+
crosses: ['entity.sync'],
|
|
28
|
+
blaze: async (_input, ctx) => ctx.cross('entity.sync', {}),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
trail('entity.sync', {
|
|
32
|
+
visibility: 'internal',
|
|
33
|
+
blaze: async () => Result.ok({}),
|
|
34
|
+
});
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('stays quiet when project context marks the trail as crossed elsewhere', () => {
|
|
41
|
+
const code = `
|
|
42
|
+
trail('entity.sync', {
|
|
43
|
+
visibility: 'internal',
|
|
44
|
+
blaze: async () => Result.ok({}),
|
|
45
|
+
});
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const diagnostics = deadInternalTrail.checkWithContext(code, TEST_FILE, {
|
|
49
|
+
crossTargetTrailIds: new Set(['entity.sync']),
|
|
50
|
+
knownTrailIds: new Set(['entity.public', 'entity.sync']),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(diagnostics).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('stays quiet when the internal trail has on: activation', () => {
|
|
57
|
+
const code = `
|
|
58
|
+
trail('entity.audit', {
|
|
59
|
+
visibility: 'internal',
|
|
60
|
+
on: ['entity.created'],
|
|
61
|
+
blaze: async () => Result.ok({}),
|
|
62
|
+
});
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('stays quiet when on: is a module-level identifier reference', () => {
|
|
69
|
+
const code = `
|
|
70
|
+
const activationSignals = ['entity.created', 'entity.updated'];
|
|
71
|
+
|
|
72
|
+
trail('entity.audit', {
|
|
73
|
+
visibility: 'internal',
|
|
74
|
+
on: activationSignals,
|
|
75
|
+
blaze: async () => Result.ok({}),
|
|
76
|
+
});
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
expect(deadInternalTrail.check(code, TEST_FILE)).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { draftFileMarking } from '../rules/draft-file-marking.js';
|
|
6
|
+
import { draftVisibleDebt } from '../rules/draft-visible-debt.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Absolute paths to the two real framework files that define the draft-prefix
|
|
10
|
+
* constants. The exemption in `collectFrameworkDraftPrefixConstantOffsets` is
|
|
11
|
+
* keyed on absolute-path equality against these two paths.
|
|
12
|
+
*/
|
|
13
|
+
const CORE_DRAFT_PATH = resolve(
|
|
14
|
+
fileURLToPath(new URL('../../../core/src/draft.ts', import.meta.url))
|
|
15
|
+
);
|
|
16
|
+
const WARDEN_DRAFT_PATH = resolve(
|
|
17
|
+
fileURLToPath(new URL('../draft.ts', import.meta.url))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/** Any file outside the two framework files — exemption must NOT apply here. */
|
|
21
|
+
const NORMAL_FILE = 'packages/example/src/ordinary.ts';
|
|
22
|
+
|
|
23
|
+
describe('draft-file-marking context-awareness', () => {
|
|
24
|
+
test('ignores framework DRAFT_ID_PREFIX in packages/core/src/draft.ts', () => {
|
|
25
|
+
const code = `export const DRAFT_ID_PREFIX = '_draft.';\n`;
|
|
26
|
+
expect(draftFileMarking.check(code, CORE_DRAFT_PATH)).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('ignores framework DRAFT_FILE_PREFIX in packages/warden/src/draft.ts', () => {
|
|
30
|
+
const code = `export const DRAFT_FILE_PREFIX = '_draft.';\n`;
|
|
31
|
+
expect(draftFileMarking.check(code, WARDEN_DRAFT_PATH)).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('fires when DRAFT_ID_PREFIX is reused in a non-framework file (false-negative closed)', () => {
|
|
35
|
+
// Before TRL-334 follow-up: identifier-name-only match silently suppressed
|
|
36
|
+
// this as "framework declaration". After: the path gate rejects the
|
|
37
|
+
// consumer file and the leak fires.
|
|
38
|
+
const code = `const DRAFT_ID_PREFIX = '_draft.user-leak';\n`;
|
|
39
|
+
const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
|
|
40
|
+
expect(diagnostics.length).toBe(1);
|
|
41
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('fires when DRAFT_FILE_PREFIX is reused in a non-framework file', () => {
|
|
45
|
+
const code = `const DRAFT_FILE_PREFIX = '_draft.other-leak';\n`;
|
|
46
|
+
const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
|
|
47
|
+
expect(diagnostics.length).toBe(1);
|
|
48
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('fires when framework file declares the constant with a non-_draft. value (value invariant)', () => {
|
|
52
|
+
// Same file path, same identifier, but wrong literal — exemption must
|
|
53
|
+
// require the exact '_draft.' value.
|
|
54
|
+
const code = `export const DRAFT_ID_PREFIX = '_draft.something-else';\n`;
|
|
55
|
+
const diagnostics = draftFileMarking.check(code, CORE_DRAFT_PATH);
|
|
56
|
+
expect(diagnostics.length).toBe(1);
|
|
57
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('still flags draft ids in arbitrary const declarations', () => {
|
|
61
|
+
const code = `const something = '_draft.foo';\n`;
|
|
62
|
+
const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
|
|
63
|
+
expect(diagnostics.length).toBe(1);
|
|
64
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('warden-ignore-next-line pragma suppresses diagnostic', () => {
|
|
68
|
+
const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
|
|
69
|
+
expect(draftFileMarking.check(code, NORMAL_FILE)).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('pragma with trailing whitespace still suppresses diagnostic', () => {
|
|
73
|
+
const code = `// warden-ignore-next-line \nconst x = '_draft.intentional';\n`;
|
|
74
|
+
expect(draftFileMarking.check(code, NORMAL_FILE)).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('pragma with blank line between does not suppress', () => {
|
|
78
|
+
const code = `// warden-ignore-next-line\n\nconst x = '_draft.intentional';\n`;
|
|
79
|
+
const diagnostics = draftFileMarking.check(code, NORMAL_FILE);
|
|
80
|
+
expect(diagnostics.length).toBe(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('does not falsely flag "marked without ids" when all draft ids are pragma-suppressed', () => {
|
|
84
|
+
// In a draft-marked file, pragma-suppressed draft ids still justify the
|
|
85
|
+
// filename marker — the user intentionally silenced them, not removed
|
|
86
|
+
// them. The "marked without ids" cleanup nudge must not fire.
|
|
87
|
+
const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
|
|
88
|
+
const diagnostics = draftFileMarking.check(
|
|
89
|
+
code,
|
|
90
|
+
'packages/example/src/thing._draft.ts'
|
|
91
|
+
);
|
|
92
|
+
expect(diagnostics).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('draft-visible-debt context-awareness', () => {
|
|
97
|
+
test('ignores framework DRAFT_ID_PREFIX in packages/core/src/draft.ts', () => {
|
|
98
|
+
const code = `export const DRAFT_ID_PREFIX = '_draft.';\n`;
|
|
99
|
+
expect(draftVisibleDebt.check(code, CORE_DRAFT_PATH)).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('ignores framework DRAFT_FILE_PREFIX in packages/warden/src/draft.ts', () => {
|
|
103
|
+
const code = `export const DRAFT_FILE_PREFIX = '_draft.';\n`;
|
|
104
|
+
expect(draftVisibleDebt.check(code, WARDEN_DRAFT_PATH)).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('fires when DRAFT_ID_PREFIX is reused in a non-framework file (false-negative closed)', () => {
|
|
108
|
+
const code = `const DRAFT_ID_PREFIX = '_draft.user-leak';\n`;
|
|
109
|
+
const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
|
|
110
|
+
expect(diagnostics.length).toBe(1);
|
|
111
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('fires when DRAFT_FILE_PREFIX is reused in a non-framework file', () => {
|
|
115
|
+
const code = `const DRAFT_FILE_PREFIX = '_draft.other-leak';\n`;
|
|
116
|
+
const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
|
|
117
|
+
expect(diagnostics.length).toBe(1);
|
|
118
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('fires when framework file declares the constant with a non-_draft. value (value invariant)', () => {
|
|
122
|
+
const code = `export const DRAFT_ID_PREFIX = '_draft.something-else';\n`;
|
|
123
|
+
const diagnostics = draftVisibleDebt.check(code, CORE_DRAFT_PATH);
|
|
124
|
+
expect(diagnostics.length).toBe(1);
|
|
125
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('still flags draft ids in arbitrary const declarations', () => {
|
|
129
|
+
const code = `const something = '_draft.foo';\n`;
|
|
130
|
+
const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
|
|
131
|
+
expect(diagnostics.length).toBe(1);
|
|
132
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('warden-ignore-next-line pragma suppresses diagnostic', () => {
|
|
136
|
+
const code = `// warden-ignore-next-line\nconst x = '_draft.intentional';\n`;
|
|
137
|
+
expect(draftVisibleDebt.check(code, '_draft.something.ts')).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('pragma with trailing whitespace still suppresses diagnostic', () => {
|
|
141
|
+
const code = `// warden-ignore-next-line \nconst x = '_draft.intentional';\n`;
|
|
142
|
+
expect(draftVisibleDebt.check(code, '_draft.something.ts')).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('pragma with blank line between does not suppress', () => {
|
|
146
|
+
const code = `// warden-ignore-next-line\n\nconst x = '_draft.intentional';\n`;
|
|
147
|
+
const diagnostics = draftVisibleDebt.check(code, '_draft.something.ts');
|
|
148
|
+
expect(diagnostics.length).toBe(1);
|
|
149
|
+
});
|
|
150
|
+
});
|