@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
|
@@ -1,9 +1,45 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, spyOn, test } from 'bun:test';
|
|
2
|
+
import * as nodeFs from 'node:fs';
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
2
6
|
|
|
3
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
clearImplementationReturnsResultCache,
|
|
9
|
+
implementationReturnsResult,
|
|
10
|
+
} from '../rules/implementation-returns-result.js';
|
|
4
11
|
|
|
5
12
|
const TEST_FILE = 'test.ts';
|
|
6
13
|
|
|
14
|
+
const writeReadCountFixture = (
|
|
15
|
+
writeFile: (name: string, content: string) => string
|
|
16
|
+
): { readonly implPath: string; readonly caller: string } => {
|
|
17
|
+
const implPath = writeFile(
|
|
18
|
+
'impl-readcount.ts',
|
|
19
|
+
`const helper = async (): Promise<Result<object, Error>> =>
|
|
20
|
+
Result.ok({ ok: true });
|
|
21
|
+
|
|
22
|
+
export default helper;
|
|
23
|
+
`
|
|
24
|
+
);
|
|
25
|
+
writeFile(
|
|
26
|
+
'barrel-readcount.ts',
|
|
27
|
+
`export { default as foo } from './impl-readcount.js';
|
|
28
|
+
`
|
|
29
|
+
);
|
|
30
|
+
const caller = writeFile(
|
|
31
|
+
'caller-readcount.ts',
|
|
32
|
+
`import { foo } from './barrel-readcount.js';
|
|
33
|
+
|
|
34
|
+
trail("entity.report", {
|
|
35
|
+
blaze: async (input, ctx) => {
|
|
36
|
+
return foo();
|
|
37
|
+
}
|
|
38
|
+
})`
|
|
39
|
+
);
|
|
40
|
+
return { caller, implPath };
|
|
41
|
+
};
|
|
42
|
+
|
|
7
43
|
describe('implementation-returns-result', () => {
|
|
8
44
|
test('flags raw object return in trail implementation', () => {
|
|
9
45
|
const code = `
|
|
@@ -104,6 +140,984 @@ trail("entity.list", {
|
|
|
104
140
|
expect(diagnostics[0]?.message).toContain('entity.list');
|
|
105
141
|
});
|
|
106
142
|
|
|
143
|
+
describe('imported helpers', () => {
|
|
144
|
+
let tmpDir: string;
|
|
145
|
+
|
|
146
|
+
beforeAll(() => {
|
|
147
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'warden-impl-result-'));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
afterAll(() => {
|
|
151
|
+
rmSync(tmpDir, { force: true, recursive: true });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const writeFile = (name: string, content: string): string => {
|
|
155
|
+
const path = join(tmpDir, name);
|
|
156
|
+
writeFileSync(path, content);
|
|
157
|
+
return path;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
test('allows imported helper with Promise<Result<...>> return annotation', () => {
|
|
161
|
+
writeFile(
|
|
162
|
+
'result-helper.ts',
|
|
163
|
+
`export const buildReport = async (): Promise<Result<object, Error>> =>
|
|
164
|
+
Result.ok({ ok: true });
|
|
165
|
+
`
|
|
166
|
+
);
|
|
167
|
+
const caller = writeFile(
|
|
168
|
+
'caller.ts',
|
|
169
|
+
`import { buildReport } from './result-helper.js';
|
|
170
|
+
|
|
171
|
+
trail("entity.report", {
|
|
172
|
+
blaze: async (input, ctx) => {
|
|
173
|
+
return buildReport();
|
|
174
|
+
}
|
|
175
|
+
})`
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const diagnostics = implementationReturnsResult.check(
|
|
179
|
+
readFileSync(caller, 'utf8'),
|
|
180
|
+
caller
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
expect(diagnostics.length).toBe(0);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('flags imported helper without Result return annotation', () => {
|
|
187
|
+
writeFile(
|
|
188
|
+
'plain-helper.ts',
|
|
189
|
+
`export const buildReport = async () => ({ ok: true });
|
|
190
|
+
`
|
|
191
|
+
);
|
|
192
|
+
const caller = writeFile(
|
|
193
|
+
'caller-plain.ts',
|
|
194
|
+
`import { buildReport } from './plain-helper.js';
|
|
195
|
+
|
|
196
|
+
trail("entity.report", {
|
|
197
|
+
blaze: async (input, ctx) => {
|
|
198
|
+
return buildReport();
|
|
199
|
+
}
|
|
200
|
+
})`
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const diagnostics = implementationReturnsResult.check(
|
|
204
|
+
readFileSync(caller, 'utf8'),
|
|
205
|
+
caller
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(diagnostics.length).toBe(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('flags helper imported from bare specifier (node_modules)', () => {
|
|
212
|
+
const caller = writeFile(
|
|
213
|
+
'caller-bare.ts',
|
|
214
|
+
`import { buildReport } from 'some-package';
|
|
215
|
+
|
|
216
|
+
trail("entity.report", {
|
|
217
|
+
blaze: async (input, ctx) => {
|
|
218
|
+
return buildReport();
|
|
219
|
+
}
|
|
220
|
+
})`
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const diagnostics = implementationReturnsResult.check(
|
|
224
|
+
readFileSync(caller, 'utf8'),
|
|
225
|
+
caller
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(diagnostics.length).toBe(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('flags gracefully when target file is unreadable', () => {
|
|
232
|
+
const caller = writeFile(
|
|
233
|
+
'caller-missing.ts',
|
|
234
|
+
`import { buildReport } from './does-not-exist.js';
|
|
235
|
+
|
|
236
|
+
trail("entity.report", {
|
|
237
|
+
blaze: async (input, ctx) => {
|
|
238
|
+
return buildReport();
|
|
239
|
+
}
|
|
240
|
+
})`
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const diagnostics = implementationReturnsResult.check(
|
|
244
|
+
readFileSync(caller, 'utf8'),
|
|
245
|
+
caller
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(diagnostics.length).toBe(1);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('specifier re-exports', () => {
|
|
252
|
+
test('allows specifier re-export with source (export { helper } from ...)', () => {
|
|
253
|
+
writeFile(
|
|
254
|
+
'impl-specifier.ts',
|
|
255
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
256
|
+
Result.ok({ ok: true });
|
|
257
|
+
`
|
|
258
|
+
);
|
|
259
|
+
writeFile(
|
|
260
|
+
'barrel-specifier.ts',
|
|
261
|
+
`export { helper } from './impl-specifier.js';
|
|
262
|
+
`
|
|
263
|
+
);
|
|
264
|
+
const caller = writeFile(
|
|
265
|
+
'caller-specifier.ts',
|
|
266
|
+
`import { helper } from './barrel-specifier.js';
|
|
267
|
+
|
|
268
|
+
trail("entity.report", {
|
|
269
|
+
blaze: async (input, ctx) => {
|
|
270
|
+
return helper();
|
|
271
|
+
}
|
|
272
|
+
})`
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const diagnostics = implementationReturnsResult.check(
|
|
276
|
+
readFileSync(caller, 'utf8'),
|
|
277
|
+
caller
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
expect(diagnostics.length).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('allows aliased specifier re-export (export { helper as aliased })', () => {
|
|
284
|
+
writeFile(
|
|
285
|
+
'impl-aliased.ts',
|
|
286
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
287
|
+
Result.ok({ ok: true });
|
|
288
|
+
`
|
|
289
|
+
);
|
|
290
|
+
writeFile(
|
|
291
|
+
'barrel-aliased.ts',
|
|
292
|
+
`export { helper as aliased } from './impl-aliased.js';
|
|
293
|
+
`
|
|
294
|
+
);
|
|
295
|
+
const caller = writeFile(
|
|
296
|
+
'caller-aliased.ts',
|
|
297
|
+
`import { aliased } from './barrel-aliased.js';
|
|
298
|
+
|
|
299
|
+
trail("entity.report", {
|
|
300
|
+
blaze: async (input, ctx) => {
|
|
301
|
+
return aliased();
|
|
302
|
+
}
|
|
303
|
+
})`
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const diagnostics = implementationReturnsResult.check(
|
|
307
|
+
readFileSync(caller, 'utf8'),
|
|
308
|
+
caller
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(diagnostics.length).toBe(0);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('allows specifier re-export without source (same-file)', () => {
|
|
315
|
+
writeFile(
|
|
316
|
+
'barrel-samefile.ts',
|
|
317
|
+
`const helper = async (): Promise<Result<object, Error>> =>
|
|
318
|
+
Result.ok({ ok: true });
|
|
319
|
+
|
|
320
|
+
export { helper };
|
|
321
|
+
`
|
|
322
|
+
);
|
|
323
|
+
const caller = writeFile(
|
|
324
|
+
'caller-samefile.ts',
|
|
325
|
+
`import { helper } from './barrel-samefile.js';
|
|
326
|
+
|
|
327
|
+
trail("entity.report", {
|
|
328
|
+
blaze: async (input, ctx) => {
|
|
329
|
+
return helper();
|
|
330
|
+
}
|
|
331
|
+
})`
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const diagnostics = implementationReturnsResult.check(
|
|
335
|
+
readFileSync(caller, 'utf8'),
|
|
336
|
+
caller
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
expect(diagnostics.length).toBe(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('allows default re-export (export { default as foo } from ...)', () => {
|
|
343
|
+
writeFile(
|
|
344
|
+
'impl-default.ts',
|
|
345
|
+
`const helper = async (): Promise<Result<object, Error>> =>
|
|
346
|
+
Result.ok({ ok: true });
|
|
347
|
+
|
|
348
|
+
export default helper;
|
|
349
|
+
`
|
|
350
|
+
);
|
|
351
|
+
writeFile(
|
|
352
|
+
'barrel-default.ts',
|
|
353
|
+
`export { default as foo } from './impl-default.js';
|
|
354
|
+
`
|
|
355
|
+
);
|
|
356
|
+
const caller = writeFile(
|
|
357
|
+
'caller-default.ts',
|
|
358
|
+
`import { foo } from './barrel-default.js';
|
|
359
|
+
|
|
360
|
+
trail("entity.report", {
|
|
361
|
+
blaze: async (input, ctx) => {
|
|
362
|
+
return foo();
|
|
363
|
+
}
|
|
364
|
+
})`
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const diagnostics = implementationReturnsResult.check(
|
|
368
|
+
readFileSync(caller, 'utf8'),
|
|
369
|
+
caller
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
expect(diagnostics.length).toBe(0);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('reads the re-export target file only once per check (default specifier)', () => {
|
|
376
|
+
// Regression for PR #204: `export { default as foo } from './impl.js'`
|
|
377
|
+
// previously triggered two reads/parses of impl.js within a single
|
|
378
|
+
// check() call — once via the downstream-names walk and once for the
|
|
379
|
+
// default-specifier AST lookup. The loaded target is now threaded
|
|
380
|
+
// through, so impl.js should be read exactly once.
|
|
381
|
+
const { implPath, caller } = writeReadCountFixture(writeFile);
|
|
382
|
+
|
|
383
|
+
clearImplementationReturnsResultCache();
|
|
384
|
+
const readSpy = spyOn(nodeFs, 'readFileSync');
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const diagnostics = implementationReturnsResult.check(
|
|
388
|
+
readFileSync(caller, 'utf8'),
|
|
389
|
+
caller
|
|
390
|
+
);
|
|
391
|
+
expect(diagnostics.length).toBe(0);
|
|
392
|
+
const implReads = readSpy.mock.calls.filter(
|
|
393
|
+
(call) => call[0] === implPath
|
|
394
|
+
);
|
|
395
|
+
expect(implReads.length).toBe(1);
|
|
396
|
+
} finally {
|
|
397
|
+
readSpy.mockRestore();
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('caps re-export chains beyond one transitive hop', () => {
|
|
402
|
+
// A -> B -> C: caller imports from A, which re-exports from B,
|
|
403
|
+
// which re-exports from C where the helper is declared. The
|
|
404
|
+
// MAX_RERESOLVE_DEPTH=1 cap should prevent resolving C, so the
|
|
405
|
+
// helper name is NOT recognized through the 2-hop chain.
|
|
406
|
+
writeFile(
|
|
407
|
+
'depth-c.ts',
|
|
408
|
+
`export const deepHelper = async (): Promise<Result<object, Error>> =>
|
|
409
|
+
Result.ok({ ok: true });
|
|
410
|
+
`
|
|
411
|
+
);
|
|
412
|
+
writeFile(
|
|
413
|
+
'depth-b.ts',
|
|
414
|
+
`export { deepHelper } from './depth-c.js';
|
|
415
|
+
`
|
|
416
|
+
);
|
|
417
|
+
writeFile(
|
|
418
|
+
'depth-a.ts',
|
|
419
|
+
`export { deepHelper } from './depth-b.js';
|
|
420
|
+
`
|
|
421
|
+
);
|
|
422
|
+
const caller = writeFile(
|
|
423
|
+
'caller-depth.ts',
|
|
424
|
+
`import { deepHelper } from './depth-a.js';
|
|
425
|
+
|
|
426
|
+
trail("entity.report", {
|
|
427
|
+
blaze: async (input, ctx) => {
|
|
428
|
+
return deepHelper();
|
|
429
|
+
}
|
|
430
|
+
})`
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const diagnostics = implementationReturnsResult.check(
|
|
434
|
+
readFileSync(caller, 'utf8'),
|
|
435
|
+
caller
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// 2-hop re-export chain exceeds MAX_RERESOLVE_DEPTH, so the helper's
|
|
439
|
+
// Result annotation is not discoverable and the return is flagged.
|
|
440
|
+
expect(diagnostics.length).toBe(1);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('cache does not bleed cycle-truncated results into direct imports', () => {
|
|
444
|
+
// A -> B -> A cycle. When A is resolved first (e.g. through a caller
|
|
445
|
+
// that imports from A), resolving B is attempted while A is in the
|
|
446
|
+
// visited set, which truncates B's transitive view back to A. A naive
|
|
447
|
+
// per-target cache would then persist an empty set for B and wrongly
|
|
448
|
+
// flag a later direct import from B.
|
|
449
|
+
writeFile(
|
|
450
|
+
'ctx-a.ts',
|
|
451
|
+
`export { helper } from './ctx-b.js';
|
|
452
|
+
`
|
|
453
|
+
);
|
|
454
|
+
writeFile(
|
|
455
|
+
'ctx-b.ts',
|
|
456
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
457
|
+
Result.ok({ ok: true });
|
|
458
|
+
|
|
459
|
+
export { helper as aliasedFromA } from './ctx-a.js';
|
|
460
|
+
`
|
|
461
|
+
);
|
|
462
|
+
const callerA = writeFile(
|
|
463
|
+
'caller-ctx-a.ts',
|
|
464
|
+
`import { helper } from './ctx-a.js';
|
|
465
|
+
|
|
466
|
+
trail("entity.first", {
|
|
467
|
+
blaze: async (input, ctx) => {
|
|
468
|
+
return helper();
|
|
469
|
+
}
|
|
470
|
+
})`
|
|
471
|
+
);
|
|
472
|
+
const callerB = writeFile(
|
|
473
|
+
'caller-ctx-b.ts',
|
|
474
|
+
`import { helper } from './ctx-b.js';
|
|
475
|
+
|
|
476
|
+
trail("entity.second", {
|
|
477
|
+
blaze: async (input, ctx) => {
|
|
478
|
+
return helper();
|
|
479
|
+
}
|
|
480
|
+
})`
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
// Resolve A first — this walks A -> B (and B -> A is cycle-guarded).
|
|
484
|
+
implementationReturnsResult.check(
|
|
485
|
+
readFileSync(callerA, 'utf8'),
|
|
486
|
+
callerA
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// Now resolve B directly. B's own inline helper must still be
|
|
490
|
+
// recognized as Result-returning, even though an earlier transitive
|
|
491
|
+
// walk touched B under a non-empty parentVisited.
|
|
492
|
+
const diagnostics = implementationReturnsResult.check(
|
|
493
|
+
readFileSync(callerB, 'utf8'),
|
|
494
|
+
callerB
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
expect(diagnostics.length).toBe(0);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('allows default-imported Result helper (import foo from ...)', () => {
|
|
501
|
+
writeFile(
|
|
502
|
+
'impl-default-import.ts',
|
|
503
|
+
`const helper = async (): Promise<Result<object, Error>> =>
|
|
504
|
+
Result.ok({ ok: true });
|
|
505
|
+
|
|
506
|
+
export default helper;
|
|
507
|
+
`
|
|
508
|
+
);
|
|
509
|
+
const caller = writeFile(
|
|
510
|
+
'caller-default-import.ts',
|
|
511
|
+
`import buildReport from './impl-default-import.js';
|
|
512
|
+
|
|
513
|
+
trail("entity.report", {
|
|
514
|
+
blaze: async (input, ctx) => {
|
|
515
|
+
return buildReport();
|
|
516
|
+
}
|
|
517
|
+
})`
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const diagnostics = implementationReturnsResult.check(
|
|
521
|
+
readFileSync(caller, 'utf8'),
|
|
522
|
+
caller
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
expect(diagnostics.length).toBe(0);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe('namespace imports and barrel export *', () => {
|
|
530
|
+
test('allows namespace-imported Result helper (import * as ns)', () => {
|
|
531
|
+
writeFile(
|
|
532
|
+
'impl-namespace.ts',
|
|
533
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
534
|
+
Result.ok({ ok: true });
|
|
535
|
+
`
|
|
536
|
+
);
|
|
537
|
+
const caller = writeFile(
|
|
538
|
+
'caller-namespace.ts',
|
|
539
|
+
`import * as ns from './impl-namespace.js';
|
|
540
|
+
|
|
541
|
+
trail("entity.report", {
|
|
542
|
+
blaze: async (input, ctx) => {
|
|
543
|
+
return ns.helper();
|
|
544
|
+
}
|
|
545
|
+
})`
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const diagnostics = implementationReturnsResult.check(
|
|
549
|
+
readFileSync(caller, 'utf8'),
|
|
550
|
+
caller
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
expect(diagnostics.length).toBe(0);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
test('flags namespace-imported non-Result member call', () => {
|
|
557
|
+
writeFile(
|
|
558
|
+
'impl-namespace-mixed.ts',
|
|
559
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
560
|
+
Result.ok({ ok: true });
|
|
561
|
+
|
|
562
|
+
export const nonResultFn = async () => ({ ok: true });
|
|
563
|
+
`
|
|
564
|
+
);
|
|
565
|
+
const caller = writeFile(
|
|
566
|
+
'caller-namespace-mixed.ts',
|
|
567
|
+
`import * as ns from './impl-namespace-mixed.js';
|
|
568
|
+
|
|
569
|
+
trail("entity.report", {
|
|
570
|
+
blaze: async (input, ctx) => {
|
|
571
|
+
return ns.nonResultFn();
|
|
572
|
+
}
|
|
573
|
+
})`
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const diagnostics = implementationReturnsResult.check(
|
|
577
|
+
readFileSync(caller, 'utf8'),
|
|
578
|
+
caller
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
expect(diagnostics.length).toBe(1);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test('falls back gracefully on unresolvable namespace import target', () => {
|
|
585
|
+
const caller = writeFile(
|
|
586
|
+
'caller-namespace-missing.ts',
|
|
587
|
+
`import * as ns from './missing-namespace.js';
|
|
588
|
+
|
|
589
|
+
trail("entity.report", {
|
|
590
|
+
blaze: async (input, ctx) => {
|
|
591
|
+
return ns.helper();
|
|
592
|
+
}
|
|
593
|
+
})`
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
const diagnostics = implementationReturnsResult.check(
|
|
597
|
+
readFileSync(caller, 'utf8'),
|
|
598
|
+
caller
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
expect(diagnostics.length).toBe(1);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
describe('shadowing by param/const/let', () => {
|
|
605
|
+
test('flags ns.helper() when blaze parameter shadows the namespace import', () => {
|
|
606
|
+
writeFile(
|
|
607
|
+
'impl-ns-shadow-param.ts',
|
|
608
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
609
|
+
Result.ok({ ok: true });
|
|
610
|
+
`
|
|
611
|
+
);
|
|
612
|
+
const caller = writeFile(
|
|
613
|
+
'caller-ns-shadow-param.ts',
|
|
614
|
+
`import * as ns from './impl-ns-shadow-param.js';
|
|
615
|
+
|
|
616
|
+
trail("entity.report", {
|
|
617
|
+
blaze: async (ns, ctx) => {
|
|
618
|
+
return ns.helper(ctx);
|
|
619
|
+
}
|
|
620
|
+
})`
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
const diagnostics = implementationReturnsResult.check(
|
|
624
|
+
readFileSync(caller, 'utf8'),
|
|
625
|
+
caller
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
// The blaze parameter \`ns\` shadows the namespace import; \`ns.helper()\`
|
|
629
|
+
// is a call on the parameter, not on the namespace, so the return
|
|
630
|
+
// must be flagged rather than silently treated as a Result helper.
|
|
631
|
+
expect(diagnostics.length).toBe(1);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('flags ns.helper() when a local const shadows the namespace import', () => {
|
|
635
|
+
writeFile(
|
|
636
|
+
'impl-ns-shadow-const.ts',
|
|
637
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
638
|
+
Result.ok({ ok: true });
|
|
639
|
+
`
|
|
640
|
+
);
|
|
641
|
+
const caller = writeFile(
|
|
642
|
+
'caller-ns-shadow-const.ts',
|
|
643
|
+
`import * as ns from './impl-ns-shadow-const.js';
|
|
644
|
+
|
|
645
|
+
trail("entity.report", {
|
|
646
|
+
blaze: async (input, ctx) => {
|
|
647
|
+
const ns = { helper: () => ({ ok: true }) };
|
|
648
|
+
return ns.helper(input);
|
|
649
|
+
}
|
|
650
|
+
})`
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const diagnostics = implementationReturnsResult.check(
|
|
654
|
+
readFileSync(caller, 'utf8'),
|
|
655
|
+
caller
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
expect(diagnostics.length).toBe(1);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('flags ns.helper() when a local let shadows the namespace import', () => {
|
|
662
|
+
writeFile(
|
|
663
|
+
'impl-ns-shadow-let.ts',
|
|
664
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
665
|
+
Result.ok({ ok: true });
|
|
666
|
+
`
|
|
667
|
+
);
|
|
668
|
+
const caller = writeFile(
|
|
669
|
+
'caller-ns-shadow-let.ts',
|
|
670
|
+
`import * as ns from './impl-ns-shadow-let.js';
|
|
671
|
+
|
|
672
|
+
trail("entity.report", {
|
|
673
|
+
blaze: async (input, ctx) => {
|
|
674
|
+
let ns = input;
|
|
675
|
+
return ns.helper(input);
|
|
676
|
+
}
|
|
677
|
+
})`
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
const diagnostics = implementationReturnsResult.check(
|
|
681
|
+
readFileSync(caller, 'utf8'),
|
|
682
|
+
caller
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
expect(diagnostics.length).toBe(1);
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe('shadowing in for/catch scopes', () => {
|
|
690
|
+
test('flags ns.helper() when a for-init const shadows the namespace import', () => {
|
|
691
|
+
writeFile(
|
|
692
|
+
'impl-ns-shadow-for-init.ts',
|
|
693
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
694
|
+
Result.ok({ ok: true });
|
|
695
|
+
`
|
|
696
|
+
);
|
|
697
|
+
const caller = writeFile(
|
|
698
|
+
'caller-ns-shadow-for-init.ts',
|
|
699
|
+
`import * as ns from './impl-ns-shadow-for-init.js';
|
|
700
|
+
|
|
701
|
+
trail("entity.report", {
|
|
702
|
+
blaze: async (input, ctx) => {
|
|
703
|
+
for (const ns = 0; ns < 1; ns++) {
|
|
704
|
+
return ns.helper(input);
|
|
705
|
+
}
|
|
706
|
+
return Result.ok({});
|
|
707
|
+
}
|
|
708
|
+
})`
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const diagnostics = implementationReturnsResult.check(
|
|
712
|
+
readFileSync(caller, 'utf8'),
|
|
713
|
+
caller
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
expect(diagnostics.length).toBe(1);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test('flags ns.helper() when a for-of binding shadows the namespace import', () => {
|
|
720
|
+
writeFile(
|
|
721
|
+
'impl-ns-shadow-for-of.ts',
|
|
722
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
723
|
+
Result.ok({ ok: true });
|
|
724
|
+
`
|
|
725
|
+
);
|
|
726
|
+
const caller = writeFile(
|
|
727
|
+
'caller-ns-shadow-for-of.ts',
|
|
728
|
+
`import * as ns from './impl-ns-shadow-for-of.js';
|
|
729
|
+
|
|
730
|
+
trail("entity.report", {
|
|
731
|
+
blaze: async (input, ctx) => {
|
|
732
|
+
for (const ns of [1, 2, 3]) {
|
|
733
|
+
return ns.helper(input);
|
|
734
|
+
}
|
|
735
|
+
return Result.ok({});
|
|
736
|
+
}
|
|
737
|
+
})`
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const diagnostics = implementationReturnsResult.check(
|
|
741
|
+
readFileSync(caller, 'utf8'),
|
|
742
|
+
caller
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
expect(diagnostics.length).toBe(1);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test('flags ns.helper() when a catch param shadows the namespace import', () => {
|
|
749
|
+
writeFile(
|
|
750
|
+
'impl-ns-shadow-catch.ts',
|
|
751
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
752
|
+
Result.ok({ ok: true });
|
|
753
|
+
`
|
|
754
|
+
);
|
|
755
|
+
const caller = writeFile(
|
|
756
|
+
'caller-ns-shadow-catch.ts',
|
|
757
|
+
`import * as ns from './impl-ns-shadow-catch.js';
|
|
758
|
+
|
|
759
|
+
trail("entity.report", {
|
|
760
|
+
blaze: async (input, ctx) => {
|
|
761
|
+
try {
|
|
762
|
+
return Result.ok({});
|
|
763
|
+
} catch (ns) {
|
|
764
|
+
return ns.helper(input);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
})`
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const diagnostics = implementationReturnsResult.check(
|
|
771
|
+
readFileSync(caller, 'utf8'),
|
|
772
|
+
caller
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
expect(diagnostics.length).toBe(1);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
test('flags ns.helper() when a catch param destructures the namespace name', () => {
|
|
779
|
+
writeFile(
|
|
780
|
+
'impl-ns-shadow-catch-destructure.ts',
|
|
781
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
782
|
+
Result.ok({ ok: true });
|
|
783
|
+
`
|
|
784
|
+
);
|
|
785
|
+
const caller = writeFile(
|
|
786
|
+
'caller-ns-shadow-catch-destructure.ts',
|
|
787
|
+
`import * as ns from './impl-ns-shadow-catch-destructure.js';
|
|
788
|
+
|
|
789
|
+
trail("entity.report", {
|
|
790
|
+
blaze: async (input, ctx) => {
|
|
791
|
+
try {
|
|
792
|
+
return Result.ok({});
|
|
793
|
+
} catch ({ ns }) {
|
|
794
|
+
return ns.helper(input);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
})`
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
const diagnostics = implementationReturnsResult.check(
|
|
801
|
+
readFileSync(caller, 'utf8'),
|
|
802
|
+
caller
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
expect(diagnostics.length).toBe(1);
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
describe('scope-frame coverage (TRL-347)', () => {
|
|
810
|
+
test('flags ns.helper() when a const ns shadow sits in a function-expression blaze body (FunctionBody frame)', () => {
|
|
811
|
+
// Regression: oxc-parser emits `FunctionBody` for regular
|
|
812
|
+
// `function expression() { ... }` bodies, not `BlockStatement`. Without
|
|
813
|
+
// a FunctionBody scope-frame collector, the `const ns = ...` at the
|
|
814
|
+
// top of this body would not push a frame and the module-level
|
|
815
|
+
// namespace import would leak through.
|
|
816
|
+
writeFile(
|
|
817
|
+
'impl-ns-shadow-fn-expr.ts',
|
|
818
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
819
|
+
Result.ok({ ok: true });
|
|
820
|
+
`
|
|
821
|
+
);
|
|
822
|
+
const caller = writeFile(
|
|
823
|
+
'caller-ns-shadow-fn-expr.ts',
|
|
824
|
+
`import * as ns from './impl-ns-shadow-fn-expr.js';
|
|
825
|
+
|
|
826
|
+
trail("entity.report", {
|
|
827
|
+
blaze: async function(input, ctx) {
|
|
828
|
+
const ns = { helper: () => ({ ok: true }) };
|
|
829
|
+
return ns.helper(input);
|
|
830
|
+
}
|
|
831
|
+
})`
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
const diagnostics = implementationReturnsResult.check(
|
|
835
|
+
readFileSync(caller, 'utf8'),
|
|
836
|
+
caller
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
expect(diagnostics.length).toBe(1);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
test('flags ns.helper() when a hoisted var ns shadows the namespace import', () => {
|
|
843
|
+
// Regression: a `var ns` nested inside a block hoists to the
|
|
844
|
+
// enclosing blaze's function scope. Without function-body-level var
|
|
845
|
+
// hoisting, the namespace import is read as the receiver and the
|
|
846
|
+
// return is silently treated as a Result helper.
|
|
847
|
+
writeFile(
|
|
848
|
+
'impl-ns-shadow-hoisted-var.ts',
|
|
849
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
850
|
+
Result.ok({ ok: true });
|
|
851
|
+
`
|
|
852
|
+
);
|
|
853
|
+
const caller = writeFile(
|
|
854
|
+
'caller-ns-shadow-hoisted-var.ts',
|
|
855
|
+
`import * as ns from './impl-ns-shadow-hoisted-var.js';
|
|
856
|
+
|
|
857
|
+
trail("entity.report", {
|
|
858
|
+
blaze: async (input, ctx) => {
|
|
859
|
+
if (input) {
|
|
860
|
+
var ns = { helper: () => ({ ok: true }) };
|
|
861
|
+
}
|
|
862
|
+
return ns.helper(input);
|
|
863
|
+
}
|
|
864
|
+
})`
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
const diagnostics = implementationReturnsResult.check(
|
|
868
|
+
readFileSync(caller, 'utf8'),
|
|
869
|
+
caller
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
expect(diagnostics.length).toBe(1);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
test('flags ns.helper() when an unbraced switch case declares a shadowing const', () => {
|
|
876
|
+
// Regression: an unbraced switch case (`case N: const ns = ...;`)
|
|
877
|
+
// does not push a BlockStatement frame. Without the enclosing
|
|
878
|
+
// SwitchStatement collector, the shadow is invisible to the walker.
|
|
879
|
+
writeFile(
|
|
880
|
+
'impl-ns-shadow-switch.ts',
|
|
881
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
882
|
+
Result.ok({ ok: true });
|
|
883
|
+
`
|
|
884
|
+
);
|
|
885
|
+
const caller = writeFile(
|
|
886
|
+
'caller-ns-shadow-switch.ts',
|
|
887
|
+
`import * as ns from './impl-ns-shadow-switch.js';
|
|
888
|
+
|
|
889
|
+
trail("entity.report", {
|
|
890
|
+
blaze: async (input, ctx) => {
|
|
891
|
+
switch (input.kind) {
|
|
892
|
+
case 'a':
|
|
893
|
+
const ns = { helper: () => ({ ok: true }) };
|
|
894
|
+
return ns.helper(input);
|
|
895
|
+
default:
|
|
896
|
+
return Result.ok({});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
})`
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
const diagnostics = implementationReturnsResult.check(
|
|
903
|
+
readFileSync(caller, 'utf8'),
|
|
904
|
+
caller
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
expect(diagnostics.length).toBe(1);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test('flags ns.helper() in a sibling switch case when an earlier case declares a shadowing const', () => {
|
|
911
|
+
// Regression: `switch` shares a single lexical scope across every
|
|
912
|
+
// case. A `const ns = ...` in case 'a' shadows the module-level
|
|
913
|
+
// namespace import for every sibling case (including via
|
|
914
|
+
// fall-through). A per-SwitchCase frame would pop the binding when
|
|
915
|
+
// case 'a' ended and miss the shadow in case 'b'.
|
|
916
|
+
writeFile(
|
|
917
|
+
'impl-ns-shadow-switch-fallthrough.ts',
|
|
918
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
919
|
+
Result.ok({ ok: true });
|
|
920
|
+
`
|
|
921
|
+
);
|
|
922
|
+
const caller = writeFile(
|
|
923
|
+
'caller-ns-shadow-switch-fallthrough.ts',
|
|
924
|
+
`import * as ns from './impl-ns-shadow-switch-fallthrough.js';
|
|
925
|
+
|
|
926
|
+
trail("entity.report", {
|
|
927
|
+
blaze: async (input, ctx) => {
|
|
928
|
+
switch (input.kind) {
|
|
929
|
+
case 'a':
|
|
930
|
+
const ns = { helper: () => ({ ok: true }) };
|
|
931
|
+
return ns.helper(input);
|
|
932
|
+
case 'b':
|
|
933
|
+
return ns.helper(input);
|
|
934
|
+
default:
|
|
935
|
+
return Result.ok({});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
})`
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
const diagnostics = implementationReturnsResult.check(
|
|
942
|
+
readFileSync(caller, 'utf8'),
|
|
943
|
+
caller
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
// Both case 'a' and case 'b' should fire — the const declared in
|
|
947
|
+
// case 'a' is visible across the entire switch scope.
|
|
948
|
+
expect(diagnostics.length).toBe(2);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
test('still resolves ns.helper() correctly when the switch case is braced', () => {
|
|
952
|
+
// Regression: braced cases create a BlockStatement frame nested
|
|
953
|
+
// inside the SwitchStatement frame. The shadow must still apply
|
|
954
|
+
// inside that block, and the sibling case must still see the
|
|
955
|
+
// namespace import (no SwitchStatement-level binding leaks).
|
|
956
|
+
writeFile(
|
|
957
|
+
'impl-ns-shadow-switch-braced.ts',
|
|
958
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
959
|
+
Result.ok({ ok: true });
|
|
960
|
+
`
|
|
961
|
+
);
|
|
962
|
+
const caller = writeFile(
|
|
963
|
+
'caller-ns-shadow-switch-braced.ts',
|
|
964
|
+
`import * as ns from './impl-ns-shadow-switch-braced.js';
|
|
965
|
+
|
|
966
|
+
trail("entity.report", {
|
|
967
|
+
blaze: async (input, ctx) => {
|
|
968
|
+
switch (input.kind) {
|
|
969
|
+
case 'a': {
|
|
970
|
+
const ns = { helper: () => ({ ok: true }) };
|
|
971
|
+
return ns.helper(input);
|
|
972
|
+
}
|
|
973
|
+
case 'b':
|
|
974
|
+
return ns.helper(input);
|
|
975
|
+
default:
|
|
976
|
+
return Result.ok({});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
})`
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
const diagnostics = implementationReturnsResult.check(
|
|
983
|
+
readFileSync(caller, 'utf8'),
|
|
984
|
+
caller
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
// Only the braced case 'a' should fire — the `const ns` is scoped
|
|
988
|
+
// to the inner block and does not leak into case 'b'.
|
|
989
|
+
expect(diagnostics.length).toBe(1);
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
test('flags ns.anything() when the namespace target exports zero Result helpers', () => {
|
|
993
|
+
// Regression: when the target file has no Result-returning exports,
|
|
994
|
+
// `resolveNamespaceSpecifier` used to drop the entry entirely. That
|
|
995
|
+
// made `isNamespaceHelperMemberCall` return false because the
|
|
996
|
+
// namespace binding was absent from the map — the general return-
|
|
997
|
+
// value analysis then fired for the wrong reason. The entry is now
|
|
998
|
+
// recorded with an empty set so the call is correctly identified as
|
|
999
|
+
// a non-Result-helper namespace member call.
|
|
1000
|
+
writeFile(
|
|
1001
|
+
'impl-ns-empty.ts',
|
|
1002
|
+
`export const nonResultFn = async () => ({ ok: true });
|
|
1003
|
+
`
|
|
1004
|
+
);
|
|
1005
|
+
const caller = writeFile(
|
|
1006
|
+
'caller-ns-empty.ts',
|
|
1007
|
+
`import * as ns from './impl-ns-empty.js';
|
|
1008
|
+
|
|
1009
|
+
trail("entity.report", {
|
|
1010
|
+
blaze: async (input, ctx) => {
|
|
1011
|
+
return ns.nonResultFn();
|
|
1012
|
+
}
|
|
1013
|
+
})`
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
const diagnostics = implementationReturnsResult.check(
|
|
1017
|
+
readFileSync(caller, 'utf8'),
|
|
1018
|
+
caller
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
expect(diagnostics.length).toBe(1);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
test('allows named import through `export * from` barrel', () => {
|
|
1026
|
+
writeFile(
|
|
1027
|
+
'impl-star.ts',
|
|
1028
|
+
`export const helper = async (): Promise<Result<object, Error>> =>
|
|
1029
|
+
Result.ok({ ok: true });
|
|
1030
|
+
`
|
|
1031
|
+
);
|
|
1032
|
+
writeFile(
|
|
1033
|
+
'barrel-star.ts',
|
|
1034
|
+
`export * from './impl-star.js';
|
|
1035
|
+
`
|
|
1036
|
+
);
|
|
1037
|
+
const caller = writeFile(
|
|
1038
|
+
'caller-star.ts',
|
|
1039
|
+
`import { helper } from './barrel-star.js';
|
|
1040
|
+
|
|
1041
|
+
trail("entity.report", {
|
|
1042
|
+
blaze: async (input, ctx) => {
|
|
1043
|
+
return helper();
|
|
1044
|
+
}
|
|
1045
|
+
})`
|
|
1046
|
+
);
|
|
1047
|
+
|
|
1048
|
+
const diagnostics = implementationReturnsResult.check(
|
|
1049
|
+
readFileSync(caller, 'utf8'),
|
|
1050
|
+
caller
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
expect(diagnostics.length).toBe(0);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
test('falls back gracefully on `export * from` cycle', () => {
|
|
1057
|
+
writeFile(
|
|
1058
|
+
'star-cycle-a.ts',
|
|
1059
|
+
`export * from './star-cycle-b.js';
|
|
1060
|
+
`
|
|
1061
|
+
);
|
|
1062
|
+
writeFile(
|
|
1063
|
+
'star-cycle-b.ts',
|
|
1064
|
+
`export * from './star-cycle-a.js';
|
|
1065
|
+
`
|
|
1066
|
+
);
|
|
1067
|
+
const caller = writeFile(
|
|
1068
|
+
'caller-star-cycle.ts',
|
|
1069
|
+
`import { helper } from './star-cycle-a.js';
|
|
1070
|
+
|
|
1071
|
+
trail("entity.report", {
|
|
1072
|
+
blaze: async (input, ctx) => {
|
|
1073
|
+
return helper();
|
|
1074
|
+
}
|
|
1075
|
+
})`
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
// Should not hang; the helper is not resolvable through the cycle.
|
|
1079
|
+
const diagnostics = implementationReturnsResult.check(
|
|
1080
|
+
readFileSync(caller, 'utf8'),
|
|
1081
|
+
caller
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
expect(diagnostics.length).toBe(1);
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
test('falls back gracefully on re-export cycle', () => {
|
|
1088
|
+
writeFile(
|
|
1089
|
+
'cycle-a.ts',
|
|
1090
|
+
`export { helper } from './cycle-b.js';
|
|
1091
|
+
`
|
|
1092
|
+
);
|
|
1093
|
+
writeFile(
|
|
1094
|
+
'cycle-b.ts',
|
|
1095
|
+
`export { helper } from './cycle-a.js';
|
|
1096
|
+
`
|
|
1097
|
+
);
|
|
1098
|
+
const caller = writeFile(
|
|
1099
|
+
'caller-cycle.ts',
|
|
1100
|
+
`import { helper } from './cycle-a.js';
|
|
1101
|
+
|
|
1102
|
+
trail("entity.report", {
|
|
1103
|
+
blaze: async (input, ctx) => {
|
|
1104
|
+
return helper();
|
|
1105
|
+
}
|
|
1106
|
+
})`
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
// Should not hang. Since the helper's annotation cannot be resolved
|
|
1110
|
+
// through the cycle, it falls back to flagging the return as non-Result.
|
|
1111
|
+
const diagnostics = implementationReturnsResult.check(
|
|
1112
|
+
readFileSync(caller, 'utf8'),
|
|
1113
|
+
caller
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
expect(diagnostics.length).toBe(1);
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
|
|
107
1121
|
test('allows returning explicitly Result-typed local helpers', () => {
|
|
108
1122
|
const code = `
|
|
109
1123
|
const buildDetail = (trailId: string): Result<object, Error> =>
|