@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,613 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
__collectFrameworkNamespaceBindingsForTest,
|
|
5
|
+
collectContourDefinitionIds,
|
|
6
|
+
collectContourReferenceSites,
|
|
7
|
+
collectNamedContourIds,
|
|
8
|
+
__getTrailCalleeNameForTest,
|
|
9
|
+
deriveContourIdentifierName,
|
|
10
|
+
findContourDefinitions,
|
|
11
|
+
findTrailDefinitions,
|
|
12
|
+
hasIgnoreCommentOnLine,
|
|
13
|
+
parse,
|
|
14
|
+
splitSourceLines,
|
|
15
|
+
} from '../rules/ast.js';
|
|
16
|
+
|
|
17
|
+
describe('deriveContourIdentifierName', () => {
|
|
18
|
+
test('supports the common *Contour binding suffix when resolving known contours', () => {
|
|
19
|
+
expect(
|
|
20
|
+
deriveContourIdentifierName(
|
|
21
|
+
'userContour',
|
|
22
|
+
new Map<string, string>(),
|
|
23
|
+
new Set(['user'])
|
|
24
|
+
)
|
|
25
|
+
).toBe('user');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('prefers exact contour ids over the *Contour fallback', () => {
|
|
29
|
+
expect(
|
|
30
|
+
deriveContourIdentifierName(
|
|
31
|
+
'userContour',
|
|
32
|
+
new Map<string, string>(),
|
|
33
|
+
new Set(['user', 'userContour'])
|
|
34
|
+
)
|
|
35
|
+
).toBe('userContour');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('hasIgnoreCommentOnLine', () => {
|
|
40
|
+
test('matches the pragma when the preceding line is exact', () => {
|
|
41
|
+
const lines = splitSourceLines(
|
|
42
|
+
"// warden-ignore-next-line\nconst x = '_draft.foo';\n"
|
|
43
|
+
);
|
|
44
|
+
expect(hasIgnoreCommentOnLine(lines, 2)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('matches the pragma with leading whitespace', () => {
|
|
48
|
+
const lines = splitSourceLines(
|
|
49
|
+
" // warden-ignore-next-line\n const x = '_draft.foo';\n"
|
|
50
|
+
);
|
|
51
|
+
expect(hasIgnoreCommentOnLine(lines, 2)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('matches the pragma with trailing whitespace (editor did not auto-trim)', () => {
|
|
55
|
+
const lines = splitSourceLines(
|
|
56
|
+
"// warden-ignore-next-line \nconst x = '_draft.foo';\n"
|
|
57
|
+
);
|
|
58
|
+
expect(hasIgnoreCommentOnLine(lines, 2)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('returns false when there is no preceding line', () => {
|
|
62
|
+
const lines = splitSourceLines("const x = '_draft.foo';\n");
|
|
63
|
+
expect(hasIgnoreCommentOnLine(lines, 1)).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('returns false when the preceding line is blank', () => {
|
|
67
|
+
const lines = splitSourceLines(
|
|
68
|
+
"// warden-ignore-next-line\n\nconst x = '_draft.foo';\n"
|
|
69
|
+
);
|
|
70
|
+
expect(hasIgnoreCommentOnLine(lines, 3)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('accepts pre-split lines so callers memoize across many matches', () => {
|
|
74
|
+
// Regression guard for the O(N × source length) re-split fix. The caller
|
|
75
|
+
// splits once and threads the same lines array through to every lookup.
|
|
76
|
+
const source = Array.from(
|
|
77
|
+
{ length: 100 },
|
|
78
|
+
(_, i) => `const v${i} = '_draft.id${i}';`
|
|
79
|
+
).join('\n');
|
|
80
|
+
const lines = splitSourceLines(source);
|
|
81
|
+
for (let line = 1; line <= 100; line += 1) {
|
|
82
|
+
expect(hasIgnoreCommentOnLine(lines, line)).toBe(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const parseOrThrow = (source: string) =>
|
|
88
|
+
parse('test.ts', source) ??
|
|
89
|
+
(() => {
|
|
90
|
+
throw new Error('failed to parse');
|
|
91
|
+
})();
|
|
92
|
+
|
|
93
|
+
const parseCallee = (source: string) => {
|
|
94
|
+
const ast = parseOrThrow(source);
|
|
95
|
+
// The first statement is an ExpressionStatement wrapping the CallExpression.
|
|
96
|
+
const [stmt] = (ast as unknown as { body: readonly unknown[] }).body;
|
|
97
|
+
const { expression } = stmt as { expression: unknown };
|
|
98
|
+
return expression as Parameters<typeof __getTrailCalleeNameForTest>[0];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const coreNamespaces: ReadonlySet<string> = new Set(['core']);
|
|
102
|
+
|
|
103
|
+
describe('getTrailCalleeName', () => {
|
|
104
|
+
test('matches bare trail(...) identifier callees', () => {
|
|
105
|
+
expect(__getTrailCalleeNameForTest(parseCallee('trail("foo", {});'))).toBe(
|
|
106
|
+
'trail'
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('matches namespaced ns.trail(...) callees when the namespace is from @ontrails/*', () => {
|
|
111
|
+
expect(
|
|
112
|
+
__getTrailCalleeNameForTest(
|
|
113
|
+
parseCallee('core.trail("foo", {});'),
|
|
114
|
+
coreNamespaces
|
|
115
|
+
)
|
|
116
|
+
).toBe('trail');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('matches bare signal(...) identifier callees', () => {
|
|
120
|
+
expect(__getTrailCalleeNameForTest(parseCallee('signal("evt", {});'))).toBe(
|
|
121
|
+
'signal'
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('matches namespaced ns.signal(...) callees when the namespace is from @ontrails/*', () => {
|
|
126
|
+
expect(
|
|
127
|
+
__getTrailCalleeNameForTest(
|
|
128
|
+
parseCallee('core.signal("evt", {});'),
|
|
129
|
+
coreNamespaces
|
|
130
|
+
)
|
|
131
|
+
).toBe('signal');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('rejects computed member access like ns[trail](...)', () => {
|
|
135
|
+
expect(
|
|
136
|
+
__getTrailCalleeNameForTest(
|
|
137
|
+
parseCallee('ns[trail]("foo", {});'),
|
|
138
|
+
new Set(['ns'])
|
|
139
|
+
)
|
|
140
|
+
).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('rejects unrelated bare callees', () => {
|
|
144
|
+
expect(
|
|
145
|
+
__getTrailCalleeNameForTest(parseCallee('other("foo", {});'))
|
|
146
|
+
).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('rejects unrelated namespaced callees', () => {
|
|
150
|
+
expect(
|
|
151
|
+
__getTrailCalleeNameForTest(
|
|
152
|
+
parseCallee('ns.other("foo", {});'),
|
|
153
|
+
new Set(['ns'])
|
|
154
|
+
)
|
|
155
|
+
).toBeNull();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('rejects namespaced callees when the receiver is not a framework namespace', () => {
|
|
159
|
+
// `analytics.trail(...)` must not be mistaken for `core.trail(...)` when
|
|
160
|
+
// the `analytics` binding is not an `@ontrails/*` namespace import.
|
|
161
|
+
expect(
|
|
162
|
+
__getTrailCalleeNameForTest(
|
|
163
|
+
parseCallee('analytics.trail("foo", {});'),
|
|
164
|
+
coreNamespaces
|
|
165
|
+
)
|
|
166
|
+
).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('collectFrameworkNamespaceBindings', () => {
|
|
171
|
+
test('collects the local name of an @ontrails/core namespace import', () => {
|
|
172
|
+
const ast = parseOrThrow(`
|
|
173
|
+
import * as core from '@ontrails/core';
|
|
174
|
+
`);
|
|
175
|
+
expect(
|
|
176
|
+
[...__collectFrameworkNamespaceBindingsForTest(ast)].toSorted()
|
|
177
|
+
).toEqual(['core']);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('collects bindings for any @ontrails/* scoped package', () => {
|
|
181
|
+
const ast = parseOrThrow(`
|
|
182
|
+
import * as core from '@ontrails/core';
|
|
183
|
+
import * as warden from '@ontrails/warden';
|
|
184
|
+
`);
|
|
185
|
+
expect(
|
|
186
|
+
[...__collectFrameworkNamespaceBindingsForTest(ast)].toSorted()
|
|
187
|
+
).toEqual(['core', 'warden']);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('ignores namespace imports from non-framework packages', () => {
|
|
191
|
+
const ast = parseOrThrow(`
|
|
192
|
+
import * as analytics from 'analytics';
|
|
193
|
+
`);
|
|
194
|
+
expect([...__collectFrameworkNamespaceBindingsForTest(ast)]).toEqual([]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('ignores named imports from @ontrails/* packages', () => {
|
|
198
|
+
const ast = parseOrThrow(`
|
|
199
|
+
import { trail } from '@ontrails/core';
|
|
200
|
+
`);
|
|
201
|
+
expect([...__collectFrameworkNamespaceBindingsForTest(ast)]).toEqual([]);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('findTrailDefinitions with namespaced callees', () => {
|
|
206
|
+
test('discovers core.trail("id", { ... }) definitions', () => {
|
|
207
|
+
const source = `
|
|
208
|
+
import * as core from '@ontrails/core';
|
|
209
|
+
export const t = core.trail('entity.show', {
|
|
210
|
+
input: {},
|
|
211
|
+
});
|
|
212
|
+
`;
|
|
213
|
+
const ast = parseOrThrow(source);
|
|
214
|
+
const defs = findTrailDefinitions(ast);
|
|
215
|
+
expect(defs).toHaveLength(1);
|
|
216
|
+
expect(defs[0]?.id).toBe('entity.show');
|
|
217
|
+
expect(defs[0]?.kind).toBe('trail');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('discovers core.trail({ id: "x", ... }) single-object form', () => {
|
|
221
|
+
// Regression: confirms the single-object form (`trail({ id: 'x', ... })`)
|
|
222
|
+
// is discovered the same way as the two-arg form via a namespaced callee.
|
|
223
|
+
const source = `
|
|
224
|
+
import * as core from '@ontrails/core';
|
|
225
|
+
export const t = core.trail({ id: 'entity.show', input: {} });
|
|
226
|
+
`;
|
|
227
|
+
const ast = parseOrThrow(source);
|
|
228
|
+
const defs = findTrailDefinitions(ast);
|
|
229
|
+
expect(defs).toHaveLength(1);
|
|
230
|
+
expect(defs[0]?.id).toBe('entity.show');
|
|
231
|
+
expect(defs[0]?.kind).toBe('trail');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('discovers core.signal("id", { ... }) definitions', () => {
|
|
235
|
+
const source = `
|
|
236
|
+
import * as core from '@ontrails/core';
|
|
237
|
+
export const s = core.signal('entity.created', { payload: {} });
|
|
238
|
+
`;
|
|
239
|
+
const ast = parseOrThrow(source);
|
|
240
|
+
const defs = findTrailDefinitions(ast);
|
|
241
|
+
expect(defs).toHaveLength(1);
|
|
242
|
+
expect(defs[0]?.id).toBe('entity.created');
|
|
243
|
+
expect(defs[0]?.kind).toBe('signal');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('still ignores computed-member access ns[trail]("id", ...)', () => {
|
|
247
|
+
const source = `
|
|
248
|
+
const trail = 'x';
|
|
249
|
+
ns[trail]('entity.show', { input: {} });
|
|
250
|
+
`;
|
|
251
|
+
const ast = parseOrThrow(source);
|
|
252
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('ignores unrelated ns.trail(...) where ns is not an @ontrails import', () => {
|
|
256
|
+
// Regression: `analytics.trail(...)` where `analytics` is not a framework
|
|
257
|
+
// namespace must not be picked up as a trail definition.
|
|
258
|
+
const source = `
|
|
259
|
+
import * as analytics from 'analytics';
|
|
260
|
+
analytics.trail('entity.show', { input: {} });
|
|
261
|
+
`;
|
|
262
|
+
const ast = parseOrThrow(source);
|
|
263
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('findTrailDefinitions scope-aware shadowing', () => {
|
|
268
|
+
test('ignores core.trail(...) inside a function that locally shadows the namespace', () => {
|
|
269
|
+
// Regression: a function-local `const core = {...}` must shadow the
|
|
270
|
+
// module-level `import * as core from '@ontrails/core'` for the duration
|
|
271
|
+
// of that function. A name-only check would let the local `core.trail()`
|
|
272
|
+
// through; scope-aware resolution rejects it.
|
|
273
|
+
const source = `
|
|
274
|
+
import * as core from '@ontrails/core';
|
|
275
|
+
function weird() {
|
|
276
|
+
const core = { trail: (_id: string, _cfg: object) => undefined };
|
|
277
|
+
core.trail('entity.show', { input: {} });
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
const ast = parseOrThrow(source);
|
|
281
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('still discovers module-level core.trail(...) when a sibling function shadows the namespace', () => {
|
|
285
|
+
// Sanity check: a shadow inside one function must not suppress a
|
|
286
|
+
// legitimate `core.trail(...)` at module scope.
|
|
287
|
+
const source = `
|
|
288
|
+
import * as core from '@ontrails/core';
|
|
289
|
+
function weird() {
|
|
290
|
+
const core = { trail: (_id: string, _cfg: object) => undefined };
|
|
291
|
+
core.trail('entity.local', { input: {} });
|
|
292
|
+
}
|
|
293
|
+
export const t = core.trail('entity.show', { input: {} });
|
|
294
|
+
`;
|
|
295
|
+
const ast = parseOrThrow(source);
|
|
296
|
+
const defs = findTrailDefinitions(ast);
|
|
297
|
+
expect(defs).toHaveLength(1);
|
|
298
|
+
expect(defs[0]?.id).toBe('entity.show');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('ignores core.trail(...) when a function parameter shadows the namespace', () => {
|
|
302
|
+
const source = `
|
|
303
|
+
import * as core from '@ontrails/core';
|
|
304
|
+
function weird(core: { trail: (id: string, cfg: object) => void }) {
|
|
305
|
+
core.trail('entity.show', { input: {} });
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
const ast = parseOrThrow(source);
|
|
309
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('ignores core.trail(...) when a ClassDeclaration shadows the namespace', () => {
|
|
313
|
+
// A class declaration binds its name in the enclosing scope. Inside the
|
|
314
|
+
// class body the name refers to the class, shadowing any module-level
|
|
315
|
+
// namespace import of the same name.
|
|
316
|
+
const source = `
|
|
317
|
+
import * as core from '@ontrails/core';
|
|
318
|
+
class core {
|
|
319
|
+
field = core.trail('entity.show', { input: {} });
|
|
320
|
+
}
|
|
321
|
+
`;
|
|
322
|
+
const ast = parseOrThrow(source);
|
|
323
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('ignores core.trail(...) when a named ClassExpression shadows the namespace inside its body', () => {
|
|
327
|
+
// A named class expression's name is visible only inside its own body.
|
|
328
|
+
const source = `
|
|
329
|
+
import * as core from '@ontrails/core';
|
|
330
|
+
const C = class core {
|
|
331
|
+
field = core.trail('entity.show', { input: {} });
|
|
332
|
+
};
|
|
333
|
+
`;
|
|
334
|
+
const ast = parseOrThrow(source);
|
|
335
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('ignores core.trail(...) when a TSEnumDeclaration shadows the namespace', () => {
|
|
339
|
+
const source = `
|
|
340
|
+
import * as core from '@ontrails/core';
|
|
341
|
+
enum core { A, B }
|
|
342
|
+
core.trail('entity.show', { input: {} });
|
|
343
|
+
`;
|
|
344
|
+
const ast = parseOrThrow(source);
|
|
345
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('ignores core.trail(...) when a TSModuleDeclaration (namespace) shadows the namespace', () => {
|
|
349
|
+
const source = `
|
|
350
|
+
import * as core from '@ontrails/core';
|
|
351
|
+
namespace core { export const x = 1; }
|
|
352
|
+
core.trail('entity.show', { input: {} });
|
|
353
|
+
`;
|
|
354
|
+
const ast = parseOrThrow(source);
|
|
355
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('ignores core.trail(...) inside a FunctionExpression body that locally shadows the namespace', () => {
|
|
359
|
+
// oxc-parser emits `FunctionBody` for `function` expression bodies, not
|
|
360
|
+
// `BlockStatement`. Without a `FunctionBody` entry in the scope-frame
|
|
361
|
+
// collectors, a local `const core = {...}` at the top of the expression
|
|
362
|
+
// body would not push a frame and the shadow would be missed.
|
|
363
|
+
const source = `
|
|
364
|
+
import * as core from '@ontrails/core';
|
|
365
|
+
const fn = function weird() {
|
|
366
|
+
const core = { trail: (_id: string, _cfg: object) => undefined };
|
|
367
|
+
core.trail('entity.show', { input: {} });
|
|
368
|
+
};
|
|
369
|
+
`;
|
|
370
|
+
const ast = parseOrThrow(source);
|
|
371
|
+
expect(findTrailDefinitions(ast)).toHaveLength(0);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('does not hoist block-local function declarations out of their block', () => {
|
|
375
|
+
// A `function core(){}` inside an `if` block is block-scoped in strict
|
|
376
|
+
// (module) code. Hoisting it to the enclosing function frame would
|
|
377
|
+
// wrongly shadow the module-level `core` namespace for later code in
|
|
378
|
+
// the same function, dropping the trail detection below.
|
|
379
|
+
const source = `
|
|
380
|
+
import * as core from '@ontrails/core';
|
|
381
|
+
export function outer() {
|
|
382
|
+
if (Math.random() > 0) {
|
|
383
|
+
function core() { return 0; }
|
|
384
|
+
core();
|
|
385
|
+
}
|
|
386
|
+
return core.trail('entity.show', { input: {} });
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
const ast = parseOrThrow(source);
|
|
390
|
+
const defs = findTrailDefinitions(ast);
|
|
391
|
+
expect(defs).toHaveLength(1);
|
|
392
|
+
expect(defs[0]?.id).toBe('entity.show');
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('getTrailCalleeName permissive fallback', () => {
|
|
397
|
+
test('resolves namespaced core.trail(...) when no context is provided', () => {
|
|
398
|
+
// Inline resolution paths (`crosses: [core.trail(...)]`,
|
|
399
|
+
// `on: [core.signal(...)]`) do not have access to the surrounding file
|
|
400
|
+
// AST and so cannot build a FrameworkNamespaceContext. They must still
|
|
401
|
+
// be able to recognize the trail/signal primitive by name.
|
|
402
|
+
expect(
|
|
403
|
+
__getTrailCalleeNameForTest(parseCallee('core.trail("foo", {});'))
|
|
404
|
+
).toBe('trail');
|
|
405
|
+
expect(
|
|
406
|
+
__getTrailCalleeNameForTest(parseCallee('core.signal("evt", {});'))
|
|
407
|
+
).toBe('signal');
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
describe('findContourDefinitions with namespaced callees', () => {
|
|
412
|
+
test('discovers core.contour("name", { ... }) definitions', () => {
|
|
413
|
+
const source = `
|
|
414
|
+
import * as core from '@ontrails/core';
|
|
415
|
+
export const user = core.contour('user', { id: 'string' });
|
|
416
|
+
`;
|
|
417
|
+
const ast = parseOrThrow(source);
|
|
418
|
+
const defs = findContourDefinitions(ast);
|
|
419
|
+
expect(defs).toHaveLength(1);
|
|
420
|
+
expect(defs[0]?.name).toBe('user');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('ignores unrelated ns.contour(...) where ns is not @ontrails/*', () => {
|
|
424
|
+
const source = `
|
|
425
|
+
import * as analytics from 'analytics';
|
|
426
|
+
analytics.contour('user', { id: 'string' });
|
|
427
|
+
`;
|
|
428
|
+
const ast = parseOrThrow(source);
|
|
429
|
+
expect(findContourDefinitions(ast)).toHaveLength(0);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('still rejects computed member access', () => {
|
|
433
|
+
const source = `
|
|
434
|
+
const contour = 'x';
|
|
435
|
+
ns[contour]('user', { id: 'string' });
|
|
436
|
+
`;
|
|
437
|
+
const ast = parseOrThrow(source);
|
|
438
|
+
expect(findContourDefinitions(ast)).toHaveLength(0);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('findContourDefinitions inline discovery', () => {
|
|
443
|
+
// Regression: `findContourDefinitions` descends into nested object
|
|
444
|
+
// expressions and surfaces inline `core.contour('inner', ...)` calls as
|
|
445
|
+
// definitions alongside the outer binding. This behavior is load-bearing for
|
|
446
|
+
// reference-site resolution (see `collectContourReferenceSites`) and must
|
|
447
|
+
// not silently regress.
|
|
448
|
+
const inlineSource = `
|
|
449
|
+
import * as core from '@ontrails/core';
|
|
450
|
+
import { z } from 'zod';
|
|
451
|
+
|
|
452
|
+
export const outer = core.contour('outer', {
|
|
453
|
+
id: z.string().uuid(),
|
|
454
|
+
inner: core.contour('inner', { id: z.string().uuid() }).id(),
|
|
455
|
+
});
|
|
456
|
+
`;
|
|
457
|
+
|
|
458
|
+
test('returns both outer and inline contour definitions by default', () => {
|
|
459
|
+
const ast = parseOrThrow(inlineSource);
|
|
460
|
+
const defs = findContourDefinitions(ast);
|
|
461
|
+
|
|
462
|
+
expect(defs).toHaveLength(2);
|
|
463
|
+
const names = defs.map((d) => d.name).toSorted();
|
|
464
|
+
expect(names).toEqual(['inner', 'outer']);
|
|
465
|
+
|
|
466
|
+
const outer = defs.find((d) => d.name === 'outer');
|
|
467
|
+
const inner = defs.find((d) => d.name === 'inner');
|
|
468
|
+
expect(outer?.bindingName).toBe('outer');
|
|
469
|
+
// Inline contours are anonymous call expressions — no binding name.
|
|
470
|
+
expect(inner?.bindingName).toBeUndefined();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('collectContourDefinitionIds includes inline contour ids', () => {
|
|
474
|
+
const ast = parseOrThrow(inlineSource);
|
|
475
|
+
const ids = collectContourDefinitionIds(ast);
|
|
476
|
+
|
|
477
|
+
expect(ids.has('outer')).toBe(true);
|
|
478
|
+
expect(ids.has('inner')).toBe(true);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test('collectNamedContourIds excludes inline contours (no bindingName)', () => {
|
|
482
|
+
const ast = parseOrThrow(inlineSource);
|
|
483
|
+
const named = collectNamedContourIds(ast);
|
|
484
|
+
|
|
485
|
+
expect([...named.keys()].toSorted()).toEqual(['outer']);
|
|
486
|
+
expect(named.get('outer')).toBe('outer');
|
|
487
|
+
expect(named.has('inner')).toBe(false);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('topLevelOnly: true skips inline contour discovery', () => {
|
|
491
|
+
const ast = parseOrThrow(inlineSource);
|
|
492
|
+
const defs = findContourDefinitions(ast, undefined, {
|
|
493
|
+
topLevelOnly: true,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
expect(defs).toHaveLength(1);
|
|
497
|
+
expect(defs[0]?.name).toBe('outer');
|
|
498
|
+
expect(defs[0]?.bindingName).toBe('outer');
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('topLevelOnly: true still surfaces top-level statement-form calls', () => {
|
|
502
|
+
// Regression for Codex P2 on PR #222: the `topLevelOnly` guard must only
|
|
503
|
+
// exclude inline nested contour calls. Top-level bare-statement forms
|
|
504
|
+
// (`core.contour('name', {...});` directly in the program body, not
|
|
505
|
+
// bound to a variable) are top-level and should still be returned.
|
|
506
|
+
const statementFormSource = `
|
|
507
|
+
import * as core from '@ontrails/core';
|
|
508
|
+
import { z } from 'zod';
|
|
509
|
+
|
|
510
|
+
export const bound = core.contour('bound', {
|
|
511
|
+
id: z.string().uuid(),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
core.contour('bare', { id: z.string().uuid() });
|
|
515
|
+
`;
|
|
516
|
+
const ast = parseOrThrow(statementFormSource);
|
|
517
|
+
const defs = findContourDefinitions(ast, undefined, {
|
|
518
|
+
topLevelOnly: true,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const names = defs.map((d) => d.name).toSorted();
|
|
522
|
+
expect(names).toEqual(['bare', 'bound']);
|
|
523
|
+
|
|
524
|
+
const bound = defs.find((d) => d.name === 'bound');
|
|
525
|
+
const bare = defs.find((d) => d.name === 'bare');
|
|
526
|
+
expect(bound?.bindingName).toBe('bound');
|
|
527
|
+
// Bare statement-form calls have no local binding.
|
|
528
|
+
expect(bare?.bindingName).toBeUndefined();
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test('topLevelOnly: true surfaces export default core.contour(...) form', () => {
|
|
532
|
+
// Regression for Greptile P2 on PR #227: collectTopLevelStatementCallStarts
|
|
533
|
+
// branches on ExportDefaultDeclaration via getCandidateCallHosts, so an
|
|
534
|
+
// export-default contour declaration must still be surfaced under the
|
|
535
|
+
// topLevelOnly: true flag.
|
|
536
|
+
const exportDefaultSource = `
|
|
537
|
+
import * as core from '@ontrails/core';
|
|
538
|
+
import { z } from 'zod';
|
|
539
|
+
|
|
540
|
+
export default core.contour('default-export', {
|
|
541
|
+
id: z.string().uuid(),
|
|
542
|
+
});
|
|
543
|
+
`;
|
|
544
|
+
const ast = parseOrThrow(exportDefaultSource);
|
|
545
|
+
const defs = findContourDefinitions(ast, undefined, {
|
|
546
|
+
topLevelOnly: true,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
expect(defs).toHaveLength(1);
|
|
550
|
+
expect(defs[0]?.name).toBe('default-export');
|
|
551
|
+
// Export-default call expressions have no local binding name.
|
|
552
|
+
expect(defs[0]?.bindingName).toBeUndefined();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('collectContourReferenceSites with namespaced inline contours', () => {
|
|
557
|
+
test('resolves core.contour(...).id() when the file context is available', () => {
|
|
558
|
+
const source = `
|
|
559
|
+
import * as core from '@ontrails/core';
|
|
560
|
+
import { z } from 'zod';
|
|
561
|
+
|
|
562
|
+
const gist = core.contour('gist', {
|
|
563
|
+
id: z.string().uuid(),
|
|
564
|
+
ownerId: core.contour('user', { id: z.string().uuid() }).id(),
|
|
565
|
+
});
|
|
566
|
+
`;
|
|
567
|
+
const ast = parseOrThrow(source);
|
|
568
|
+
const refs = collectContourReferenceSites(ast);
|
|
569
|
+
|
|
570
|
+
expect(refs).toHaveLength(1);
|
|
571
|
+
expect(refs[0]?.source).toBe('gist');
|
|
572
|
+
expect(refs[0]?.field).toBe('ownerId');
|
|
573
|
+
expect(refs[0]?.target).toBe('user');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test('ignores analytics.contour(...).id() when the receiver is not a framework namespace', () => {
|
|
577
|
+
const source = `
|
|
578
|
+
import * as analytics from 'analytics';
|
|
579
|
+
import { z } from 'zod';
|
|
580
|
+
|
|
581
|
+
const gist = contour('gist', {
|
|
582
|
+
id: z.string().uuid(),
|
|
583
|
+
ownerId: analytics.contour('user', { id: z.string().uuid() }).id(),
|
|
584
|
+
});
|
|
585
|
+
`;
|
|
586
|
+
const ast = parseOrThrow(source);
|
|
587
|
+
|
|
588
|
+
expect(collectContourReferenceSites(ast)).toEqual([]);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('unwraps wrapped contour id schemas before resolving the target', () => {
|
|
592
|
+
const source = `
|
|
593
|
+
import { contour } from '@ontrails/core';
|
|
594
|
+
import { z } from 'zod';
|
|
595
|
+
|
|
596
|
+
const user = contour('user', {
|
|
597
|
+
id: z.string().uuid(),
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const gist = contour('gist', {
|
|
601
|
+
id: z.string().uuid(),
|
|
602
|
+
ownerId: user.id().nullable().optional().default(null),
|
|
603
|
+
});
|
|
604
|
+
`;
|
|
605
|
+
const ast = parseOrThrow(source);
|
|
606
|
+
const refs = collectContourReferenceSites(ast);
|
|
607
|
+
|
|
608
|
+
expect(refs).toHaveLength(1);
|
|
609
|
+
expect(refs[0]?.source).toBe('gist');
|
|
610
|
+
expect(refs[0]?.field).toBe('ownerId');
|
|
611
|
+
expect(refs[0]?.target).toBe('user');
|
|
612
|
+
});
|
|
613
|
+
});
|