@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,42 +1,16 @@
|
|
|
1
|
+
import { identifierName, isBlazeCall, offsetToLine, parse } from './ast.js';
|
|
2
|
+
import type { AstNode } from './ast.js';
|
|
3
|
+
import { isFrameworkInternalFile, isTestFile } from './scan.js';
|
|
1
4
|
import type { WardenDiagnostic, WardenRule } from './types.js';
|
|
2
|
-
import {
|
|
3
|
-
isFrameworkInternalFile,
|
|
4
|
-
isTestFile,
|
|
5
|
-
stripQuotedContent,
|
|
6
|
-
} from './scan.js';
|
|
7
|
-
|
|
8
|
-
const RESULT_ACCESS_PATTERN =
|
|
9
|
-
/\.(?:isOk|isErr|match|map)\s*\(|\.(?:value|error)\b/;
|
|
10
|
-
const IMPLEMENTATION_CALL_PATTERN = /\.blaze\s*\(/;
|
|
11
|
-
|
|
12
|
-
const isAwaitedImplementationCall = (line: string): boolean => {
|
|
13
|
-
const callIndex = line.indexOf('.blaze(');
|
|
14
|
-
if (callIndex === -1) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const awaitIndex = line.indexOf('await');
|
|
19
|
-
return awaitIndex !== -1 && awaitIndex < callIndex;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const isDirectResultAccess = (line: string): boolean =>
|
|
23
|
-
IMPLEMENTATION_CALL_PATTERN.test(line) &&
|
|
24
|
-
RESULT_ACCESS_PATTERN.test(line) &&
|
|
25
|
-
!isAwaitedImplementationCall(line);
|
|
26
5
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
interface PendingCall {
|
|
36
|
-
line: number;
|
|
37
|
-
remainingLines: number;
|
|
38
|
-
variableName: string;
|
|
39
|
-
}
|
|
6
|
+
const RESULT_ACCESSOR_PROPERTIES = new Set([
|
|
7
|
+
'error',
|
|
8
|
+
'isErr',
|
|
9
|
+
'isOk',
|
|
10
|
+
'map',
|
|
11
|
+
'match',
|
|
12
|
+
'value',
|
|
13
|
+
]);
|
|
40
14
|
|
|
41
15
|
const MISSING_AWAIT_MESSAGE =
|
|
42
16
|
'Missing await: .blaze() returns Promise<Result> after normalization. Use `const result = await trail.blaze(input, ctx)`.';
|
|
@@ -52,91 +26,1151 @@ const createMissingAwaitDiagnostic = (
|
|
|
52
26
|
severity: 'error',
|
|
53
27
|
});
|
|
54
28
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
29
|
+
const isAstLike = (value: unknown): value is AstNode =>
|
|
30
|
+
!!value && typeof value === 'object' && !!(value as AstNode).type;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build parent map for a full AST.
|
|
34
|
+
*
|
|
35
|
+
* Populates a `WeakMap` directly during traversal so we never materialize a
|
|
36
|
+
* strong `Map` holding references to every AST node — the WeakMap lets parent
|
|
37
|
+
* entries be reclaimed alongside their nodes once the rule invocation ends.
|
|
38
|
+
*/
|
|
39
|
+
const buildParentMap = (ast: AstNode): WeakMap<AstNode, AstNode> => {
|
|
40
|
+
const parents = new WeakMap<AstNode, AstNode>();
|
|
41
|
+
|
|
42
|
+
const recordAndVisit = (child: unknown, parent: AstNode): void => {
|
|
43
|
+
if (isAstLike(child)) {
|
|
44
|
+
parents.set(child, parent);
|
|
45
|
+
// eslint-disable-next-line no-use-before-define
|
|
46
|
+
visit(child);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const visit = (node: AstNode): void => {
|
|
51
|
+
for (const val of Object.values(node)) {
|
|
52
|
+
if (Array.isArray(val)) {
|
|
53
|
+
for (const item of val) {
|
|
54
|
+
recordAndVisit(item, node);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
recordAndVisit(val, node);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
visit(ast);
|
|
63
|
+
return parents;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Walk up the parent chain and return true when the expression is awaited
|
|
68
|
+
* before any result-accessing member access fires on it.
|
|
69
|
+
*
|
|
70
|
+
* `await x.blaze(...)` → awaited.
|
|
71
|
+
* `(await x.blaze(...)).isOk()` → awaited (await wraps before member access).
|
|
72
|
+
* `x.blaze(...).isOk()` → NOT awaited (member access on raw call).
|
|
73
|
+
*/
|
|
74
|
+
const TRANSPARENT_WRAPPER_TYPES = new Set([
|
|
75
|
+
'ParenthesizedExpression',
|
|
76
|
+
'TSAsExpression',
|
|
77
|
+
'TSSatisfiesExpression',
|
|
78
|
+
'TSNonNullExpression',
|
|
79
|
+
'TSTypeAssertion',
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const skipParens = (
|
|
83
|
+
node: AstNode,
|
|
84
|
+
parents: WeakMap<AstNode, AstNode>
|
|
85
|
+
): AstNode => {
|
|
86
|
+
let current = node;
|
|
87
|
+
let parent = parents.get(current);
|
|
88
|
+
while (parent?.type && TRANSPARENT_WRAPPER_TYPES.has(parent.type)) {
|
|
89
|
+
current = parent;
|
|
90
|
+
parent = parents.get(current);
|
|
91
|
+
}
|
|
92
|
+
return current;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Walk up through any wrapping parentheses and, when the current node sits
|
|
97
|
+
* in the `consequent` or `alternate` of a `ConditionalExpression`, through
|
|
98
|
+
* that conditional too. Returns the node whose parent should be inspected.
|
|
99
|
+
*
|
|
100
|
+
* Conservative: we only hop across a conditional when the node is one of
|
|
101
|
+
* its branches (not the `test` position). This lets us treat both
|
|
102
|
+
* `const r = cond ? x.blaze(...) : fallback` and
|
|
103
|
+
* `await (cond ? x.blaze(...) : fallback)` correctly without misattributing
|
|
104
|
+
* calls used as conditions.
|
|
105
|
+
*/
|
|
106
|
+
const isBranchOfConditional = (outer: AstNode, parent: AstNode): boolean => {
|
|
107
|
+
if (parent.type !== 'ConditionalExpression') {
|
|
108
|
+
return false;
|
|
61
109
|
}
|
|
110
|
+
const cond = parent as unknown as {
|
|
111
|
+
consequent?: AstNode;
|
|
112
|
+
alternate?: AstNode;
|
|
113
|
+
};
|
|
114
|
+
return cond.consequent === outer || cond.alternate === outer;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Logical expressions (`&&`, `||`, `??`) carry the blaze result through either
|
|
119
|
+
* side. A `.blaze()` on either operand may be the value ultimately bound to a
|
|
120
|
+
* declarator (e.g. `const r = cond && trail.blaze(...)`), so we treat both
|
|
121
|
+
* operands as carriers.
|
|
122
|
+
*/
|
|
123
|
+
const isOperandOfLogical = (outer: AstNode, parent: AstNode): boolean => {
|
|
124
|
+
if (parent.type !== 'LogicalExpression') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const logical = parent as unknown as { left?: AstNode; right?: AstNode };
|
|
128
|
+
return logical.left === outer || logical.right === outer;
|
|
129
|
+
};
|
|
62
130
|
|
|
63
|
-
|
|
64
|
-
|
|
131
|
+
const skipParensAndBranchConditionals = (
|
|
132
|
+
node: AstNode,
|
|
133
|
+
parents: WeakMap<AstNode, AstNode>
|
|
134
|
+
): AstNode => {
|
|
135
|
+
let outer = skipParens(node, parents);
|
|
136
|
+
while (true) {
|
|
137
|
+
const parent = parents.get(outer);
|
|
138
|
+
if (!parent) {
|
|
139
|
+
return outer;
|
|
140
|
+
}
|
|
141
|
+
if (
|
|
142
|
+
!(
|
|
143
|
+
isBranchOfConditional(outer, parent) ||
|
|
144
|
+
isOperandOfLogical(outer, parent)
|
|
145
|
+
)
|
|
146
|
+
) {
|
|
147
|
+
return outer;
|
|
148
|
+
}
|
|
149
|
+
outer = skipParens(parent, parents);
|
|
65
150
|
}
|
|
151
|
+
};
|
|
66
152
|
|
|
67
|
-
|
|
153
|
+
const isAwaited = (
|
|
154
|
+
node: AstNode,
|
|
155
|
+
parents: WeakMap<AstNode, AstNode>
|
|
156
|
+
): boolean => {
|
|
157
|
+
// Walk up through parens and any conditional whose branch is the blaze
|
|
158
|
+
// call. `await (c ? x.blaze(...) : fallback)` awaits the conditional as a
|
|
159
|
+
// whole, so the blaze call in a branch is effectively awaited.
|
|
160
|
+
const outer = skipParensAndBranchConditionals(node, parents);
|
|
161
|
+
return parents.get(outer)?.type === 'AwaitExpression';
|
|
68
162
|
};
|
|
69
163
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
164
|
+
const memberPropertyName = (node: AstNode): string | null => {
|
|
165
|
+
if (
|
|
166
|
+
node.type !== 'MemberExpression' &&
|
|
167
|
+
node.type !== 'StaticMemberExpression'
|
|
168
|
+
) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const prop = (node as unknown as { property?: AstNode }).property;
|
|
172
|
+
if (prop?.type !== 'Identifier') {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return (prop as unknown as { name?: string }).name ?? null;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if the blaze call is directly consumed by a result accessor
|
|
180
|
+
* (e.g. `foo.blaze(...).isOk()` or `foo.blaze(...).value`).
|
|
181
|
+
*/
|
|
182
|
+
const hasDirectResultAccess = (
|
|
183
|
+
blazeCall: AstNode,
|
|
184
|
+
parents: WeakMap<AstNode, AstNode>
|
|
185
|
+
): boolean => {
|
|
186
|
+
// Unwrap wrapping parentheses, conditional branches, and logical-operator
|
|
187
|
+
// operands so `(x.blaze(...)).isOk()`,
|
|
188
|
+
// `(cond ? x.blaze(...) : fb).isOk()`, and
|
|
189
|
+
// `(cond && x.blaze(...)).isOk()` are all detected the same way as the
|
|
190
|
+
// bare `x.blaze(...).isOk()` shape.
|
|
191
|
+
const outer = skipParensAndBranchConditionals(blazeCall, parents);
|
|
192
|
+
const parent = parents.get(outer);
|
|
193
|
+
if (!parent) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const property = memberPropertyName(parent);
|
|
197
|
+
return property !== null && RESULT_ACCESSOR_PROPERTIES.has(property);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* If the blaze call is the init of a VariableDeclarator (directly, through
|
|
202
|
+
* parens, or as a branch of a ConditionalExpression init), return the bound
|
|
203
|
+
* identifier name. Otherwise null.
|
|
204
|
+
*/
|
|
205
|
+
const extractAssignedBinding = (
|
|
206
|
+
blazeCall: AstNode,
|
|
207
|
+
parents: WeakMap<AstNode, AstNode>
|
|
208
|
+
): string | null => {
|
|
209
|
+
const outer = skipParensAndBranchConditionals(blazeCall, parents);
|
|
210
|
+
const parent = parents.get(outer);
|
|
211
|
+
if (!parent || parent.type !== 'VariableDeclarator') {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const { id } = parent as unknown as { id?: AstNode };
|
|
215
|
+
return identifierName(id);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
interface PendingBinding {
|
|
219
|
+
readonly name: string;
|
|
220
|
+
readonly declarationNode: AstNode;
|
|
221
|
+
/** Unique id of the scope frame that owns this binding. */
|
|
222
|
+
readonly scopeId: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const isResultAccessorMember = (node: AstNode): boolean => {
|
|
226
|
+
if (
|
|
227
|
+
node.type !== 'MemberExpression' &&
|
|
228
|
+
node.type !== 'StaticMemberExpression'
|
|
229
|
+
) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const property = memberPropertyName(node);
|
|
233
|
+
return property !== null && RESULT_ACCESSOR_PROPERTIES.has(property);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const getIdentifierObjectName = (node: AstNode): string | null => {
|
|
237
|
+
const { object } = node as unknown as { object?: AstNode };
|
|
238
|
+
return object?.type === 'Identifier' ? identifierName(object) : null;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Scope tracking
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
const collectIdentifierBinding = (pattern: AstNode, out: Set<string>): void => {
|
|
246
|
+
const name = identifierName(pattern);
|
|
247
|
+
if (name) {
|
|
248
|
+
out.add(name);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const collectAssignmentPatternBindings = (
|
|
253
|
+
pattern: AstNode,
|
|
254
|
+
out: Set<string>
|
|
74
255
|
): void => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
variableName,
|
|
79
|
-
});
|
|
256
|
+
const { left } = pattern as unknown as { left?: AstNode };
|
|
257
|
+
// eslint-disable-next-line no-use-before-define
|
|
258
|
+
collectPatternBindings(left, out);
|
|
80
259
|
};
|
|
81
260
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
261
|
+
const collectRestElementBindings = (
|
|
262
|
+
pattern: AstNode,
|
|
263
|
+
out: Set<string>
|
|
264
|
+
): void => {
|
|
265
|
+
const { argument } = pattern as unknown as { argument?: AstNode };
|
|
266
|
+
// eslint-disable-next-line no-use-before-define
|
|
267
|
+
collectPatternBindings(argument, out);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
type PatternHandler = (pattern: AstNode, out: Set<string>) => void;
|
|
271
|
+
|
|
272
|
+
const PATTERN_HANDLERS: Record<string, PatternHandler> = {
|
|
273
|
+
// eslint-disable-next-line no-use-before-define
|
|
274
|
+
ArrayPattern: (p, out) => collectArrayPatternBindings(p, out),
|
|
275
|
+
AssignmentPattern: collectAssignmentPatternBindings,
|
|
276
|
+
Identifier: collectIdentifierBinding,
|
|
277
|
+
// eslint-disable-next-line no-use-before-define
|
|
278
|
+
ObjectPattern: (p, out) => collectObjectPatternBindings(p, out),
|
|
279
|
+
RestElement: collectRestElementBindings,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Collect binding names introduced by a destructuring / parameter pattern.
|
|
284
|
+
* Handles Identifier, AssignmentPattern, ObjectPattern, ArrayPattern,
|
|
285
|
+
* and RestElement shapes.
|
|
286
|
+
*
|
|
287
|
+
* `function` declaration (instead of an arrow) so it can be hoisted for the
|
|
288
|
+
* mutually recursive calls from the array / object pattern helpers below.
|
|
289
|
+
*/
|
|
290
|
+
// biome-ignore lint/style/useConst: hoisted for mutual recursion
|
|
291
|
+
// eslint-disable-next-line func-style
|
|
292
|
+
function collectPatternBindings(
|
|
293
|
+
pattern: AstNode | undefined,
|
|
294
|
+
out: Set<string>
|
|
295
|
+
): void {
|
|
296
|
+
if (!pattern) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const handler = PATTERN_HANDLERS[pattern.type];
|
|
300
|
+
if (handler) {
|
|
301
|
+
handler(pattern, out);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const collectArrayPatternBindings = (
|
|
306
|
+
pattern: AstNode,
|
|
307
|
+
out: Set<string>
|
|
308
|
+
): void => {
|
|
309
|
+
const { elements } = pattern as unknown as {
|
|
310
|
+
elements?: readonly (AstNode | null)[];
|
|
311
|
+
};
|
|
312
|
+
if (!elements) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
for (const element of elements) {
|
|
316
|
+
if (element) {
|
|
317
|
+
// eslint-disable-next-line no-use-before-define
|
|
318
|
+
collectPatternBindings(element, out);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const collectObjectPatternBindings = (
|
|
324
|
+
pattern: AstNode,
|
|
325
|
+
out: Set<string>
|
|
326
|
+
): void => {
|
|
327
|
+
const { properties } = pattern as unknown as {
|
|
328
|
+
properties?: readonly AstNode[];
|
|
329
|
+
};
|
|
330
|
+
if (!properties) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
for (const prop of properties) {
|
|
334
|
+
if (prop.type === 'RestElement') {
|
|
335
|
+
// eslint-disable-next-line no-use-before-define
|
|
336
|
+
collectPatternBindings(prop, out);
|
|
337
|
+
} else {
|
|
338
|
+
// Property node: value holds the binding pattern.
|
|
339
|
+
const { value } = prop as unknown as { value?: AstNode };
|
|
340
|
+
// eslint-disable-next-line no-use-before-define
|
|
341
|
+
collectPatternBindings(value, out);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const SCOPE_NODE_TYPES = new Set([
|
|
347
|
+
'FunctionDeclaration',
|
|
348
|
+
'FunctionExpression',
|
|
349
|
+
'ArrowFunctionExpression',
|
|
350
|
+
'BlockStatement',
|
|
351
|
+
'StaticBlock',
|
|
352
|
+
'CatchClause',
|
|
353
|
+
'ForStatement',
|
|
354
|
+
'ForInStatement',
|
|
355
|
+
'ForOfStatement',
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
const isScopeBoundary = (node: AstNode): boolean =>
|
|
359
|
+
SCOPE_NODE_TYPES.has(node.type);
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Collect the local binding names introduced directly in this scope's own
|
|
363
|
+
* declarations (params + var/let/const/catch/for declarations), without
|
|
364
|
+
* descending into nested function or block scopes.
|
|
365
|
+
*
|
|
366
|
+
* For function-like scopes, the body (a BlockStatement) is its own child
|
|
367
|
+
* scope — we do not merge params into it. Params and body bindings are
|
|
368
|
+
* treated as sibling frames via the scope walk: when entering the function,
|
|
369
|
+
* we push a frame with params; when entering its body block, we push another
|
|
370
|
+
* frame with the block's declarations. Nearest-scope resolution treats them
|
|
371
|
+
* as a single effective scope chain.
|
|
372
|
+
*/
|
|
373
|
+
const FUNCTION_SCOPE_TYPES = new Set([
|
|
374
|
+
'FunctionDeclaration',
|
|
375
|
+
'FunctionExpression',
|
|
376
|
+
'ArrowFunctionExpression',
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
const collectVariableDeclarationBindings = (
|
|
380
|
+
declNode: AstNode | undefined,
|
|
381
|
+
out: Set<string>
|
|
382
|
+
): void => {
|
|
383
|
+
if (!declNode || declNode.type !== 'VariableDeclaration') {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const declarators = (
|
|
387
|
+
declNode as unknown as {
|
|
388
|
+
declarations?: readonly AstNode[];
|
|
389
|
+
}
|
|
390
|
+
).declarations;
|
|
391
|
+
if (!declarators) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
for (const d of declarators) {
|
|
395
|
+
const { id } = d as unknown as { id?: AstNode };
|
|
396
|
+
collectPatternBindings(id, out);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const getVariableDeclarationKind = (
|
|
401
|
+
declNode: AstNode | undefined
|
|
402
|
+
): string | null => {
|
|
403
|
+
if (!declNode || declNode.type !== 'VariableDeclaration') {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return (declNode as unknown as { kind?: string }).kind ?? null;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/** True if declaration is `var` (function/program-scoped, hoistable). */
|
|
410
|
+
const isVarDeclaration = (declNode: AstNode | undefined): boolean =>
|
|
411
|
+
getVariableDeclarationKind(declNode) === 'var';
|
|
412
|
+
|
|
413
|
+
/** Collect only `let`/`const` declarator bindings (block-scoped). */
|
|
414
|
+
const collectBlockScopedDeclaratorBindings = (
|
|
415
|
+
declNode: AstNode | undefined,
|
|
416
|
+
out: Set<string>
|
|
88
417
|
): void => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
418
|
+
const kind = getVariableDeclarationKind(declNode);
|
|
419
|
+
if (!kind || kind === 'var') {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
collectVariableDeclarationBindings(declNode, out);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
interface FunctionScopeBindings {
|
|
426
|
+
readonly bindings: Set<string>;
|
|
427
|
+
readonly paramBindings: Set<string>;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const collectParamBindings = (scope: AstNode): Set<string> => {
|
|
431
|
+
const paramBindings = new Set<string>();
|
|
432
|
+
const { params } = scope as unknown as { params?: readonly AstNode[] };
|
|
433
|
+
if (params) {
|
|
434
|
+
for (const param of params) {
|
|
435
|
+
collectPatternBindings(param, paramBindings);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return paramBindings;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const addHoistedVarsFromBody = (scope: AstNode, out: Set<string>): void => {
|
|
442
|
+
const { body } = scope as unknown as { body?: AstNode };
|
|
443
|
+
if (!(body && isAstLike(body))) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const hoisted = new Set<string>();
|
|
447
|
+
// eslint-disable-next-line no-use-before-define
|
|
448
|
+
collectHoistedVarBindings(body, hoisted);
|
|
449
|
+
for (const name of hoisted) {
|
|
450
|
+
out.add(name);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const collectFunctionScopeBindingsEx = (
|
|
455
|
+
scope: AstNode
|
|
456
|
+
): FunctionScopeBindings => {
|
|
457
|
+
const paramBindings = collectParamBindings(scope);
|
|
458
|
+
const bindings = new Set<string>(paramBindings);
|
|
459
|
+
addHoistedVarsFromBody(scope, bindings);
|
|
460
|
+
return { bindings, paramBindings };
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const collectFunctionScopeBindings = (scope: AstNode): Set<string> =>
|
|
464
|
+
collectFunctionScopeBindingsEx(scope).bindings;
|
|
465
|
+
|
|
466
|
+
const collectCatchScopeBindings = (scope: AstNode): Set<string> => {
|
|
467
|
+
const bindings = new Set<string>();
|
|
468
|
+
const { param } = scope as unknown as { param?: AstNode };
|
|
469
|
+
collectPatternBindings(param, bindings);
|
|
470
|
+
return bindings;
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const collectForScopeBindings = (scope: AstNode): Set<string> => {
|
|
474
|
+
const bindings = new Set<string>();
|
|
475
|
+
if (scope.type === 'ForStatement') {
|
|
476
|
+
const { init } = scope as unknown as { init?: AstNode };
|
|
477
|
+
collectBlockScopedDeclaratorBindings(init, bindings);
|
|
478
|
+
} else {
|
|
479
|
+
const { left } = scope as unknown as { left?: AstNode };
|
|
480
|
+
collectBlockScopedDeclaratorBindings(left, bindings);
|
|
481
|
+
}
|
|
482
|
+
return bindings;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const addFunctionDeclarationName = (stmt: AstNode, out: Set<string>): void => {
|
|
486
|
+
if (stmt.type !== 'FunctionDeclaration') {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const { id } = stmt as unknown as { id?: AstNode };
|
|
490
|
+
const fnName = identifierName(id);
|
|
491
|
+
if (fnName) {
|
|
492
|
+
out.add(fnName);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const addClassDeclarationName = (stmt: AstNode, out: Set<string>): void => {
|
|
497
|
+
if (stmt.type !== 'ClassDeclaration') {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const { id } = stmt as unknown as { id?: AstNode };
|
|
501
|
+
const className = identifierName(id);
|
|
502
|
+
if (className) {
|
|
503
|
+
out.add(className);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const collectBlockScopedStatementListBindings = (
|
|
508
|
+
statements: readonly AstNode[] | undefined,
|
|
509
|
+
out: Set<string>
|
|
510
|
+
): void => {
|
|
511
|
+
if (!statements) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
for (const stmt of statements) {
|
|
515
|
+
collectBlockScopedDeclaratorBindings(stmt, out);
|
|
516
|
+
addFunctionDeclarationName(stmt, out);
|
|
517
|
+
addClassDeclarationName(stmt, out);
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const collectBlockStatementBindings = (scope: AstNode): Set<string> => {
|
|
522
|
+
const bindings = new Set<string>();
|
|
523
|
+
const { body } = scope as unknown as { body?: readonly AstNode[] };
|
|
524
|
+
collectBlockScopedStatementListBindings(body, bindings);
|
|
525
|
+
// Static initializer blocks own their own VariableEnvironment (per ES spec),
|
|
526
|
+
// so `var` declarations inside them do not escape into the enclosing class
|
|
527
|
+
// or function scope. `collectHoistedVarBindings` correctly refuses to cross
|
|
528
|
+
// a `StaticBlock` boundary from the outside, which means nothing else will
|
|
529
|
+
// register these bindings. Hoist them here so `var result = trail.blaze(...)`
|
|
530
|
+
// inside a `static { ... }` block is tracked against the block itself.
|
|
531
|
+
if (scope.type === 'StaticBlock') {
|
|
532
|
+
// `collectHoistedVarBindings` is called with the StaticBlock as the root,
|
|
533
|
+
// so the own-VariableEnvironment check (which refuses to descend *into* a
|
|
534
|
+
// nested StaticBlock) does not short-circuit traversal of the node itself.
|
|
535
|
+
// eslint-disable-next-line no-use-before-define
|
|
536
|
+
collectHoistedVarBindings(scope, bindings);
|
|
537
|
+
}
|
|
538
|
+
return bindings;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Collect the local binding names introduced directly in this scope's own
|
|
543
|
+
* declarations (params + var/let/const/catch/for declarations), without
|
|
544
|
+
* descending into nested function or block scopes.
|
|
545
|
+
*/
|
|
546
|
+
const collectScopeBindings = (scope: AstNode): Set<string> => {
|
|
547
|
+
if (FUNCTION_SCOPE_TYPES.has(scope.type)) {
|
|
548
|
+
return collectFunctionScopeBindings(scope);
|
|
549
|
+
}
|
|
550
|
+
if (scope.type === 'CatchClause') {
|
|
551
|
+
return collectCatchScopeBindings(scope);
|
|
552
|
+
}
|
|
553
|
+
if (
|
|
554
|
+
scope.type === 'ForStatement' ||
|
|
555
|
+
scope.type === 'ForInStatement' ||
|
|
556
|
+
scope.type === 'ForOfStatement'
|
|
557
|
+
) {
|
|
558
|
+
return collectForScopeBindings(scope);
|
|
559
|
+
}
|
|
560
|
+
if (scope.type === 'BlockStatement' || scope.type === 'StaticBlock') {
|
|
561
|
+
return collectBlockStatementBindings(scope);
|
|
562
|
+
}
|
|
563
|
+
return new Set();
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
type ScopeKind = 'program' | 'function' | 'block' | 'for' | 'catch';
|
|
567
|
+
|
|
568
|
+
interface ScopeFrame {
|
|
569
|
+
readonly id: number;
|
|
570
|
+
readonly kind: ScopeKind;
|
|
571
|
+
readonly bindings: Set<string>;
|
|
572
|
+
/**
|
|
573
|
+
* For function frames: names that came from parameters (not hoisted `var`s).
|
|
574
|
+
* A `var` declaration with the same name as a parameter is redundant in JS —
|
|
575
|
+
* the parameter is the real binding. We track params separately so we don't
|
|
576
|
+
* register a pending `.blaze()` binding that is actually shadowed by a param.
|
|
577
|
+
*/
|
|
578
|
+
readonly paramBindings?: Set<string>;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const scopeKindForNode = (node: AstNode): ScopeKind => {
|
|
582
|
+
if (FUNCTION_SCOPE_TYPES.has(node.type)) {
|
|
583
|
+
return 'function';
|
|
584
|
+
}
|
|
585
|
+
if (node.type === 'CatchClause') {
|
|
586
|
+
return 'catch';
|
|
587
|
+
}
|
|
588
|
+
if (
|
|
589
|
+
node.type === 'ForStatement' ||
|
|
590
|
+
node.type === 'ForInStatement' ||
|
|
591
|
+
node.type === 'ForOfStatement'
|
|
592
|
+
) {
|
|
593
|
+
return 'for';
|
|
594
|
+
}
|
|
595
|
+
return 'block';
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* True when a nested node owns its own VariableEnvironment and therefore stops
|
|
600
|
+
* `var` hoisting from crossing into the enclosing function/program scope.
|
|
601
|
+
* Covers function-like nodes and `StaticBlock` (ECMAScript: static blocks
|
|
602
|
+
* introduce their own LexicalEnvironment and VariableEnvironment).
|
|
603
|
+
*/
|
|
604
|
+
const ownsVariableEnvironment = (node: AstNode): boolean =>
|
|
605
|
+
FUNCTION_SCOPE_TYPES.has(node.type) || node.type === 'StaticBlock';
|
|
606
|
+
|
|
607
|
+
const collectHoistedVarBindings = (root: AstNode, out: Set<string>): void => {
|
|
608
|
+
const visit = (node: AstNode, isRoot: boolean): void => {
|
|
609
|
+
// Nested var-environment owners (functions, static blocks) do not leak
|
|
610
|
+
// their `var`s to the enclosing scope.
|
|
611
|
+
if (!isRoot && ownsVariableEnvironment(node)) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (node.type === 'VariableDeclaration' && isVarDeclaration(node)) {
|
|
615
|
+
collectVariableDeclarationBindings(node, out);
|
|
616
|
+
}
|
|
617
|
+
for (const val of Object.values(node)) {
|
|
618
|
+
if (Array.isArray(val)) {
|
|
619
|
+
for (const item of val) {
|
|
620
|
+
if (isAstLike(item)) {
|
|
621
|
+
visit(item, false);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} else if (isAstLike(val)) {
|
|
625
|
+
visit(val, false);
|
|
98
626
|
}
|
|
99
627
|
}
|
|
628
|
+
};
|
|
629
|
+
visit(root, true);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
interface AnalyzeState {
|
|
633
|
+
readonly parents: WeakMap<AstNode, AstNode>;
|
|
634
|
+
readonly diagnostics: WardenDiagnostic[];
|
|
635
|
+
readonly sourceCode: string;
|
|
636
|
+
readonly filePath: string;
|
|
637
|
+
/** Pending `.blaze()` bindings seen so far, keyed by scope id + name. */
|
|
638
|
+
readonly pendingByScopeAndName: Map<string, PendingBinding>;
|
|
639
|
+
readonly scopeStack: ScopeFrame[];
|
|
640
|
+
readonly reportedAt: Set<number>;
|
|
641
|
+
/**
|
|
642
|
+
* Monotonic counter for scope frame ids. Intentionally mutable — every other
|
|
643
|
+
* field on `AnalyzeState` is `readonly`, but this one is incremented with
|
|
644
|
+
* `state.nextScopeId += 1` each time a scope frame is pushed so sibling
|
|
645
|
+
* scopes get distinct ids. Keeping it as a plain number (rather than a
|
|
646
|
+
* boxed `{ current: number }`) avoids an extra allocation and indirection
|
|
647
|
+
* on a hot path; the mutability is local to `pushScopeIfBoundary`.
|
|
648
|
+
*/
|
|
649
|
+
nextScopeId: number;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const pendingKey = (scopeId: number, name: string): string =>
|
|
653
|
+
`${scopeId}\u0000${name}`;
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Resolve an identifier use to the nearest enclosing scope frame that binds
|
|
657
|
+
* the name. Returns `null` if no frame binds it.
|
|
658
|
+
*/
|
|
659
|
+
const resolveNearestScope = (
|
|
660
|
+
name: string,
|
|
661
|
+
stack: readonly ScopeFrame[]
|
|
662
|
+
): ScopeFrame | null => {
|
|
663
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
664
|
+
const frame = stack[i];
|
|
665
|
+
if (frame && frame.bindings.has(name)) {
|
|
666
|
+
return frame;
|
|
667
|
+
}
|
|
100
668
|
}
|
|
669
|
+
return null;
|
|
101
670
|
};
|
|
102
671
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
672
|
+
/**
|
|
673
|
+
* Resolve the blaze call to a `{ name, declarator }` pair when it is the init
|
|
674
|
+
* of a `VariableDeclarator` (directly, through parens, or as a branch of a
|
|
675
|
+
* `ConditionalExpression` init). Returns null otherwise.
|
|
676
|
+
*/
|
|
677
|
+
const resolveBlazeBinding = (
|
|
678
|
+
blazeCall: AstNode,
|
|
679
|
+
parents: WeakMap<AstNode, AstNode>
|
|
680
|
+
): { readonly name: string; readonly declarator: AstNode } | null => {
|
|
681
|
+
const name = extractAssignedBinding(blazeCall, parents);
|
|
682
|
+
if (!name) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
// Mirror `extractAssignedBinding`: unwrap parens and branch-position
|
|
686
|
+
// conditionals so the stored declaration node points at the
|
|
687
|
+
// `VariableDeclarator`, not at an intermediate `ParenthesizedExpression`
|
|
688
|
+
// or `ConditionalExpression`.
|
|
689
|
+
const outer = skipParensAndBranchConditionals(blazeCall, parents);
|
|
690
|
+
const declarator = parents.get(outer);
|
|
691
|
+
return declarator ? { declarator, name } : null;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Resolve the blaze call to a `{ name, assignment }` pair when it is the RHS
|
|
696
|
+
* of a plain `=` `AssignmentExpression` with an `Identifier` LHS (directly,
|
|
697
|
+
* through parens, or as a branch of a conditional/logical expression).
|
|
698
|
+
*
|
|
699
|
+
* Covers patterns like:
|
|
700
|
+
* let result;
|
|
701
|
+
* result = trail.blaze(input, ctx);
|
|
702
|
+
* result.isOk();
|
|
703
|
+
*
|
|
704
|
+
* Member-expression LHS (`obj.result = blaze(...)`) is intentionally skipped —
|
|
705
|
+
* those are property writes, not bare bindings we can track by name.
|
|
706
|
+
*/
|
|
707
|
+
const extractPlainIdentifierAssignmentName = (
|
|
708
|
+
parent: AstNode | undefined
|
|
709
|
+
): string | null => {
|
|
710
|
+
if (!parent || parent.type !== 'AssignmentExpression') {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const { operator, left } = parent as unknown as {
|
|
714
|
+
operator?: string;
|
|
715
|
+
left?: AstNode;
|
|
716
|
+
};
|
|
717
|
+
// Only plain `=` assignments to a bare identifier. Member-expression LHS
|
|
718
|
+
// (`obj.result = blaze(...)`) is a property write, not a bare binding we
|
|
719
|
+
// can track by name.
|
|
720
|
+
if (operator !== '=' || !left || left.type !== 'Identifier') {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
return identifierName(left);
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const resolveBlazeAssignment = (
|
|
727
|
+
blazeCall: AstNode,
|
|
728
|
+
parents: WeakMap<AstNode, AstNode>
|
|
729
|
+
): { readonly name: string; readonly assignment: AstNode } | null => {
|
|
730
|
+
const outer = skipParensAndBranchConditionals(blazeCall, parents);
|
|
731
|
+
const parent = parents.get(outer);
|
|
732
|
+
const name = extractPlainIdentifierAssignmentName(parent);
|
|
733
|
+
return name && parent ? { assignment: parent, name } : null;
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* True when `declarator` is a `VariableDeclarator` whose parent
|
|
738
|
+
* `VariableDeclaration` uses the `var` kind. Such declarators re-initialize
|
|
739
|
+
* a same-named function parameter rather than shadowing it, because `var`
|
|
740
|
+
* and parameters share the function's VariableEnvironment.
|
|
741
|
+
*/
|
|
742
|
+
const isVarDeclaratorOfParamName = (
|
|
743
|
+
declarator: AstNode,
|
|
744
|
+
parents: WeakMap<AstNode, AstNode>
|
|
745
|
+
): boolean => {
|
|
746
|
+
if (declarator.type !== 'VariableDeclarator') {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
const decl = parents.get(declarator);
|
|
750
|
+
return isVarDeclaration(decl);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* True when `node` is a plain `=` `AssignmentExpression` with an `Identifier`
|
|
755
|
+
* LHS. Such an assignment writes to the existing binding for that name — if
|
|
756
|
+
* that name is a function parameter, the assignment re-initializes the
|
|
757
|
+
* parameter's slot in the VariableEnvironment, just like `var <name> = ...`.
|
|
758
|
+
* Compound assignments (`+=`, `??=`, etc.) are excluded because they do not
|
|
759
|
+
* unconditionally replace the slot with the blaze result.
|
|
760
|
+
*/
|
|
761
|
+
const isAssignmentToParamName = (node: AstNode): boolean => {
|
|
762
|
+
if (node.type !== 'AssignmentExpression') {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
const { operator, left } = node as unknown as {
|
|
766
|
+
operator?: string;
|
|
767
|
+
left?: AstNode;
|
|
768
|
+
};
|
|
769
|
+
return operator === '=' && left?.type === 'Identifier';
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const recordPendingBinding = (
|
|
773
|
+
blazeCall: AstNode,
|
|
774
|
+
state: AnalyzeState
|
|
775
|
+
): void => {
|
|
776
|
+
const binding =
|
|
777
|
+
resolveBlazeBinding(blazeCall, state.parents) ??
|
|
778
|
+
(() => {
|
|
779
|
+
const asn = resolveBlazeAssignment(blazeCall, state.parents);
|
|
780
|
+
return asn ? { declarator: asn.assignment, name: asn.name } : null;
|
|
781
|
+
})();
|
|
782
|
+
if (!binding) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const { name, declarator } = binding;
|
|
786
|
+
// The pending binding lives in the nearest scope that declares `name`.
|
|
787
|
+
// That is always the innermost scope in the current stack, because the
|
|
788
|
+
// variable declaration's id was contributed to its enclosing scope's
|
|
789
|
+
// bindings when that scope was entered.
|
|
790
|
+
const owningFrame = resolveNearestScope(name, state.scopeStack);
|
|
791
|
+
if (!owningFrame) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// If the name resolves to a function parameter, the `var` that visually
|
|
795
|
+
// appears to declare it is redundant — the parameter is the real binding,
|
|
796
|
+
// and parameters are not pending `.blaze()` results.
|
|
797
|
+
//
|
|
798
|
+
// Carve-out: a `var <name> = blaze(...)` *initializer* inside the same
|
|
799
|
+
// function body legitimately re-binds the parameter at that point. `var`
|
|
800
|
+
// and parameters share the function's VariableEnvironment, so the `var`
|
|
801
|
+
// writes to the existing parameter slot and the subsequent use resolves
|
|
802
|
+
// to the freshly-assigned `.blaze()` result. Treat that as a pending
|
|
803
|
+
// binding.
|
|
804
|
+
//
|
|
805
|
+
// The same logic applies to a bare `result = blaze(...)` assignment: it
|
|
806
|
+
// writes to the parameter's existing slot in the same VariableEnvironment,
|
|
807
|
+
// so the subsequent `result.isOk()` observes the blaze result. Only
|
|
808
|
+
// compound assignments (`+=`, `??=`, etc.) and member-expression LHS fall
|
|
809
|
+
// through the param-shadow suppression, because they do not
|
|
810
|
+
// unconditionally replace the parameter slot with the blaze result.
|
|
811
|
+
if (
|
|
812
|
+
owningFrame.paramBindings?.has(name) &&
|
|
813
|
+
!isVarDeclaratorOfParamName(declarator, state.parents) &&
|
|
814
|
+
!isAssignmentToParamName(declarator)
|
|
815
|
+
) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
state.pendingByScopeAndName.set(pendingKey(owningFrame.id, name), {
|
|
819
|
+
declarationNode: declarator,
|
|
820
|
+
name,
|
|
821
|
+
scopeId: owningFrame.id,
|
|
822
|
+
});
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* True when `expr`, descended through wrapping parens, conditional branches,
|
|
827
|
+
* and logical-operator operands, contains a `.blaze()` call that would be
|
|
828
|
+
* registered by `recordPendingBinding` for this assignment.
|
|
829
|
+
*
|
|
830
|
+
* This mirrors the *upward* carrier walk done by
|
|
831
|
+
* `skipParensAndBranchConditionals` — if a blaze call is anywhere along a
|
|
832
|
+
* carrier path descending from `expr`, then visiting that blaze call will
|
|
833
|
+
* re-register the pending binding, so we must not clear it on the way in.
|
|
834
|
+
*/
|
|
835
|
+
type CarrierChildExtractor = (
|
|
836
|
+
expr: AstNode
|
|
837
|
+
) => readonly (AstNode | undefined)[];
|
|
838
|
+
|
|
839
|
+
const CARRIER_CHILDREN: Record<string, CarrierChildExtractor> = {
|
|
840
|
+
ConditionalExpression: (expr) => {
|
|
841
|
+
const { consequent, alternate } = expr as unknown as {
|
|
842
|
+
consequent?: AstNode;
|
|
843
|
+
alternate?: AstNode;
|
|
844
|
+
};
|
|
845
|
+
return [consequent, alternate];
|
|
846
|
+
},
|
|
847
|
+
LogicalExpression: (expr) => {
|
|
848
|
+
const { left, right } = expr as unknown as {
|
|
849
|
+
left?: AstNode;
|
|
850
|
+
right?: AstNode;
|
|
851
|
+
};
|
|
852
|
+
return [left, right];
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const unwrapTransparentWrapper = (expr: AstNode): AstNode | undefined =>
|
|
857
|
+
(expr as unknown as { expression?: AstNode }).expression;
|
|
858
|
+
|
|
859
|
+
// biome-ignore lint/style/useConst: hoisted for recursive call
|
|
860
|
+
// eslint-disable-next-line func-style
|
|
861
|
+
function rhsCarriesBlazeReinit(expr: AstNode | undefined): boolean {
|
|
862
|
+
if (!expr) {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
if (TRANSPARENT_WRAPPER_TYPES.has(expr.type)) {
|
|
866
|
+
return rhsCarriesBlazeReinit(unwrapTransparentWrapper(expr));
|
|
867
|
+
}
|
|
868
|
+
const extractor = CARRIER_CHILDREN[expr.type];
|
|
869
|
+
if (extractor) {
|
|
870
|
+
return extractor(expr).some(rhsCarriesBlazeReinit);
|
|
871
|
+
}
|
|
872
|
+
return isBlazeCall(expr);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Nullish/falsy-skip compound assignments (`??=`, `||=`) only write to the slot
|
|
877
|
+
* when the LHS is nullish or falsy. A pending `.blaze()` binding holds a
|
|
878
|
+
* truthy `Promise<Result>`, so the RHS never runs and the pending binding must
|
|
879
|
+
* survive them.
|
|
880
|
+
*
|
|
881
|
+
* `&&=` is intentionally excluded: it writes when the LHS is truthy, so a
|
|
882
|
+
* pending `Promise<Result>` is *always* overwritten by the RHS. That matches
|
|
883
|
+
* the clearing behavior of mathematical compound operators (`+=`, `-=`, ...).
|
|
884
|
+
*/
|
|
885
|
+
const NULLISH_SKIP_OPERATORS = new Set(['??=', '||=']);
|
|
886
|
+
|
|
887
|
+
interface IdentifierAssignment {
|
|
888
|
+
readonly operator: string;
|
|
889
|
+
readonly name: string;
|
|
890
|
+
readonly right: AstNode | undefined;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const extractIdentifierAssignment = (
|
|
894
|
+
node: AstNode
|
|
895
|
+
): IdentifierAssignment | null => {
|
|
896
|
+
if (node.type !== 'AssignmentExpression') {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
const { operator, left, right } = node as unknown as {
|
|
900
|
+
operator?: string;
|
|
901
|
+
left?: AstNode;
|
|
902
|
+
right?: AstNode;
|
|
903
|
+
};
|
|
904
|
+
if (!(operator && left) || left.type !== 'Identifier') {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const name = identifierName(left);
|
|
908
|
+
return name ? { name, operator, right } : null;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const resolvePendingKeyFor = (
|
|
912
|
+
name: string,
|
|
913
|
+
state: AnalyzeState
|
|
914
|
+
): string | null => {
|
|
915
|
+
const frame = resolveNearestScope(name, state.scopeStack);
|
|
916
|
+
if (!frame) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
const key = pendingKey(frame.id, name);
|
|
920
|
+
return state.pendingByScopeAndName.has(key) ? key : null;
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Handle a plain `=` assignment (or clearing compound assignment) to a bare
|
|
925
|
+
* identifier whose name currently has a pending `.blaze()` binding in scope.
|
|
926
|
+
*
|
|
927
|
+
* A plain `=` whose RHS carries another blaze call leaves the pending entry
|
|
928
|
+
* alone — `recordPendingBinding` will re-register it when the blaze call
|
|
929
|
+
* itself is visited. Otherwise, clear the pending entry: the identifier has
|
|
930
|
+
* been overwritten with a non-Result value, so the original
|
|
931
|
+
* `result.isOk()`-style diagnostic no longer applies.
|
|
932
|
+
*
|
|
933
|
+
* Nullish/falsy-skip compound assignments (`??=`, `||=`) are ignored — a
|
|
934
|
+
* truthy pending `Promise<Result>` causes the RHS to be skipped, so the
|
|
935
|
+
* pending binding is preserved. `&&=` is *not* in this set: a truthy LHS
|
|
936
|
+
* causes the RHS to always run, overwriting the pending slot, so it falls
|
|
937
|
+
* through to the clearing path alongside `+=`, `-=`, etc. Member-expression
|
|
938
|
+
* LHS is ignored because it writes a property, not the tracked identifier.
|
|
939
|
+
*/
|
|
940
|
+
const handleAssignmentReassignment = (
|
|
941
|
+
node: AstNode,
|
|
942
|
+
state: AnalyzeState
|
|
109
943
|
): void => {
|
|
110
|
-
|
|
111
|
-
|
|
944
|
+
const assignment = extractIdentifierAssignment(node);
|
|
945
|
+
if (!assignment || NULLISH_SKIP_OPERATORS.has(assignment.operator)) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const key = resolvePendingKeyFor(assignment.name, state);
|
|
949
|
+
if (!key) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
// Plain `=` with a blaze-carrying RHS will re-register via
|
|
953
|
+
// `recordPendingBinding` when the blaze call itself is visited. Other
|
|
954
|
+
// compound operators (`+=`, `-=`, `*=`, etc.) produce a primitive value
|
|
955
|
+
// from the existing slot, so they always clear.
|
|
956
|
+
if (assignment.operator === '=' && rhsCarriesBlazeReinit(assignment.right)) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
state.pendingByScopeAndName.delete(key);
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
const reportMissingAwait = (node: AstNode, state: AnalyzeState): void => {
|
|
963
|
+
if (state.reportedAt.has(node.start)) {
|
|
112
964
|
return;
|
|
113
965
|
}
|
|
966
|
+
state.reportedAt.add(node.start);
|
|
967
|
+
state.diagnostics.push(
|
|
968
|
+
createMissingAwaitDiagnostic(
|
|
969
|
+
state.filePath,
|
|
970
|
+
offsetToLine(state.sourceCode, node.start)
|
|
971
|
+
)
|
|
972
|
+
);
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const findPendingBindingForUse = (
|
|
976
|
+
node: AstNode,
|
|
977
|
+
state: AnalyzeState
|
|
978
|
+
): PendingBinding | null => {
|
|
979
|
+
if (!isResultAccessorMember(node)) {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
const name = getIdentifierObjectName(node);
|
|
983
|
+
if (!name) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
const frame = resolveNearestScope(name, state.scopeStack);
|
|
987
|
+
if (!frame) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
return state.pendingByScopeAndName.get(pendingKey(frame.id, name)) ?? null;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
const checkPendingAccess = (node: AstNode, state: AnalyzeState): void => {
|
|
994
|
+
const binding = findPendingBindingForUse(node, state);
|
|
995
|
+
if (!binding) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
// Declaration must precede the use. Use source offsets for ordering.
|
|
999
|
+
if (node.start < binding.declarationNode.end) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
reportMissingAwait(node, state);
|
|
1003
|
+
};
|
|
114
1004
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1005
|
+
/**
|
|
1006
|
+
* If the blaze call is the init of a VariableDeclarator whose id is an
|
|
1007
|
+
* ObjectPattern that destructures any known Result accessor property,
|
|
1008
|
+
* return the declarator node. Otherwise null.
|
|
1009
|
+
*
|
|
1010
|
+
* Catches the core missing-await shape when written as destructuring:
|
|
1011
|
+
* `const { isOk } = entityShow.blaze(input, ctx)` — no await, immediate
|
|
1012
|
+
* access to a Result accessor, should fire.
|
|
1013
|
+
*/
|
|
1014
|
+
const propertyDestructuresResultAccessor = (prop: AstNode): boolean => {
|
|
1015
|
+
if (prop.type === 'RestElement') {
|
|
1016
|
+
return false;
|
|
118
1017
|
}
|
|
1018
|
+
const { key } = prop as unknown as { key?: AstNode };
|
|
1019
|
+
const keyName = identifierName(key);
|
|
1020
|
+
return keyName !== null && RESULT_ACCESSOR_PROPERTIES.has(keyName);
|
|
1021
|
+
};
|
|
119
1022
|
|
|
120
|
-
|
|
1023
|
+
const objectPatternHasResultAccessorKey = (pattern: AstNode): boolean => {
|
|
1024
|
+
const { properties } = pattern as unknown as {
|
|
1025
|
+
properties?: readonly AstNode[];
|
|
1026
|
+
};
|
|
1027
|
+
return properties?.some(propertyDestructuresResultAccessor) ?? false;
|
|
121
1028
|
};
|
|
122
1029
|
|
|
123
|
-
const
|
|
1030
|
+
const getDestructuredResultAccessorDeclarator = (
|
|
1031
|
+
blazeCall: AstNode,
|
|
1032
|
+
parents: WeakMap<AstNode, AstNode>
|
|
1033
|
+
): AstNode | null => {
|
|
1034
|
+
// Unwrap any wrapping parentheses and branch-position conditionals so
|
|
1035
|
+
// `const { isOk } = (trail.blaze(...));` and
|
|
1036
|
+
// `const { isOk } = cond ? trail.blaze(...) : fallback;` are treated as
|
|
1037
|
+
// `const { isOk } = trail.blaze(...);`.
|
|
1038
|
+
const outer = skipParensAndBranchConditionals(blazeCall, parents);
|
|
1039
|
+
const parent = parents.get(outer);
|
|
1040
|
+
if (!parent || parent.type !== 'VariableDeclarator') {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
const { id } = parent as unknown as { id?: AstNode };
|
|
1044
|
+
if (!id || id.type !== 'ObjectPattern') {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
return objectPatternHasResultAccessorKey(id) ? parent : null;
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
const visitBlazeCall = (node: AstNode, state: AnalyzeState): void => {
|
|
1051
|
+
if (!isBlazeCall(node) || isAwaited(node, state.parents)) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (hasDirectResultAccess(node, state.parents)) {
|
|
1055
|
+
reportMissingAwait(node, state);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const destructuredDeclarator = getDestructuredResultAccessorDeclarator(
|
|
1059
|
+
node,
|
|
1060
|
+
state.parents
|
|
1061
|
+
);
|
|
1062
|
+
if (destructuredDeclarator) {
|
|
1063
|
+
reportMissingAwait(destructuredDeclarator, state);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
recordPendingBinding(node, state);
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const visitNode = (node: AstNode, state: AnalyzeState): void => {
|
|
1070
|
+
visitBlazeCall(node, state);
|
|
1071
|
+
checkPendingAccess(node, state);
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Post-order visitor for assignment re-assignment clearing.
|
|
1076
|
+
*
|
|
1077
|
+
* `handleAssignmentReassignment` must run *after* the RHS subtree has been
|
|
1078
|
+
* walked. Otherwise a self-referential `result = result.value` would clear
|
|
1079
|
+
* the pending entry before the RHS `result.value` access is observed — the
|
|
1080
|
+
* missing-await diagnostic would disappear even though the write produced
|
|
1081
|
+
* a non-Result value from the same pending slot.
|
|
1082
|
+
*/
|
|
1083
|
+
const visitNodePost = (node: AstNode, state: AnalyzeState): void => {
|
|
1084
|
+
handleAssignmentReassignment(node, state);
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const pushScopeIfBoundary = (node: AstNode, state: AnalyzeState): boolean => {
|
|
1088
|
+
if (!isScopeBoundary(node)) {
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
const kind = scopeKindForNode(node);
|
|
1092
|
+
if (kind === 'function') {
|
|
1093
|
+
const { bindings, paramBindings } = collectFunctionScopeBindingsEx(node);
|
|
1094
|
+
state.scopeStack.push({
|
|
1095
|
+
bindings,
|
|
1096
|
+
id: state.nextScopeId,
|
|
1097
|
+
kind,
|
|
1098
|
+
paramBindings,
|
|
1099
|
+
});
|
|
1100
|
+
} else {
|
|
1101
|
+
state.scopeStack.push({
|
|
1102
|
+
bindings: collectScopeBindings(node),
|
|
1103
|
+
id: state.nextScopeId,
|
|
1104
|
+
kind,
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
state.nextScopeId += 1;
|
|
1108
|
+
return true;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
const walkChild = (child: unknown, state: AnalyzeState): void => {
|
|
1112
|
+
if (child && typeof child === 'object' && (child as AstNode).type) {
|
|
1113
|
+
// eslint-disable-next-line no-use-before-define
|
|
1114
|
+
walkWithScopes(child as AstNode, state);
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
const walkChildren = (node: AstNode, state: AnalyzeState): void => {
|
|
1119
|
+
for (const val of Object.values(node)) {
|
|
1120
|
+
if (Array.isArray(val)) {
|
|
1121
|
+
for (const item of val) {
|
|
1122
|
+
walkChild(item, state);
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
walkChild(val, state);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
// biome-ignore lint/style/useConst: hoisted for mutual recursion with walkChildren
|
|
1131
|
+
// eslint-disable-next-line func-style
|
|
1132
|
+
function walkWithScopes(node: AstNode, state: AnalyzeState): void {
|
|
1133
|
+
const pushed = pushScopeIfBoundary(node, state);
|
|
1134
|
+
visitNode(node, state);
|
|
1135
|
+
walkChildren(node, state);
|
|
1136
|
+
visitNodePost(node, state);
|
|
1137
|
+
if (pushed) {
|
|
1138
|
+
state.scopeStack.pop();
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const collectProgramBindings = (ast: AstNode): Set<string> => {
|
|
1143
|
+
const bindings = new Set<string>();
|
|
1144
|
+
const programBody = (ast as unknown as { body?: readonly AstNode[] }).body;
|
|
1145
|
+
// Top-level `let`/`const`/function declarations.
|
|
1146
|
+
collectBlockScopedStatementListBindings(programBody, bindings);
|
|
1147
|
+
// Top-level `var`s are program-scoped; also hoist any `var`s nested
|
|
1148
|
+
// inside blocks/loops at program level.
|
|
1149
|
+
collectHoistedVarBindings(ast, bindings);
|
|
1150
|
+
return bindings;
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const analyze = (
|
|
1154
|
+
ast: AstNode,
|
|
124
1155
|
sourceCode: string,
|
|
125
1156
|
filePath: string
|
|
126
1157
|
): readonly WardenDiagnostic[] => {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1158
|
+
const state: AnalyzeState = {
|
|
1159
|
+
diagnostics: [],
|
|
1160
|
+
filePath,
|
|
1161
|
+
nextScopeId: 1,
|
|
1162
|
+
parents: buildParentMap(ast),
|
|
1163
|
+
pendingByScopeAndName: new Map(),
|
|
1164
|
+
reportedAt: new Set(),
|
|
1165
|
+
scopeStack: [
|
|
1166
|
+
{ bindings: collectProgramBindings(ast), id: 0, kind: 'program' },
|
|
1167
|
+
],
|
|
1168
|
+
sourceCode,
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
walkWithScopes(ast, state);
|
|
138
1172
|
|
|
139
|
-
return diagnostics;
|
|
1173
|
+
return state.diagnostics;
|
|
140
1174
|
};
|
|
141
1175
|
|
|
142
1176
|
/**
|
|
@@ -147,7 +1181,11 @@ export const noSyncResultAssumption: WardenRule = {
|
|
|
147
1181
|
if (isTestFile(filePath) || isFrameworkInternalFile(filePath)) {
|
|
148
1182
|
return [];
|
|
149
1183
|
}
|
|
150
|
-
|
|
1184
|
+
const ast = parse(filePath, sourceCode);
|
|
1185
|
+
if (!ast) {
|
|
1186
|
+
return [];
|
|
1187
|
+
}
|
|
1188
|
+
return analyze(ast, sourceCode, filePath);
|
|
151
1189
|
},
|
|
152
1190
|
description:
|
|
153
1191
|
'Disallow treating .blaze() as synchronous after normalization. Always await the returned Promise<Result>.',
|