@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,652 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { firesDeclarations } from '../rules/fires-declarations.js';
|
|
4
|
+
|
|
5
|
+
const TEST_FILE = 'test.ts';
|
|
6
|
+
|
|
7
|
+
describe('fires-declarations', () => {
|
|
8
|
+
describe('clean cases', () => {
|
|
9
|
+
test('declared and called match exactly', () => {
|
|
10
|
+
const code = `
|
|
11
|
+
import { trail, Result } from '@ontrails/core';
|
|
12
|
+
const t = trail('onboard', {
|
|
13
|
+
fires: ['entity.created', 'audit.logged'],
|
|
14
|
+
input: z.object({ name: z.string() }),
|
|
15
|
+
blaze: async (input, ctx) => {
|
|
16
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
17
|
+
await ctx.fire('audit.logged', { actor: input.name });
|
|
18
|
+
return Result.ok({});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
24
|
+
|
|
25
|
+
expect(diagnostics.length).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('optional-chained ctx.fire?.() call matching declaration is clean', () => {
|
|
29
|
+
const code = `
|
|
30
|
+
trail('optionalChain', {
|
|
31
|
+
fires: ['declared.signal'],
|
|
32
|
+
input: z.object({ name: z.string() }),
|
|
33
|
+
blaze: async (input, ctx) => {
|
|
34
|
+
await ctx.fire?.('declared.signal', { name: input.name });
|
|
35
|
+
return Result.ok({});
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
41
|
+
// Optional-chain invocation of ctx.fire must not produce false positives.
|
|
42
|
+
// Locks in current behavior so the dogfood demo's `ctx.fire?.(...)` stays
|
|
43
|
+
// clean regardless of future AST walker changes.
|
|
44
|
+
expect(diagnostics.length).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('no fires declaration and no ctx.fire() calls', () => {
|
|
48
|
+
const code = `
|
|
49
|
+
trail('simple', {
|
|
50
|
+
input: z.object({ name: z.string() }),
|
|
51
|
+
blaze: async (input, ctx) => {
|
|
52
|
+
return Result.ok({ greeting: 'hello ' + input.name });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
58
|
+
|
|
59
|
+
expect(diagnostics.length).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('error cases', () => {
|
|
64
|
+
test('called but not declared produces error', () => {
|
|
65
|
+
const code = `
|
|
66
|
+
trail('onboard', {
|
|
67
|
+
input: z.object({ name: z.string() }),
|
|
68
|
+
blaze: async (input, ctx) => {
|
|
69
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
70
|
+
return Result.ok({});
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
76
|
+
|
|
77
|
+
expect(diagnostics.length).toBe(1);
|
|
78
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
79
|
+
expect(diagnostics[0]?.rule).toBe('fires-declarations');
|
|
80
|
+
expect(diagnostics[0]?.message).toContain("ctx.fire('entity.created')");
|
|
81
|
+
expect(diagnostics[0]?.message).toContain('not declared in fires');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('warn cases', () => {
|
|
86
|
+
test('declared but not called produces warning', () => {
|
|
87
|
+
const code = `
|
|
88
|
+
trail('onboard', {
|
|
89
|
+
fires: ['entity.created', 'audit.logged'],
|
|
90
|
+
blaze: async (input, ctx) => {
|
|
91
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
92
|
+
return Result.ok({});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
98
|
+
|
|
99
|
+
expect(diagnostics.length).toBe(1);
|
|
100
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
101
|
+
expect(diagnostics[0]?.rule).toBe('fires-declarations');
|
|
102
|
+
expect(diagnostics[0]?.message).toContain(
|
|
103
|
+
"'audit.logged' declared in fires"
|
|
104
|
+
);
|
|
105
|
+
expect(diagnostics[0]?.message).toContain('never called');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('single-object overload', () => {
|
|
110
|
+
test('recognizes trail({ id, fires, blaze }) form', () => {
|
|
111
|
+
const code = `
|
|
112
|
+
trail({
|
|
113
|
+
id: 'onboard',
|
|
114
|
+
fires: ['entity.created'],
|
|
115
|
+
input: z.object({ name: z.string() }),
|
|
116
|
+
blaze: async (input, ctx) => {
|
|
117
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
118
|
+
return Result.ok({});
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
124
|
+
|
|
125
|
+
expect(diagnostics.length).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('detects undeclared fires in single-object form', () => {
|
|
129
|
+
const code = `
|
|
130
|
+
trail({
|
|
131
|
+
id: 'onboard',
|
|
132
|
+
input: z.object({ name: z.string() }),
|
|
133
|
+
blaze: async (input, ctx) => {
|
|
134
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
135
|
+
return Result.ok({});
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
141
|
+
|
|
142
|
+
expect(diagnostics.length).toBe(1);
|
|
143
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
144
|
+
expect(diagnostics[0]?.message).toContain("'entity.created'");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('context parameter naming', () => {
|
|
149
|
+
test('recognizes context.fire() when second param is named context', () => {
|
|
150
|
+
const code = `
|
|
151
|
+
trail('onboard', {
|
|
152
|
+
fires: ['entity.created'],
|
|
153
|
+
input: z.object({ name: z.string() }),
|
|
154
|
+
blaze: async (input, context) => {
|
|
155
|
+
await context.fire('entity.created', { name: input.name });
|
|
156
|
+
return Result.ok({});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
162
|
+
|
|
163
|
+
expect(diagnostics.length).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('blaze with no second parameter: unrelated closure ctx.fire is not tracked', () => {
|
|
167
|
+
const code = `
|
|
168
|
+
const ctx = { fire: (_: string) => {} };
|
|
169
|
+
trail('noCtxParam', {
|
|
170
|
+
blaze: async (input) => {
|
|
171
|
+
ctx.fire('closure.signal');
|
|
172
|
+
return Result.ok({});
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
178
|
+
// The blaze has no context parameter, so `ctx` in the body refers to
|
|
179
|
+
// some unrelated closure identifier, not a trail context. It must not
|
|
180
|
+
// be tracked — no diagnostics.
|
|
181
|
+
expect(diagnostics.length).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('custom-named context param: only that name is tracked', () => {
|
|
185
|
+
const code = `
|
|
186
|
+
const ctx = { fire: (_: string) => {} };
|
|
187
|
+
trail('customCtx', {
|
|
188
|
+
fires: ['declared.id'],
|
|
189
|
+
blaze: async (input, c) => {
|
|
190
|
+
await c.fire('declared.id', {});
|
|
191
|
+
ctx.fire('whatever');
|
|
192
|
+
return Result.ok({});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
198
|
+
// `c.fire('declared.id')` matches the declaration. The unrelated
|
|
199
|
+
// closure `ctx.fire('whatever')` must not be flagged because `ctx` is
|
|
200
|
+
// not the trail context — only `c` is.
|
|
201
|
+
expect(diagnostics.length).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('custom-named context param: undeclared call via that name is flagged', () => {
|
|
205
|
+
const code = `
|
|
206
|
+
trail('customCtxUndeclared', {
|
|
207
|
+
fires: ['declared.id'],
|
|
208
|
+
blaze: async (input, c) => {
|
|
209
|
+
await c.fire('undeclared.id', {});
|
|
210
|
+
return Result.ok({});
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
216
|
+
const undeclared = diagnostics.filter((d) =>
|
|
217
|
+
d.message.includes("'undeclared.id'")
|
|
218
|
+
);
|
|
219
|
+
expect(undeclared.length).toBe(1);
|
|
220
|
+
expect(undeclared[0]?.severity).toBe('error');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('recognizes destructured fire() calls', () => {
|
|
224
|
+
const code = `
|
|
225
|
+
trail('onboard', {
|
|
226
|
+
fires: ['entity.created'],
|
|
227
|
+
input: z.object({ name: z.string() }),
|
|
228
|
+
blaze: async (input, ctx) => {
|
|
229
|
+
const { fire } = ctx;
|
|
230
|
+
await fire('entity.created', { name: input.name });
|
|
231
|
+
return Result.ok({});
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
`;
|
|
235
|
+
|
|
236
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
237
|
+
|
|
238
|
+
expect(diagnostics.length).toBe(0);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('nested run false positives', () => {
|
|
243
|
+
test('meta.blaze with phantom fire does not trigger false positives', () => {
|
|
244
|
+
const code = `
|
|
245
|
+
trail('onboard', {
|
|
246
|
+
fires: ['entity.created'],
|
|
247
|
+
input: z.object({ name: z.string() }),
|
|
248
|
+
meta: { blaze: async () => ctx.fire('phantom') },
|
|
249
|
+
blaze: async (input, ctx) => {
|
|
250
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
251
|
+
return Result.ok({});
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
257
|
+
|
|
258
|
+
expect(diagnostics.length).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('identifier resolution in fires arrays', () => {
|
|
263
|
+
test('resolves const identifiers in fires array', () => {
|
|
264
|
+
const code = `
|
|
265
|
+
const ENTITY_CREATED = 'entity.created';
|
|
266
|
+
trail('onboard', {
|
|
267
|
+
fires: [ENTITY_CREATED],
|
|
268
|
+
input: z.object({ name: z.string() }),
|
|
269
|
+
blaze: async (input, ctx) => {
|
|
270
|
+
await ctx.fire('entity.created', { name: input.name });
|
|
271
|
+
return Result.ok({});
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
277
|
+
|
|
278
|
+
expect(diagnostics.length).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('edge cases', () => {
|
|
283
|
+
test('dynamic fire IDs are skipped', () => {
|
|
284
|
+
const code = `
|
|
285
|
+
trail('dispatch', {
|
|
286
|
+
fires: ['entity.created'],
|
|
287
|
+
blaze: async (input, ctx) => {
|
|
288
|
+
const signalId = input.target;
|
|
289
|
+
await ctx.fire(signalId, input);
|
|
290
|
+
await ctx.fire('entity.created', input);
|
|
291
|
+
return Result.ok({});
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
`;
|
|
295
|
+
|
|
296
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
297
|
+
|
|
298
|
+
expect(diagnostics.length).toBe(0);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('multiple trails in one file are validated independently', () => {
|
|
302
|
+
const code = `
|
|
303
|
+
trail('alpha', {
|
|
304
|
+
fires: ['shared.signal'],
|
|
305
|
+
blaze: async (input, ctx) => {
|
|
306
|
+
await ctx.fire('shared.signal', input);
|
|
307
|
+
return Result.ok({});
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
trail('beta', {
|
|
312
|
+
blaze: async (input, ctx) => {
|
|
313
|
+
await ctx.fire('undeclared.signal', input);
|
|
314
|
+
return Result.ok({});
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
`;
|
|
318
|
+
|
|
319
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
320
|
+
|
|
321
|
+
expect(diagnostics.length).toBe(1);
|
|
322
|
+
expect(diagnostics[0]?.message).toContain('Trail "beta"');
|
|
323
|
+
expect(diagnostics[0]?.message).toContain("'undeclared.signal'");
|
|
324
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('skips test files', () => {
|
|
328
|
+
const code = `
|
|
329
|
+
trail('onboard', {
|
|
330
|
+
blaze: async (input, ctx) => {
|
|
331
|
+
await ctx.fire('entity.created', input);
|
|
332
|
+
return Result.ok({});
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
`;
|
|
336
|
+
|
|
337
|
+
const diagnostics = firesDeclarations.check(
|
|
338
|
+
code,
|
|
339
|
+
'src/__tests__/trails.test.ts'
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
expect(diagnostics.length).toBe(0);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('both wrong: called and undeclared, declared and unused', () => {
|
|
346
|
+
const code = `
|
|
347
|
+
trail('mixed', {
|
|
348
|
+
fires: ['declared.only'],
|
|
349
|
+
blaze: async (input, ctx) => {
|
|
350
|
+
await ctx.fire('called.only', input);
|
|
351
|
+
return Result.ok({});
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
`;
|
|
355
|
+
|
|
356
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
357
|
+
|
|
358
|
+
expect(diagnostics.length).toBe(2);
|
|
359
|
+
const errors = diagnostics.filter((d) => d.severity === 'error');
|
|
360
|
+
const warns = diagnostics.filter((d) => d.severity === 'warn');
|
|
361
|
+
expect(errors.length).toBe(1);
|
|
362
|
+
expect(warns.length).toBe(1);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('bare fire() helpers', () => {
|
|
367
|
+
test('unrelated local fire() helper is not flagged', () => {
|
|
368
|
+
const code = `
|
|
369
|
+
import { trail, Result } from '@ontrails/core';
|
|
370
|
+
const fire = (x: number) => x * 2;
|
|
371
|
+
const t = trail('calc', {
|
|
372
|
+
input: z.object({ n: z.number() }),
|
|
373
|
+
blaze: async (input, ctx) => {
|
|
374
|
+
const doubled = fire(input.n);
|
|
375
|
+
return Result.ok({ doubled });
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
`;
|
|
379
|
+
|
|
380
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
381
|
+
|
|
382
|
+
// Neither declared nor "called" from the trail's perspective.
|
|
383
|
+
expect(diagnostics.length).toBe(0);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('destructured { fire } from ctx is tracked', () => {
|
|
387
|
+
const code = `
|
|
388
|
+
trail('onboard', {
|
|
389
|
+
fires: ['entity.created'],
|
|
390
|
+
blaze: async (input, ctx) => {
|
|
391
|
+
const { fire } = ctx;
|
|
392
|
+
await fire('entity.created', { name: input.name });
|
|
393
|
+
return Result.ok({});
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
`;
|
|
397
|
+
|
|
398
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
399
|
+
expect(diagnostics.length).toBe(0);
|
|
400
|
+
});
|
|
401
|
+
test('nested-scope destructure does not leak to outer blaze scope', () => {
|
|
402
|
+
const code = `
|
|
403
|
+
trail('outer', {
|
|
404
|
+
blaze: async (input, ctx) => {
|
|
405
|
+
function nested() {
|
|
406
|
+
const { fire } = ctx;
|
|
407
|
+
return fire;
|
|
408
|
+
}
|
|
409
|
+
fire('outer.signal');
|
|
410
|
+
return Result.ok({});
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
`;
|
|
414
|
+
|
|
415
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
416
|
+
// Top-level fire('outer.signal') should NOT be treated as a ctx call —
|
|
417
|
+
// the nested destructure must not leak out.
|
|
418
|
+
expect(diagnostics.length).toBe(0);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('top-level destructure is still tracked (regression check)', () => {
|
|
422
|
+
const code = `
|
|
423
|
+
trail('tracked', {
|
|
424
|
+
blaze: async (input, ctx) => {
|
|
425
|
+
const { fire } = ctx;
|
|
426
|
+
await fire('undeclared.signal', {});
|
|
427
|
+
return Result.ok({});
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
`;
|
|
431
|
+
|
|
432
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
433
|
+
expect(diagnostics.length).toBe(1);
|
|
434
|
+
expect(diagnostics[0]?.severity).toBe('error');
|
|
435
|
+
expect(diagnostics[0]?.message).toContain("'undeclared.signal'");
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('nested function parameter shadowing fire is not flagged', () => {
|
|
439
|
+
const code = `
|
|
440
|
+
trail('shadowFire', {
|
|
441
|
+
blaze: async (_, ctx) => {
|
|
442
|
+
const { fire } = ctx;
|
|
443
|
+
function nested(fire) {
|
|
444
|
+
fire('shadow.only');
|
|
445
|
+
}
|
|
446
|
+
nested(() => {});
|
|
447
|
+
return Result.ok({});
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
`;
|
|
451
|
+
|
|
452
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
453
|
+
// The nested parameter shadows the outer destructured `fire`, so the
|
|
454
|
+
// call inside `nested` must not be treated as a trail fire.
|
|
455
|
+
expect(diagnostics.length).toBe(0);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('nested function parameter shadowing ctx is not flagged', () => {
|
|
459
|
+
const code = `
|
|
460
|
+
trail('shadowCtx', {
|
|
461
|
+
blaze: async (_, ctx) => {
|
|
462
|
+
function nested(ctx) {
|
|
463
|
+
ctx.fire('shadow.ctx');
|
|
464
|
+
}
|
|
465
|
+
nested({});
|
|
466
|
+
return Result.ok({});
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
`;
|
|
470
|
+
|
|
471
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
472
|
+
expect(diagnostics.length).toBe(0);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test('top-level ctx.fire with matching declaration still clean (regression)', () => {
|
|
476
|
+
const code = `
|
|
477
|
+
trail('regression', {
|
|
478
|
+
fires: ['declared.signal'],
|
|
479
|
+
blaze: async (input, ctx) => {
|
|
480
|
+
await ctx.fire('declared.signal', {});
|
|
481
|
+
return Result.ok({});
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
487
|
+
expect(diagnostics.length).toBe(0);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('let { fire: emit } = ctx is not tracked (precision tradeoff)', () => {
|
|
491
|
+
const code = `
|
|
492
|
+
trail('letDestructure', {
|
|
493
|
+
blaze: async (input, ctx) => {
|
|
494
|
+
let { fire: emit } = ctx;
|
|
495
|
+
emit('some.id');
|
|
496
|
+
return Result.ok({});
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
`;
|
|
500
|
+
|
|
501
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
502
|
+
// Only `const` destructures are tracked. `let` / `var` allow
|
|
503
|
+
// reassignment which this flow-insensitive walker cannot follow, so
|
|
504
|
+
// `emit` is treated as unrelated and no diagnostic is produced.
|
|
505
|
+
expect(diagnostics.length).toBe(0);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test('destructured { fire: emit } alias from ctx is tracked', () => {
|
|
509
|
+
const code = `
|
|
510
|
+
trail('onboard', {
|
|
511
|
+
fires: ['entity.created'],
|
|
512
|
+
blaze: async (input, ctx) => {
|
|
513
|
+
const { fire: emit } = ctx;
|
|
514
|
+
await emit('entity.created', { name: input.name });
|
|
515
|
+
return Result.ok({});
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
521
|
+
expect(diagnostics.length).toBe(0);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe('object-form fires declarations', () => {
|
|
526
|
+
test('Signal value reference downgrades undeclared to warn', () => {
|
|
527
|
+
const code = `
|
|
528
|
+
import { trail, signal, Result } from '@ontrails/core';
|
|
529
|
+
const orderPlaced = signal('order.placed', { payload: z.object({}) });
|
|
530
|
+
trail('checkout', {
|
|
531
|
+
fires: [orderPlaced],
|
|
532
|
+
blaze: async (input, ctx) => {
|
|
533
|
+
await ctx.fire('order.placed', {});
|
|
534
|
+
return Result.ok({});
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
`;
|
|
538
|
+
|
|
539
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
540
|
+
// Object-form makes the declared set unresolvable — downgrade undeclared
|
|
541
|
+
// to warn rather than silently suppressing; runtime normalization in
|
|
542
|
+
// trail() may cover the call.
|
|
543
|
+
expect(diagnostics.length).toBe(1);
|
|
544
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
545
|
+
expect(diagnostics[0]?.message).toContain('object-form fires entries');
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('genuinely undeclared string call alongside Signal value is downgraded to warn', () => {
|
|
549
|
+
const code = `
|
|
550
|
+
import { trail, signal, Result } from '@ontrails/core';
|
|
551
|
+
const orderPlaced = signal('order.placed', { payload: z.object({}) });
|
|
552
|
+
trail('checkout', {
|
|
553
|
+
fires: [orderPlaced],
|
|
554
|
+
blaze: async (input, ctx) => {
|
|
555
|
+
await ctx.fire('audit.logged', {});
|
|
556
|
+
return Result.ok({});
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
`;
|
|
560
|
+
|
|
561
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
562
|
+
// Cannot statically prove 'audit.logged' isn't covered by the object-form
|
|
563
|
+
// entry, but also cannot stay silent — warn with disclaimer.
|
|
564
|
+
const undeclared = diagnostics.filter((d) =>
|
|
565
|
+
d.message.includes("'audit.logged'")
|
|
566
|
+
);
|
|
567
|
+
expect(undeclared.length).toBe(1);
|
|
568
|
+
expect(undeclared[0]?.severity).toBe('warn');
|
|
569
|
+
expect(undeclared[0]?.message).toContain(
|
|
570
|
+
'may be declared via object-form fires entries'
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('mixed string + Signal value — resolved string still matches, unresolved warns', () => {
|
|
575
|
+
const code = `
|
|
576
|
+
import { trail, signal, Result } from '@ontrails/core';
|
|
577
|
+
const orderPlaced = signal('order.placed', { payload: z.object({}) });
|
|
578
|
+
trail('checkout', {
|
|
579
|
+
fires: ['audit.logged', orderPlaced],
|
|
580
|
+
blaze: async (input, ctx) => {
|
|
581
|
+
await ctx.fire('order.placed', {});
|
|
582
|
+
await ctx.fire('audit.logged', {});
|
|
583
|
+
return Result.ok({});
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
`;
|
|
587
|
+
|
|
588
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
589
|
+
// 'audit.logged' resolves from the string literal; 'order.placed' can't
|
|
590
|
+
// be resolved statically so it downgrades to warn.
|
|
591
|
+
expect(diagnostics.length).toBe(1);
|
|
592
|
+
expect(diagnostics[0]?.severity).toBe('warn');
|
|
593
|
+
expect(diagnostics[0]?.message).toContain("'order.placed'");
|
|
594
|
+
expect(diagnostics[0]?.message).toContain('object-form fires entries');
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
describe('fires-declarations helper-scoped blind spots', () => {
|
|
600
|
+
test('helper-scoped ctx.fire stays a documented unused-only blind spot', () => {
|
|
601
|
+
const code = `
|
|
602
|
+
trail('nestedLegit', {
|
|
603
|
+
fires: ['legitimate.signal'],
|
|
604
|
+
blaze: async (input, ctx) => {
|
|
605
|
+
const runLater = () => ctx.fire('legitimate.signal', {});
|
|
606
|
+
runLater();
|
|
607
|
+
return Result.ok({});
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
`;
|
|
611
|
+
|
|
612
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
613
|
+
// Precision tradeoff: helper-scoped ctx.fire isn't walked yet, so the
|
|
614
|
+
// rule sees the declared signal as "unused" today. What matters is that
|
|
615
|
+
// it is NOT reported as undeclared, and that the limitation stays
|
|
616
|
+
// stable until helper-aware analysis lands.
|
|
617
|
+
const undeclared = diagnostics.filter((d) =>
|
|
618
|
+
d.message.includes('not declared in fires')
|
|
619
|
+
);
|
|
620
|
+
const unused = diagnostics.filter((d) =>
|
|
621
|
+
d.message.includes("'legitimate.signal' declared in fires")
|
|
622
|
+
);
|
|
623
|
+
expect(undeclared.length).toBe(0);
|
|
624
|
+
expect(unused.length).toBe(1);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test('helper-local destructured fire stays a documented unused-only blind spot', () => {
|
|
628
|
+
const code = `
|
|
629
|
+
trail('nestedDestructure', {
|
|
630
|
+
fires: ['helper.signal'],
|
|
631
|
+
blaze: async (input, ctx) => {
|
|
632
|
+
const runLater = () => {
|
|
633
|
+
const { fire } = ctx;
|
|
634
|
+
return fire('helper.signal', {});
|
|
635
|
+
};
|
|
636
|
+
runLater();
|
|
637
|
+
return Result.ok({});
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
`;
|
|
641
|
+
|
|
642
|
+
const diagnostics = firesDeclarations.check(code, TEST_FILE);
|
|
643
|
+
const undeclared = diagnostics.filter((d) =>
|
|
644
|
+
d.message.includes('not declared in fires')
|
|
645
|
+
);
|
|
646
|
+
const unused = diagnostics.filter((d) =>
|
|
647
|
+
d.message.includes("'helper.signal' declared in fires")
|
|
648
|
+
);
|
|
649
|
+
expect(undeclared.length).toBe(0);
|
|
650
|
+
expect(unused.length).toBe(1);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
@@ -67,7 +67,7 @@ describe('formatGitHubAnnotations', () => {
|
|
|
67
67
|
|
|
68
68
|
test('emits drift as a single ::error annotation', () => {
|
|
69
69
|
const output = formatGitHubAnnotations(reportWithDrift);
|
|
70
|
-
expect(output).toContain('::error::drift:
|
|
70
|
+
expect(output).toContain('::error::drift: trails.lock is stale');
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test('produces one line per diagnostic', () => {
|
|
@@ -148,7 +148,7 @@ describe('formatSummary', () => {
|
|
|
148
148
|
test('includes drift section when stale', () => {
|
|
149
149
|
const output = formatSummary(reportWithDrift);
|
|
150
150
|
expect(output).toContain('### Drift');
|
|
151
|
-
expect(output).toContain('
|
|
151
|
+
expect(output).toContain('trails.lock is stale');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
test('omits drift section when clean', () => {
|