@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.
Files changed (474) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +30 -0
  3. package/README.md +31 -20
  4. package/dist/cli.d.ts +19 -2
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +261 -64
  7. package/dist/cli.js.map +1 -1
  8. package/dist/draft.d.ts +5 -0
  9. package/dist/draft.d.ts.map +1 -0
  10. package/dist/draft.js +16 -0
  11. package/dist/draft.js.map +1 -0
  12. package/dist/drift.d.ts +10 -7
  13. package/dist/drift.d.ts.map +1 -1
  14. package/dist/drift.js +50 -16
  15. package/dist/drift.js.map +1 -1
  16. package/dist/formatters.d.ts +2 -1
  17. package/dist/formatters.d.ts.map +1 -1
  18. package/dist/formatters.js +15 -4
  19. package/dist/formatters.js.map +1 -1
  20. package/dist/index.d.ts +9 -17
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +10 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/rules/ast.d.ts +412 -7
  25. package/dist/rules/ast.d.ts.map +1 -1
  26. package/dist/rules/ast.js +1847 -102
  27. package/dist/rules/ast.js.map +1 -1
  28. package/dist/rules/circular-refs.d.ts +6 -0
  29. package/dist/rules/circular-refs.d.ts.map +1 -0
  30. package/dist/rules/circular-refs.js +83 -0
  31. package/dist/rules/circular-refs.js.map +1 -0
  32. package/dist/rules/context-no-surface-types.d.ts.map +1 -1
  33. package/dist/rules/context-no-surface-types.js +59 -3
  34. package/dist/rules/context-no-surface-types.js.map +1 -1
  35. package/dist/rules/contour-exists.d.ts +7 -0
  36. package/dist/rules/contour-exists.d.ts.map +1 -0
  37. package/dist/rules/contour-exists.js +113 -0
  38. package/dist/rules/contour-exists.js.map +1 -0
  39. package/dist/rules/contour-ids.d.ts +10 -0
  40. package/dist/rules/contour-ids.d.ts.map +1 -0
  41. package/dist/rules/contour-ids.js +12 -0
  42. package/dist/rules/contour-ids.js.map +1 -0
  43. package/dist/rules/cross-declarations.d.ts.map +1 -1
  44. package/dist/rules/cross-declarations.js +171 -57
  45. package/dist/rules/cross-declarations.js.map +1 -1
  46. package/dist/rules/dead-internal-trail.d.ts +3 -0
  47. package/dist/rules/dead-internal-trail.d.ts.map +1 -0
  48. package/dist/rules/dead-internal-trail.js +80 -0
  49. package/dist/rules/dead-internal-trail.js.map +1 -0
  50. package/dist/rules/draft-file-marking.d.ts +6 -0
  51. package/dist/rules/draft-file-marking.d.ts.map +1 -0
  52. package/dist/rules/draft-file-marking.js +87 -0
  53. package/dist/rules/draft-file-marking.js.map +1 -0
  54. package/dist/rules/draft-visible-debt.d.ts +12 -0
  55. package/dist/rules/draft-visible-debt.d.ts.map +1 -0
  56. package/dist/rules/draft-visible-debt.js +50 -0
  57. package/dist/rules/draft-visible-debt.js.map +1 -0
  58. package/dist/rules/error-mapping-completeness.d.ts +13 -0
  59. package/dist/rules/error-mapping-completeness.d.ts.map +1 -0
  60. package/dist/rules/error-mapping-completeness.js +160 -0
  61. package/dist/rules/error-mapping-completeness.js.map +1 -0
  62. package/dist/rules/example-valid.d.ts +6 -0
  63. package/dist/rules/example-valid.d.ts.map +1 -0
  64. package/dist/rules/example-valid.js +203 -0
  65. package/dist/rules/example-valid.js.map +1 -0
  66. package/dist/rules/fires-declarations.d.ts +16 -0
  67. package/dist/rules/fires-declarations.d.ts.map +1 -0
  68. package/dist/rules/fires-declarations.js +444 -0
  69. package/dist/rules/fires-declarations.js.map +1 -0
  70. package/dist/rules/implementation-returns-result.d.ts +9 -0
  71. package/dist/rules/implementation-returns-result.d.ts.map +1 -1
  72. package/dist/rules/implementation-returns-result.js +638 -76
  73. package/dist/rules/implementation-returns-result.js.map +1 -1
  74. package/dist/rules/incomplete-accessor-for-standard-op.d.ts +30 -0
  75. package/dist/rules/incomplete-accessor-for-standard-op.d.ts.map +1 -0
  76. package/dist/rules/incomplete-accessor-for-standard-op.js +226 -0
  77. package/dist/rules/incomplete-accessor-for-standard-op.js.map +1 -0
  78. package/dist/rules/incomplete-crud.d.ts +21 -0
  79. package/dist/rules/incomplete-crud.d.ts.map +1 -0
  80. package/dist/rules/incomplete-crud.js +368 -0
  81. package/dist/rules/incomplete-crud.js.map +1 -0
  82. package/dist/rules/index.d.ts +40 -7
  83. package/dist/rules/index.d.ts.map +1 -1
  84. package/dist/rules/index.js +91 -15
  85. package/dist/rules/index.js.map +1 -1
  86. package/dist/rules/intent-propagation.d.ts +3 -0
  87. package/dist/rules/intent-propagation.d.ts.map +1 -0
  88. package/dist/rules/intent-propagation.js +57 -0
  89. package/dist/rules/intent-propagation.js.map +1 -0
  90. package/dist/rules/missing-reconcile.d.ts +3 -0
  91. package/dist/rules/missing-reconcile.d.ts.map +1 -0
  92. package/dist/rules/missing-reconcile.js +44 -0
  93. package/dist/rules/missing-reconcile.js.map +1 -0
  94. package/dist/rules/missing-visibility.d.ts +3 -0
  95. package/dist/rules/missing-visibility.d.ts.map +1 -0
  96. package/dist/rules/missing-visibility.js +63 -0
  97. package/dist/rules/missing-visibility.js.map +1 -0
  98. package/dist/rules/no-direct-impl-in-route.d.ts.map +1 -1
  99. package/dist/rules/no-direct-impl-in-route.js +0 -3
  100. package/dist/rules/no-direct-impl-in-route.js.map +1 -1
  101. package/dist/rules/no-direct-implementation-call.js +1 -1
  102. package/dist/rules/no-direct-implementation-call.js.map +1 -1
  103. package/dist/rules/no-sync-result-assumption.d.ts.map +1 -1
  104. package/dist/rules/no-sync-result-assumption.js +870 -61
  105. package/dist/rules/no-sync-result-assumption.js.map +1 -1
  106. package/dist/rules/no-throw-in-detour-recover.d.ts +3 -0
  107. package/dist/rules/no-throw-in-detour-recover.d.ts.map +1 -0
  108. package/dist/rules/no-throw-in-detour-recover.js +147 -0
  109. package/dist/rules/no-throw-in-detour-recover.js.map +1 -0
  110. package/dist/rules/no-throw-in-detour-target.d.ts +4 -1
  111. package/dist/rules/no-throw-in-detour-target.d.ts.map +1 -1
  112. package/dist/rules/no-throw-in-detour-target.js +6 -3
  113. package/dist/rules/no-throw-in-detour-target.js.map +1 -1
  114. package/dist/rules/no-throw-in-implementation.d.ts +4 -2
  115. package/dist/rules/no-throw-in-implementation.d.ts.map +1 -1
  116. package/dist/rules/no-throw-in-implementation.js +6 -4
  117. package/dist/rules/no-throw-in-implementation.js.map +1 -1
  118. package/dist/rules/on-references-exist.d.ts +14 -0
  119. package/dist/rules/on-references-exist.d.ts.map +1 -0
  120. package/dist/rules/on-references-exist.js +109 -0
  121. package/dist/rules/on-references-exist.js.map +1 -0
  122. package/dist/rules/orphaned-signal.d.ts +3 -0
  123. package/dist/rules/orphaned-signal.d.ts.map +1 -0
  124. package/dist/rules/orphaned-signal.js +67 -0
  125. package/dist/rules/orphaned-signal.js.map +1 -0
  126. package/dist/rules/permit-governance.d.ts +3 -0
  127. package/dist/rules/permit-governance.d.ts.map +1 -0
  128. package/dist/rules/permit-governance.js +15 -0
  129. package/dist/rules/permit-governance.js.map +1 -0
  130. package/dist/rules/reference-exists.d.ts +6 -0
  131. package/dist/rules/reference-exists.d.ts.map +1 -0
  132. package/dist/rules/reference-exists.js +47 -0
  133. package/dist/rules/reference-exists.js.map +1 -0
  134. package/dist/rules/registry-names.d.ts +8 -0
  135. package/dist/rules/registry-names.d.ts.map +1 -0
  136. package/dist/rules/registry-names.js +83 -0
  137. package/dist/rules/registry-names.js.map +1 -0
  138. package/dist/rules/resource-declarations.d.ts +14 -0
  139. package/dist/rules/resource-declarations.d.ts.map +1 -0
  140. package/dist/rules/resource-declarations.js +413 -0
  141. package/dist/rules/resource-declarations.js.map +1 -0
  142. package/dist/rules/resource-exists.d.ts +6 -0
  143. package/dist/rules/resource-exists.d.ts.map +1 -0
  144. package/dist/rules/resource-exists.js +90 -0
  145. package/dist/rules/resource-exists.js.map +1 -0
  146. package/dist/rules/resource-id-grammar.d.ts +3 -0
  147. package/dist/rules/resource-id-grammar.d.ts.map +1 -0
  148. package/dist/rules/resource-id-grammar.js +39 -0
  149. package/dist/rules/resource-id-grammar.js.map +1 -0
  150. package/dist/rules/specs.d.ts.map +1 -1
  151. package/dist/rules/specs.js +5 -1
  152. package/dist/rules/specs.js.map +1 -1
  153. package/dist/rules/types.d.ts +53 -4
  154. package/dist/rules/types.d.ts.map +1 -1
  155. package/dist/rules/unreachable-detour-shadowing.d.ts +3 -0
  156. package/dist/rules/unreachable-detour-shadowing.d.ts.map +1 -0
  157. package/dist/rules/unreachable-detour-shadowing.js +202 -0
  158. package/dist/rules/unreachable-detour-shadowing.js.map +1 -0
  159. package/dist/rules/valid-describe-refs.d.ts.map +1 -1
  160. package/dist/rules/valid-describe-refs.js +132 -16
  161. package/dist/rules/valid-describe-refs.js.map +1 -1
  162. package/dist/rules/valid-detour-contract.d.ts +3 -0
  163. package/dist/rules/valid-detour-contract.d.ts.map +1 -0
  164. package/dist/rules/valid-detour-contract.js +47 -0
  165. package/dist/rules/valid-detour-contract.js.map +1 -0
  166. package/dist/rules/valid-detour-refs.d.ts.map +1 -1
  167. package/dist/rules/valid-detour-refs.js +73 -82
  168. package/dist/rules/valid-detour-refs.js.map +1 -1
  169. package/dist/rules/warden-export-symmetry.d.ts +7 -0
  170. package/dist/rules/warden-export-symmetry.d.ts.map +1 -0
  171. package/dist/rules/warden-export-symmetry.js +352 -0
  172. package/dist/rules/warden-export-symmetry.js.map +1 -0
  173. package/dist/rules/warden-rules-use-ast.d.ts +17 -0
  174. package/dist/rules/warden-rules-use-ast.d.ts.map +1 -0
  175. package/dist/rules/warden-rules-use-ast.js +778 -0
  176. package/dist/rules/warden-rules-use-ast.js.map +1 -0
  177. package/dist/trails/circular-refs.trail.d.ts +24 -0
  178. package/dist/trails/circular-refs.trail.d.ts.map +1 -0
  179. package/dist/trails/circular-refs.trail.js +29 -0
  180. package/dist/trails/circular-refs.trail.js.map +1 -0
  181. package/dist/trails/context-no-surface-types.trail.d.ts +2 -2
  182. package/dist/trails/context-no-surface-types.trail.d.ts.map +1 -1
  183. package/dist/trails/context-no-trailhead-types.trail.d.ts +2 -2
  184. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -1
  185. package/dist/trails/contour-exists.trail.d.ts +24 -0
  186. package/dist/trails/contour-exists.trail.d.ts.map +1 -0
  187. package/dist/trails/contour-exists.trail.js +21 -0
  188. package/dist/trails/contour-exists.trail.js.map +1 -0
  189. package/dist/trails/cross-declarations.trail.d.ts +2 -2
  190. package/dist/trails/cross-declarations.trail.d.ts.map +1 -1
  191. package/dist/trails/dead-internal-trail.trail.d.ts +24 -0
  192. package/dist/trails/dead-internal-trail.trail.d.ts.map +1 -0
  193. package/dist/trails/dead-internal-trail.trail.js +26 -0
  194. package/dist/trails/dead-internal-trail.trail.js.map +1 -0
  195. package/dist/trails/{provision-declarations.trail.d.ts → draft-file-marking.trail.d.ts} +3 -3
  196. package/dist/trails/draft-file-marking.trail.d.ts.map +1 -0
  197. package/dist/trails/draft-file-marking.trail.js +16 -0
  198. package/dist/trails/draft-file-marking.trail.js.map +1 -0
  199. package/dist/trails/draft-visible-debt.trail.d.ts +13 -0
  200. package/dist/trails/draft-visible-debt.trail.d.ts.map +1 -0
  201. package/dist/trails/draft-visible-debt.trail.js +16 -0
  202. package/dist/trails/draft-visible-debt.trail.js.map +1 -0
  203. package/dist/trails/error-mapping-completeness.trail.d.ts +13 -0
  204. package/dist/trails/error-mapping-completeness.trail.d.ts.map +1 -0
  205. package/dist/trails/error-mapping-completeness.trail.js +29 -0
  206. package/dist/trails/error-mapping-completeness.trail.js.map +1 -0
  207. package/dist/trails/{follow-declarations.trail.d.ts → example-valid.trail.d.ts} +3 -3
  208. package/dist/trails/example-valid.trail.d.ts.map +1 -0
  209. package/dist/trails/example-valid.trail.js +25 -0
  210. package/dist/trails/example-valid.trail.js.map +1 -0
  211. package/dist/trails/fires-declarations.trail.d.ts +13 -0
  212. package/dist/trails/fires-declarations.trail.d.ts.map +1 -0
  213. package/dist/trails/fires-declarations.trail.js +22 -0
  214. package/dist/trails/fires-declarations.trail.js.map +1 -0
  215. package/dist/trails/implementation-returns-result.trail.d.ts +2 -2
  216. package/dist/trails/implementation-returns-result.trail.d.ts.map +1 -1
  217. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts +12 -0
  218. package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts.map +1 -0
  219. package/dist/trails/incomplete-accessor-for-standard-op.trail.js +60 -0
  220. package/dist/trails/incomplete-accessor-for-standard-op.trail.js.map +1 -0
  221. package/dist/trails/incomplete-crud.trail.d.ts +24 -0
  222. package/dist/trails/incomplete-crud.trail.d.ts.map +1 -0
  223. package/dist/trails/incomplete-crud.trail.js +39 -0
  224. package/dist/trails/incomplete-crud.trail.js.map +1 -0
  225. package/dist/trails/index.d.ts +29 -7
  226. package/dist/trails/index.d.ts.map +1 -1
  227. package/dist/trails/index.js +28 -6
  228. package/dist/trails/index.js.map +1 -1
  229. package/dist/trails/intent-propagation.trail.d.ts +24 -0
  230. package/dist/trails/intent-propagation.trail.d.ts.map +1 -0
  231. package/dist/trails/intent-propagation.trail.js +30 -0
  232. package/dist/trails/intent-propagation.trail.js.map +1 -0
  233. package/dist/trails/missing-reconcile.trail.d.ts +24 -0
  234. package/dist/trails/missing-reconcile.trail.d.ts.map +1 -0
  235. package/dist/trails/missing-reconcile.trail.js +33 -0
  236. package/dist/trails/missing-reconcile.trail.js.map +1 -0
  237. package/dist/trails/missing-visibility.trail.d.ts +24 -0
  238. package/dist/trails/missing-visibility.trail.d.ts.map +1 -0
  239. package/dist/trails/missing-visibility.trail.js +22 -0
  240. package/dist/trails/missing-visibility.trail.js.map +1 -0
  241. package/dist/trails/no-direct-impl-in-route.trail.d.ts +2 -2
  242. package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +1 -1
  243. package/dist/trails/no-direct-implementation-call.trail.d.ts +2 -2
  244. package/dist/trails/no-direct-implementation-call.trail.d.ts.map +1 -1
  245. package/dist/trails/no-sync-result-assumption.trail.d.ts +2 -2
  246. package/dist/trails/no-sync-result-assumption.trail.d.ts.map +1 -1
  247. package/dist/trails/no-throw-in-detour-recover.trail.d.ts +13 -0
  248. package/dist/trails/no-throw-in-detour-recover.trail.d.ts.map +1 -0
  249. package/dist/trails/no-throw-in-detour-recover.trail.js +24 -0
  250. package/dist/trails/no-throw-in-detour-recover.trail.js.map +1 -0
  251. package/dist/trails/no-throw-in-detour-target.trail.d.ts +13 -3
  252. package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +1 -1
  253. package/dist/trails/no-throw-in-implementation.trail.d.ts +2 -2
  254. package/dist/trails/no-throw-in-implementation.trail.d.ts.map +1 -1
  255. package/dist/trails/on-references-exist.trail.d.ts +24 -0
  256. package/dist/trails/on-references-exist.trail.d.ts.map +1 -0
  257. package/dist/trails/on-references-exist.trail.js +21 -0
  258. package/dist/trails/on-references-exist.trail.js.map +1 -0
  259. package/dist/trails/orphaned-signal.trail.d.ts +24 -0
  260. package/dist/trails/orphaned-signal.trail.d.ts.map +1 -0
  261. package/dist/trails/orphaned-signal.trail.js +36 -0
  262. package/dist/trails/orphaned-signal.trail.js.map +1 -0
  263. package/dist/trails/permit-governance.trail.d.ts +12 -0
  264. package/dist/trails/permit-governance.trail.d.ts.map +1 -0
  265. package/dist/trails/permit-governance.trail.js +47 -0
  266. package/dist/trails/permit-governance.trail.js.map +1 -0
  267. package/dist/trails/prefer-schema-inference.trail.d.ts +2 -2
  268. package/dist/trails/prefer-schema-inference.trail.d.ts.map +1 -1
  269. package/dist/trails/reference-exists.trail.d.ts +24 -0
  270. package/dist/trails/reference-exists.trail.d.ts.map +1 -0
  271. package/dist/trails/reference-exists.trail.js +25 -0
  272. package/dist/trails/reference-exists.trail.js.map +1 -0
  273. package/dist/trails/resource-declarations.trail.d.ts +13 -0
  274. package/dist/trails/resource-declarations.trail.d.ts.map +1 -0
  275. package/dist/trails/{provision-declarations.trail.js → resource-declarations.trail.js} +7 -7
  276. package/dist/trails/resource-declarations.trail.js.map +1 -0
  277. package/dist/trails/resource-exists.trail.d.ts +24 -0
  278. package/dist/trails/resource-exists.trail.d.ts.map +1 -0
  279. package/dist/trails/{provision-exists.trail.js → resource-exists.trail.js} +8 -8
  280. package/dist/trails/resource-exists.trail.js.map +1 -0
  281. package/dist/trails/resource-id-grammar.trail.d.ts +13 -0
  282. package/dist/trails/resource-id-grammar.trail.d.ts.map +1 -0
  283. package/dist/trails/resource-id-grammar.trail.js +38 -0
  284. package/dist/trails/resource-id-grammar.trail.js.map +1 -0
  285. package/dist/trails/run.d.ts +25 -9
  286. package/dist/trails/run.d.ts.map +1 -1
  287. package/dist/trails/run.js +63 -19
  288. package/dist/trails/run.js.map +1 -1
  289. package/dist/trails/schema.d.ts +28 -3
  290. package/dist/trails/schema.d.ts.map +1 -1
  291. package/dist/trails/schema.js +57 -4
  292. package/dist/trails/schema.js.map +1 -1
  293. package/dist/trails/unreachable-detour-shadowing.trail.d.ts +13 -0
  294. package/dist/trails/unreachable-detour-shadowing.trail.d.ts.map +1 -0
  295. package/dist/trails/unreachable-detour-shadowing.trail.js +44 -0
  296. package/dist/trails/unreachable-detour-shadowing.trail.js.map +1 -0
  297. package/dist/trails/valid-describe-refs.trail.d.ts +12 -3
  298. package/dist/trails/valid-describe-refs.trail.d.ts.map +1 -1
  299. package/dist/trails/valid-detour-contract.trail.d.ts +12 -0
  300. package/dist/trails/valid-detour-contract.trail.d.ts.map +1 -0
  301. package/dist/trails/valid-detour-contract.trail.js +66 -0
  302. package/dist/trails/valid-detour-contract.trail.js.map +1 -0
  303. package/dist/trails/valid-detour-refs.trail.d.ts +13 -3
  304. package/dist/trails/valid-detour-refs.trail.d.ts.map +1 -1
  305. package/dist/trails/warden-export-symmetry.trail.d.ts +13 -0
  306. package/dist/trails/warden-export-symmetry.trail.d.ts.map +1 -0
  307. package/dist/trails/warden-export-symmetry.trail.js +16 -0
  308. package/dist/trails/warden-export-symmetry.trail.js.map +1 -0
  309. package/dist/trails/warden-rules-use-ast.trail.d.ts +13 -0
  310. package/dist/trails/warden-rules-use-ast.trail.d.ts.map +1 -0
  311. package/dist/trails/warden-rules-use-ast.trail.js +41 -0
  312. package/dist/trails/warden-rules-use-ast.trail.js.map +1 -0
  313. package/dist/trails/wrap-rule.d.ts +16 -2
  314. package/dist/trails/wrap-rule.d.ts.map +1 -1
  315. package/dist/trails/wrap-rule.js +71 -11
  316. package/dist/trails/wrap-rule.js.map +1 -1
  317. package/package.json +7 -4
  318. package/src/__tests__/ast.test.ts +613 -0
  319. package/src/__tests__/circular-refs.test.ts +121 -0
  320. package/src/__tests__/cli.test.ts +360 -32
  321. package/src/__tests__/contour-exists.test.ts +203 -0
  322. package/src/__tests__/cross-declarations.test.ts +245 -0
  323. package/src/__tests__/dead-internal-trail.test.ts +81 -0
  324. package/src/__tests__/draft-rules-context.test.ts +150 -0
  325. package/src/__tests__/drift.test.ts +75 -5
  326. package/src/__tests__/error-mapping-completeness.test.ts +56 -0
  327. package/src/__tests__/example-valid.test.ts +101 -0
  328. package/src/__tests__/fires-declarations-param-destructure.test.ts +54 -0
  329. package/src/__tests__/fires-declarations.test.ts +652 -0
  330. package/src/__tests__/formatters.test.ts +2 -2
  331. package/src/__tests__/implementation-returns-result.test.ts +1016 -2
  332. package/src/__tests__/incomplete-accessor-for-standard-op.test.ts +337 -0
  333. package/src/__tests__/incomplete-crud.test.ts +498 -0
  334. package/src/__tests__/intent-propagation.test.ts +116 -0
  335. package/src/__tests__/missing-reconcile.test.ts +154 -0
  336. package/src/__tests__/missing-visibility.test.ts +108 -0
  337. package/src/__tests__/no-sync-result-assumption.test.ts +870 -39
  338. package/src/__tests__/no-throw-in-detour-recover.test.ts +93 -0
  339. package/src/__tests__/no-throw-in-implementation.test.ts +88 -0
  340. package/src/__tests__/on-references-exist.test.ts +151 -0
  341. package/src/__tests__/orphaned-signal.test.ts +137 -0
  342. package/src/__tests__/permit-governance.test.ts +66 -0
  343. package/src/__tests__/reference-exists.test.ts +281 -0
  344. package/src/__tests__/resource-declarations.test.ts +448 -0
  345. package/src/__tests__/resource-exists.test.ts +122 -0
  346. package/src/__tests__/resource-id-grammar.test.ts +50 -0
  347. package/src/__tests__/rules.test.ts +17 -77
  348. package/src/__tests__/topo-aware-rule.test.ts +257 -0
  349. package/src/__tests__/trails.test.ts +2 -2
  350. package/src/__tests__/unreachable-detour-shadowing.test.ts +128 -0
  351. package/src/__tests__/valid-describe-refs.test.ts +183 -0
  352. package/src/__tests__/valid-detour-contract.test.ts +86 -0
  353. package/src/__tests__/warden-export-symmetry.test.ts +251 -0
  354. package/src/__tests__/warden-rules-use-ast.test.ts +468 -0
  355. package/src/__tests__/wrap-rule.test.ts +3 -3
  356. package/src/cli.ts +458 -91
  357. package/src/draft.ts +22 -0
  358. package/src/drift.ts +63 -21
  359. package/src/formatters.ts +15 -4
  360. package/src/index.ts +62 -23
  361. package/src/rules/ast.ts +2715 -119
  362. package/src/rules/circular-refs.ts +154 -0
  363. package/src/rules/{context-no-trailhead-types.ts → context-no-surface-types.ts} +72 -12
  364. package/src/rules/contour-exists.ts +251 -0
  365. package/src/rules/contour-ids.ts +15 -0
  366. package/src/rules/cross-declarations.ts +277 -69
  367. package/src/rules/dead-internal-trail.ts +141 -0
  368. package/src/rules/draft-file-marking.ts +160 -0
  369. package/src/rules/draft-visible-debt.ts +87 -0
  370. package/src/rules/error-mapping-completeness.ts +273 -0
  371. package/src/rules/example-valid.ts +401 -0
  372. package/src/rules/fires-declarations.ts +609 -0
  373. package/src/rules/implementation-returns-result.ts +1042 -122
  374. package/src/rules/incomplete-accessor-for-standard-op.ts +315 -0
  375. package/src/rules/incomplete-crud.ts +579 -0
  376. package/src/rules/index.ts +95 -16
  377. package/src/rules/intent-propagation.ts +142 -0
  378. package/src/rules/missing-reconcile.ts +98 -0
  379. package/src/rules/missing-visibility.ts +110 -0
  380. package/src/rules/no-direct-impl-in-route.ts +0 -4
  381. package/src/rules/no-direct-implementation-call.ts +1 -1
  382. package/src/rules/no-sync-result-assumption.ts +1134 -96
  383. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  384. package/src/rules/no-throw-in-implementation.ts +6 -4
  385. package/src/rules/on-references-exist.ts +194 -0
  386. package/src/rules/orphaned-signal.ts +150 -0
  387. package/src/rules/permit-governance.ts +25 -0
  388. package/src/rules/reference-exists.ts +98 -0
  389. package/src/rules/registry-names.ts +83 -0
  390. package/src/rules/{provision-declarations.ts → resource-declarations.ts} +208 -138
  391. package/src/rules/{provision-exists.ts → resource-exists.ts} +48 -51
  392. package/src/rules/resource-id-grammar.ts +65 -0
  393. package/src/rules/specs.ts +5 -1
  394. package/src/rules/types.ts +57 -4
  395. package/src/rules/unreachable-detour-shadowing.ts +375 -0
  396. package/src/rules/valid-describe-refs.ts +160 -32
  397. package/src/rules/valid-detour-contract.ts +78 -0
  398. package/src/rules/warden-export-symmetry.ts +533 -0
  399. package/src/rules/warden-rules-use-ast.ts +996 -0
  400. package/src/trails/circular-refs.trail.ts +29 -0
  401. package/src/trails/{context-no-trailhead-types.trail.ts → context-no-surface-types.trail.ts} +4 -4
  402. package/src/trails/contour-exists.trail.ts +21 -0
  403. package/src/trails/dead-internal-trail.trail.ts +26 -0
  404. package/src/trails/draft-file-marking.trail.ts +16 -0
  405. package/src/trails/draft-visible-debt.trail.ts +16 -0
  406. package/src/trails/error-mapping-completeness.trail.ts +29 -0
  407. package/src/trails/example-valid.trail.ts +25 -0
  408. package/src/trails/fires-declarations.trail.ts +22 -0
  409. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  410. package/src/trails/incomplete-crud.trail.ts +39 -0
  411. package/src/trails/index.ts +40 -7
  412. package/src/trails/intent-propagation.trail.ts +30 -0
  413. package/src/trails/missing-reconcile.trail.ts +33 -0
  414. package/src/trails/missing-visibility.trail.ts +22 -0
  415. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  416. package/src/trails/on-references-exist.trail.ts +21 -0
  417. package/src/trails/orphaned-signal.trail.ts +36 -0
  418. package/src/trails/permit-governance.trail.ts +51 -0
  419. package/src/trails/reference-exists.trail.ts +25 -0
  420. package/src/trails/{provision-declarations.trail.ts → resource-declarations.trail.ts} +6 -6
  421. package/src/trails/{provision-exists.trail.ts → resource-exists.trail.ts} +7 -7
  422. package/src/trails/resource-id-grammar.trail.ts +39 -0
  423. package/src/trails/run.ts +121 -24
  424. package/src/trails/schema.ts +66 -4
  425. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  426. package/src/trails/valid-detour-contract.trail.ts +71 -0
  427. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  428. package/src/trails/warden-rules-use-ast.trail.ts +45 -0
  429. package/src/trails/wrap-rule.ts +104 -12
  430. package/tsconfig.tests.json +10 -0
  431. package/tsconfig.tsbuildinfo +1 -1
  432. package/dist/rules/follow-declarations.d.ts +0 -13
  433. package/dist/rules/follow-declarations.d.ts.map +0 -1
  434. package/dist/rules/follow-declarations.js +0 -264
  435. package/dist/rules/follow-declarations.js.map +0 -1
  436. package/dist/rules/provision-declarations.d.ts +0 -14
  437. package/dist/rules/provision-declarations.d.ts.map +0 -1
  438. package/dist/rules/provision-declarations.js +0 -344
  439. package/dist/rules/provision-declarations.js.map +0 -1
  440. package/dist/rules/provision-exists.d.ts +0 -6
  441. package/dist/rules/provision-exists.d.ts.map +0 -1
  442. package/dist/rules/provision-exists.js +0 -89
  443. package/dist/rules/provision-exists.js.map +0 -1
  444. package/dist/rules/service-declarations.d.ts +0 -16
  445. package/dist/rules/service-declarations.d.ts.map +0 -1
  446. package/dist/rules/service-declarations.js +0 -346
  447. package/dist/rules/service-declarations.js.map +0 -1
  448. package/dist/rules/service-exists.d.ts +0 -8
  449. package/dist/rules/service-exists.d.ts.map +0 -1
  450. package/dist/rules/service-exists.js +0 -91
  451. package/dist/rules/service-exists.js.map +0 -1
  452. package/dist/trails/follow-declarations.trail.d.ts.map +0 -1
  453. package/dist/trails/follow-declarations.trail.js +0 -22
  454. package/dist/trails/follow-declarations.trail.js.map +0 -1
  455. package/dist/trails/provision-declarations.trail.d.ts.map +0 -1
  456. package/dist/trails/provision-declarations.trail.js.map +0 -1
  457. package/dist/trails/provision-exists.trail.d.ts +0 -15
  458. package/dist/trails/provision-exists.trail.d.ts.map +0 -1
  459. package/dist/trails/provision-exists.trail.js.map +0 -1
  460. package/dist/trails/service-declarations.trail.d.ts +0 -26
  461. package/dist/trails/service-declarations.trail.d.ts.map +0 -1
  462. package/dist/trails/service-declarations.trail.js +0 -27
  463. package/dist/trails/service-declarations.trail.js.map +0 -1
  464. package/dist/trails/service-exists.trail.d.ts +0 -32
  465. package/dist/trails/service-exists.trail.d.ts.map +0 -1
  466. package/dist/trails/service-exists.trail.js +0 -29
  467. package/dist/trails/service-exists.trail.js.map +0 -1
  468. package/src/__tests__/no-throw-in-detour-target.test.ts +0 -78
  469. package/src/__tests__/provision-declarations.test.ts +0 -318
  470. package/src/__tests__/provision-exists.test.ts +0 -122
  471. package/src/rules/no-throw-in-detour-target.ts +0 -150
  472. package/src/rules/valid-detour-refs.ts +0 -187
  473. package/src/trails/no-throw-in-detour-target.trail.ts +0 -20
  474. package/src/trails/valid-detour-refs.trail.ts +0 -24
@@ -1,22 +1,13 @@
1
- import { isFrameworkInternalFile, isTestFile, stripQuotedContent, } from './scan.js';
2
- const RESULT_ACCESS_PATTERN = /\.(?:isOk|isErr|match|map)\s*\(|\.(?:value|error)\b/;
3
- const IMPLEMENTATION_CALL_PATTERN = /\.blaze\s*\(/;
4
- const isAwaitedImplementationCall = (line) => {
5
- const callIndex = line.indexOf('.blaze(');
6
- if (callIndex === -1) {
7
- return false;
8
- }
9
- const awaitIndex = line.indexOf('await');
10
- return awaitIndex !== -1 && awaitIndex < callIndex;
11
- };
12
- const isDirectResultAccess = (line) => IMPLEMENTATION_CALL_PATTERN.test(line) &&
13
- RESULT_ACCESS_PATTERN.test(line) &&
14
- !isAwaitedImplementationCall(line);
15
- const isPendingUse = (line, variableName) => {
16
- const escaped = variableName.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
- const pendingPattern = new RegExp(`\\b${escaped}\\s*(?:\\.(?:isOk|isErr|match|map)\\s*\\(|\\.(?:value|error)\\b)`);
18
- return pendingPattern.test(line);
19
- };
1
+ import { identifierName, isBlazeCall, offsetToLine, parse } from './ast.js';
2
+ import { isFrameworkInternalFile, isTestFile } from './scan.js';
3
+ const RESULT_ACCESSOR_PROPERTIES = new Set([
4
+ 'error',
5
+ 'isErr',
6
+ 'isOk',
7
+ 'map',
8
+ 'match',
9
+ 'value',
10
+ ]);
20
11
  const MISSING_AWAIT_MESSAGE = 'Missing await: .blaze() returns Promise<Result> after normalization. Use `const result = await trail.blaze(input, ctx)`.';
21
12
  const createMissingAwaitDiagnostic = (filePath, line) => ({
22
13
  filePath,
@@ -25,61 +16,875 @@ const createMissingAwaitDiagnostic = (filePath, line) => ({
25
16
  rule: 'no-sync-result-assumption',
26
17
  severity: 'error',
27
18
  });
28
- const trackPendingCall = (line) => {
29
- const match = line.match(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]*)/);
30
- if (!match?.[1] || !match[2] || !IMPLEMENTATION_CALL_PATTERN.test(match[2])) {
31
- return undefined;
19
+ const isAstLike = (value) => !!value && typeof value === 'object' && !!value.type;
20
+ /**
21
+ * Build parent map for a full AST.
22
+ *
23
+ * Populates a `WeakMap` directly during traversal so we never materialize a
24
+ * strong `Map` holding references to every AST node — the WeakMap lets parent
25
+ * entries be reclaimed alongside their nodes once the rule invocation ends.
26
+ */
27
+ const buildParentMap = (ast) => {
28
+ const parents = new WeakMap();
29
+ const recordAndVisit = (child, parent) => {
30
+ if (isAstLike(child)) {
31
+ parents.set(child, parent);
32
+ // eslint-disable-next-line no-use-before-define
33
+ visit(child);
34
+ }
35
+ };
36
+ const visit = (node) => {
37
+ for (const val of Object.values(node)) {
38
+ if (Array.isArray(val)) {
39
+ for (const item of val) {
40
+ recordAndVisit(item, node);
41
+ }
42
+ }
43
+ else {
44
+ recordAndVisit(val, node);
45
+ }
46
+ }
47
+ };
48
+ visit(ast);
49
+ return parents;
50
+ };
51
+ /**
52
+ * Walk up the parent chain and return true when the expression is awaited
53
+ * before any result-accessing member access fires on it.
54
+ *
55
+ * `await x.blaze(...)` → awaited.
56
+ * `(await x.blaze(...)).isOk()` → awaited (await wraps before member access).
57
+ * `x.blaze(...).isOk()` → NOT awaited (member access on raw call).
58
+ */
59
+ const TRANSPARENT_WRAPPER_TYPES = new Set([
60
+ 'ParenthesizedExpression',
61
+ 'TSAsExpression',
62
+ 'TSSatisfiesExpression',
63
+ 'TSNonNullExpression',
64
+ 'TSTypeAssertion',
65
+ ]);
66
+ const skipParens = (node, parents) => {
67
+ let current = node;
68
+ let parent = parents.get(current);
69
+ while (parent?.type && TRANSPARENT_WRAPPER_TYPES.has(parent.type)) {
70
+ current = parent;
71
+ parent = parents.get(current);
32
72
  }
33
- if (isAwaitedImplementationCall(match[2])) {
34
- return undefined;
73
+ return current;
74
+ };
75
+ /**
76
+ * Walk up through any wrapping parentheses and, when the current node sits
77
+ * in the `consequent` or `alternate` of a `ConditionalExpression`, through
78
+ * that conditional too. Returns the node whose parent should be inspected.
79
+ *
80
+ * Conservative: we only hop across a conditional when the node is one of
81
+ * its branches (not the `test` position). This lets us treat both
82
+ * `const r = cond ? x.blaze(...) : fallback` and
83
+ * `await (cond ? x.blaze(...) : fallback)` correctly without misattributing
84
+ * calls used as conditions.
85
+ */
86
+ const isBranchOfConditional = (outer, parent) => {
87
+ if (parent.type !== 'ConditionalExpression') {
88
+ return false;
35
89
  }
36
- return match[1];
90
+ const cond = parent;
91
+ return cond.consequent === outer || cond.alternate === outer;
37
92
  };
38
- const addPendingCall = (pendingCalls, variableName, lineNumber) => {
39
- pendingCalls.push({
40
- line: lineNumber,
41
- remainingLines: 6,
42
- variableName,
43
- });
93
+ /**
94
+ * Logical expressions (`&&`, `||`, `??`) carry the blaze result through either
95
+ * side. A `.blaze()` on either operand may be the value ultimately bound to a
96
+ * declarator (e.g. `const r = cond && trail.blaze(...)`), so we treat both
97
+ * operands as carriers.
98
+ */
99
+ const isOperandOfLogical = (outer, parent) => {
100
+ if (parent.type !== 'LogicalExpression') {
101
+ return false;
102
+ }
103
+ const logical = parent;
104
+ return logical.left === outer || logical.right === outer;
105
+ };
106
+ const skipParensAndBranchConditionals = (node, parents) => {
107
+ let outer = skipParens(node, parents);
108
+ while (true) {
109
+ const parent = parents.get(outer);
110
+ if (!parent) {
111
+ return outer;
112
+ }
113
+ if (!(isBranchOfConditional(outer, parent) ||
114
+ isOperandOfLogical(outer, parent))) {
115
+ return outer;
116
+ }
117
+ outer = skipParens(parent, parents);
118
+ }
119
+ };
120
+ const isAwaited = (node, parents) => {
121
+ // Walk up through parens and any conditional whose branch is the blaze
122
+ // call. `await (c ? x.blaze(...) : fallback)` awaits the conditional as a
123
+ // whole, so the blaze call in a branch is effectively awaited.
124
+ const outer = skipParensAndBranchConditionals(node, parents);
125
+ return parents.get(outer)?.type === 'AwaitExpression';
126
+ };
127
+ const memberPropertyName = (node) => {
128
+ if (node.type !== 'MemberExpression' &&
129
+ node.type !== 'StaticMemberExpression') {
130
+ return null;
131
+ }
132
+ const prop = node.property;
133
+ if (prop?.type !== 'Identifier') {
134
+ return null;
135
+ }
136
+ return prop.name ?? null;
137
+ };
138
+ /**
139
+ * Check if the blaze call is directly consumed by a result accessor
140
+ * (e.g. `foo.blaze(...).isOk()` or `foo.blaze(...).value`).
141
+ */
142
+ const hasDirectResultAccess = (blazeCall, parents) => {
143
+ // Unwrap wrapping parentheses, conditional branches, and logical-operator
144
+ // operands so `(x.blaze(...)).isOk()`,
145
+ // `(cond ? x.blaze(...) : fb).isOk()`, and
146
+ // `(cond && x.blaze(...)).isOk()` are all detected the same way as the
147
+ // bare `x.blaze(...).isOk()` shape.
148
+ const outer = skipParensAndBranchConditionals(blazeCall, parents);
149
+ const parent = parents.get(outer);
150
+ if (!parent) {
151
+ return false;
152
+ }
153
+ const property = memberPropertyName(parent);
154
+ return property !== null && RESULT_ACCESSOR_PROPERTIES.has(property);
155
+ };
156
+ /**
157
+ * If the blaze call is the init of a VariableDeclarator (directly, through
158
+ * parens, or as a branch of a ConditionalExpression init), return the bound
159
+ * identifier name. Otherwise null.
160
+ */
161
+ const extractAssignedBinding = (blazeCall, parents) => {
162
+ const outer = skipParensAndBranchConditionals(blazeCall, parents);
163
+ const parent = parents.get(outer);
164
+ if (!parent || parent.type !== 'VariableDeclarator') {
165
+ return null;
166
+ }
167
+ const { id } = parent;
168
+ return identifierName(id);
169
+ };
170
+ const isResultAccessorMember = (node) => {
171
+ if (node.type !== 'MemberExpression' &&
172
+ node.type !== 'StaticMemberExpression') {
173
+ return false;
174
+ }
175
+ const property = memberPropertyName(node);
176
+ return property !== null && RESULT_ACCESSOR_PROPERTIES.has(property);
177
+ };
178
+ const getIdentifierObjectName = (node) => {
179
+ const { object } = node;
180
+ return object?.type === 'Identifier' ? identifierName(object) : null;
181
+ };
182
+ // ---------------------------------------------------------------------------
183
+ // Scope tracking
184
+ // ---------------------------------------------------------------------------
185
+ const collectIdentifierBinding = (pattern, out) => {
186
+ const name = identifierName(pattern);
187
+ if (name) {
188
+ out.add(name);
189
+ }
190
+ };
191
+ const collectAssignmentPatternBindings = (pattern, out) => {
192
+ const { left } = pattern;
193
+ // eslint-disable-next-line no-use-before-define
194
+ collectPatternBindings(left, out);
195
+ };
196
+ const collectRestElementBindings = (pattern, out) => {
197
+ const { argument } = pattern;
198
+ // eslint-disable-next-line no-use-before-define
199
+ collectPatternBindings(argument, out);
200
+ };
201
+ const PATTERN_HANDLERS = {
202
+ // eslint-disable-next-line no-use-before-define
203
+ ArrayPattern: (p, out) => collectArrayPatternBindings(p, out),
204
+ AssignmentPattern: collectAssignmentPatternBindings,
205
+ Identifier: collectIdentifierBinding,
206
+ // eslint-disable-next-line no-use-before-define
207
+ ObjectPattern: (p, out) => collectObjectPatternBindings(p, out),
208
+ RestElement: collectRestElementBindings,
209
+ };
210
+ /**
211
+ * Collect binding names introduced by a destructuring / parameter pattern.
212
+ * Handles Identifier, AssignmentPattern, ObjectPattern, ArrayPattern,
213
+ * and RestElement shapes.
214
+ *
215
+ * `function` declaration (instead of an arrow) so it can be hoisted for the
216
+ * mutually recursive calls from the array / object pattern helpers below.
217
+ */
218
+ // biome-ignore lint/style/useConst: hoisted for mutual recursion
219
+ // eslint-disable-next-line func-style
220
+ function collectPatternBindings(pattern, out) {
221
+ if (!pattern) {
222
+ return;
223
+ }
224
+ const handler = PATTERN_HANDLERS[pattern.type];
225
+ if (handler) {
226
+ handler(pattern, out);
227
+ }
228
+ }
229
+ const collectArrayPatternBindings = (pattern, out) => {
230
+ const { elements } = pattern;
231
+ if (!elements) {
232
+ return;
233
+ }
234
+ for (const element of elements) {
235
+ if (element) {
236
+ // eslint-disable-next-line no-use-before-define
237
+ collectPatternBindings(element, out);
238
+ }
239
+ }
240
+ };
241
+ const collectObjectPatternBindings = (pattern, out) => {
242
+ const { properties } = pattern;
243
+ if (!properties) {
244
+ return;
245
+ }
246
+ for (const prop of properties) {
247
+ if (prop.type === 'RestElement') {
248
+ // eslint-disable-next-line no-use-before-define
249
+ collectPatternBindings(prop, out);
250
+ }
251
+ else {
252
+ // Property node: value holds the binding pattern.
253
+ const { value } = prop;
254
+ // eslint-disable-next-line no-use-before-define
255
+ collectPatternBindings(value, out);
256
+ }
257
+ }
258
+ };
259
+ const SCOPE_NODE_TYPES = new Set([
260
+ 'FunctionDeclaration',
261
+ 'FunctionExpression',
262
+ 'ArrowFunctionExpression',
263
+ 'BlockStatement',
264
+ 'StaticBlock',
265
+ 'CatchClause',
266
+ 'ForStatement',
267
+ 'ForInStatement',
268
+ 'ForOfStatement',
269
+ ]);
270
+ const isScopeBoundary = (node) => SCOPE_NODE_TYPES.has(node.type);
271
+ /**
272
+ * Collect the local binding names introduced directly in this scope's own
273
+ * declarations (params + var/let/const/catch/for declarations), without
274
+ * descending into nested function or block scopes.
275
+ *
276
+ * For function-like scopes, the body (a BlockStatement) is its own child
277
+ * scope — we do not merge params into it. Params and body bindings are
278
+ * treated as sibling frames via the scope walk: when entering the function,
279
+ * we push a frame with params; when entering its body block, we push another
280
+ * frame with the block's declarations. Nearest-scope resolution treats them
281
+ * as a single effective scope chain.
282
+ */
283
+ const FUNCTION_SCOPE_TYPES = new Set([
284
+ 'FunctionDeclaration',
285
+ 'FunctionExpression',
286
+ 'ArrowFunctionExpression',
287
+ ]);
288
+ const collectVariableDeclarationBindings = (declNode, out) => {
289
+ if (!declNode || declNode.type !== 'VariableDeclaration') {
290
+ return;
291
+ }
292
+ const declarators = declNode.declarations;
293
+ if (!declarators) {
294
+ return;
295
+ }
296
+ for (const d of declarators) {
297
+ const { id } = d;
298
+ collectPatternBindings(id, out);
299
+ }
300
+ };
301
+ const getVariableDeclarationKind = (declNode) => {
302
+ if (!declNode || declNode.type !== 'VariableDeclaration') {
303
+ return null;
304
+ }
305
+ return declNode.kind ?? null;
306
+ };
307
+ /** True if declaration is `var` (function/program-scoped, hoistable). */
308
+ const isVarDeclaration = (declNode) => getVariableDeclarationKind(declNode) === 'var';
309
+ /** Collect only `let`/`const` declarator bindings (block-scoped). */
310
+ const collectBlockScopedDeclaratorBindings = (declNode, out) => {
311
+ const kind = getVariableDeclarationKind(declNode);
312
+ if (!kind || kind === 'var') {
313
+ return;
314
+ }
315
+ collectVariableDeclarationBindings(declNode, out);
316
+ };
317
+ const collectParamBindings = (scope) => {
318
+ const paramBindings = new Set();
319
+ const { params } = scope;
320
+ if (params) {
321
+ for (const param of params) {
322
+ collectPatternBindings(param, paramBindings);
323
+ }
324
+ }
325
+ return paramBindings;
326
+ };
327
+ const addHoistedVarsFromBody = (scope, out) => {
328
+ const { body } = scope;
329
+ if (!(body && isAstLike(body))) {
330
+ return;
331
+ }
332
+ const hoisted = new Set();
333
+ // eslint-disable-next-line no-use-before-define
334
+ collectHoistedVarBindings(body, hoisted);
335
+ for (const name of hoisted) {
336
+ out.add(name);
337
+ }
338
+ };
339
+ const collectFunctionScopeBindingsEx = (scope) => {
340
+ const paramBindings = collectParamBindings(scope);
341
+ const bindings = new Set(paramBindings);
342
+ addHoistedVarsFromBody(scope, bindings);
343
+ return { bindings, paramBindings };
344
+ };
345
+ const collectFunctionScopeBindings = (scope) => collectFunctionScopeBindingsEx(scope).bindings;
346
+ const collectCatchScopeBindings = (scope) => {
347
+ const bindings = new Set();
348
+ const { param } = scope;
349
+ collectPatternBindings(param, bindings);
350
+ return bindings;
351
+ };
352
+ const collectForScopeBindings = (scope) => {
353
+ const bindings = new Set();
354
+ if (scope.type === 'ForStatement') {
355
+ const { init } = scope;
356
+ collectBlockScopedDeclaratorBindings(init, bindings);
357
+ }
358
+ else {
359
+ const { left } = scope;
360
+ collectBlockScopedDeclaratorBindings(left, bindings);
361
+ }
362
+ return bindings;
363
+ };
364
+ const addFunctionDeclarationName = (stmt, out) => {
365
+ if (stmt.type !== 'FunctionDeclaration') {
366
+ return;
367
+ }
368
+ const { id } = stmt;
369
+ const fnName = identifierName(id);
370
+ if (fnName) {
371
+ out.add(fnName);
372
+ }
373
+ };
374
+ const addClassDeclarationName = (stmt, out) => {
375
+ if (stmt.type !== 'ClassDeclaration') {
376
+ return;
377
+ }
378
+ const { id } = stmt;
379
+ const className = identifierName(id);
380
+ if (className) {
381
+ out.add(className);
382
+ }
383
+ };
384
+ const collectBlockScopedStatementListBindings = (statements, out) => {
385
+ if (!statements) {
386
+ return;
387
+ }
388
+ for (const stmt of statements) {
389
+ collectBlockScopedDeclaratorBindings(stmt, out);
390
+ addFunctionDeclarationName(stmt, out);
391
+ addClassDeclarationName(stmt, out);
392
+ }
393
+ };
394
+ const collectBlockStatementBindings = (scope) => {
395
+ const bindings = new Set();
396
+ const { body } = scope;
397
+ collectBlockScopedStatementListBindings(body, bindings);
398
+ // Static initializer blocks own their own VariableEnvironment (per ES spec),
399
+ // so `var` declarations inside them do not escape into the enclosing class
400
+ // or function scope. `collectHoistedVarBindings` correctly refuses to cross
401
+ // a `StaticBlock` boundary from the outside, which means nothing else will
402
+ // register these bindings. Hoist them here so `var result = trail.blaze(...)`
403
+ // inside a `static { ... }` block is tracked against the block itself.
404
+ if (scope.type === 'StaticBlock') {
405
+ // `collectHoistedVarBindings` is called with the StaticBlock as the root,
406
+ // so the own-VariableEnvironment check (which refuses to descend *into* a
407
+ // nested StaticBlock) does not short-circuit traversal of the node itself.
408
+ // eslint-disable-next-line no-use-before-define
409
+ collectHoistedVarBindings(scope, bindings);
410
+ }
411
+ return bindings;
412
+ };
413
+ /**
414
+ * Collect the local binding names introduced directly in this scope's own
415
+ * declarations (params + var/let/const/catch/for declarations), without
416
+ * descending into nested function or block scopes.
417
+ */
418
+ const collectScopeBindings = (scope) => {
419
+ if (FUNCTION_SCOPE_TYPES.has(scope.type)) {
420
+ return collectFunctionScopeBindings(scope);
421
+ }
422
+ if (scope.type === 'CatchClause') {
423
+ return collectCatchScopeBindings(scope);
424
+ }
425
+ if (scope.type === 'ForStatement' ||
426
+ scope.type === 'ForInStatement' ||
427
+ scope.type === 'ForOfStatement') {
428
+ return collectForScopeBindings(scope);
429
+ }
430
+ if (scope.type === 'BlockStatement' || scope.type === 'StaticBlock') {
431
+ return collectBlockStatementBindings(scope);
432
+ }
433
+ return new Set();
44
434
  };
45
- const advancePendingCalls = (line, filePath, lineNumber, pendingCalls, diagnostics) => {
46
- for (let j = pendingCalls.length - 1; j >= 0; j -= 1) {
47
- const pendingCall = pendingCalls[j];
48
- if (pendingCall && isPendingUse(line, pendingCall.variableName)) {
49
- diagnostics.push(createMissingAwaitDiagnostic(filePath, lineNumber));
50
- pendingCalls.splice(j, 1);
51
- }
52
- else if (pendingCall) {
53
- pendingCall.remainingLines -= 1;
54
- if (pendingCall.remainingLines <= 0) {
55
- pendingCalls.splice(j, 1);
435
+ const scopeKindForNode = (node) => {
436
+ if (FUNCTION_SCOPE_TYPES.has(node.type)) {
437
+ return 'function';
438
+ }
439
+ if (node.type === 'CatchClause') {
440
+ return 'catch';
441
+ }
442
+ if (node.type === 'ForStatement' ||
443
+ node.type === 'ForInStatement' ||
444
+ node.type === 'ForOfStatement') {
445
+ return 'for';
446
+ }
447
+ return 'block';
448
+ };
449
+ /**
450
+ * True when a nested node owns its own VariableEnvironment and therefore stops
451
+ * `var` hoisting from crossing into the enclosing function/program scope.
452
+ * Covers function-like nodes and `StaticBlock` (ECMAScript: static blocks
453
+ * introduce their own LexicalEnvironment and VariableEnvironment).
454
+ */
455
+ const ownsVariableEnvironment = (node) => FUNCTION_SCOPE_TYPES.has(node.type) || node.type === 'StaticBlock';
456
+ const collectHoistedVarBindings = (root, out) => {
457
+ const visit = (node, isRoot) => {
458
+ // Nested var-environment owners (functions, static blocks) do not leak
459
+ // their `var`s to the enclosing scope.
460
+ if (!isRoot && ownsVariableEnvironment(node)) {
461
+ return;
462
+ }
463
+ if (node.type === 'VariableDeclaration' && isVarDeclaration(node)) {
464
+ collectVariableDeclarationBindings(node, out);
465
+ }
466
+ for (const val of Object.values(node)) {
467
+ if (Array.isArray(val)) {
468
+ for (const item of val) {
469
+ if (isAstLike(item)) {
470
+ visit(item, false);
471
+ }
472
+ }
473
+ }
474
+ else if (isAstLike(val)) {
475
+ visit(val, false);
56
476
  }
57
477
  }
478
+ };
479
+ visit(root, true);
480
+ };
481
+ const pendingKey = (scopeId, name) => `${scopeId}\u0000${name}`;
482
+ /**
483
+ * Resolve an identifier use to the nearest enclosing scope frame that binds
484
+ * the name. Returns `null` if no frame binds it.
485
+ */
486
+ const resolveNearestScope = (name, stack) => {
487
+ for (let i = stack.length - 1; i >= 0; i -= 1) {
488
+ const frame = stack[i];
489
+ if (frame && frame.bindings.has(name)) {
490
+ return frame;
491
+ }
492
+ }
493
+ return null;
494
+ };
495
+ /**
496
+ * Resolve the blaze call to a `{ name, declarator }` pair when it is the init
497
+ * of a `VariableDeclarator` (directly, through parens, or as a branch of a
498
+ * `ConditionalExpression` init). Returns null otherwise.
499
+ */
500
+ const resolveBlazeBinding = (blazeCall, parents) => {
501
+ const name = extractAssignedBinding(blazeCall, parents);
502
+ if (!name) {
503
+ return null;
504
+ }
505
+ // Mirror `extractAssignedBinding`: unwrap parens and branch-position
506
+ // conditionals so the stored declaration node points at the
507
+ // `VariableDeclarator`, not at an intermediate `ParenthesizedExpression`
508
+ // or `ConditionalExpression`.
509
+ const outer = skipParensAndBranchConditionals(blazeCall, parents);
510
+ const declarator = parents.get(outer);
511
+ return declarator ? { declarator, name } : null;
512
+ };
513
+ /**
514
+ * Resolve the blaze call to a `{ name, assignment }` pair when it is the RHS
515
+ * of a plain `=` `AssignmentExpression` with an `Identifier` LHS (directly,
516
+ * through parens, or as a branch of a conditional/logical expression).
517
+ *
518
+ * Covers patterns like:
519
+ * let result;
520
+ * result = trail.blaze(input, ctx);
521
+ * result.isOk();
522
+ *
523
+ * Member-expression LHS (`obj.result = blaze(...)`) is intentionally skipped —
524
+ * those are property writes, not bare bindings we can track by name.
525
+ */
526
+ const extractPlainIdentifierAssignmentName = (parent) => {
527
+ if (!parent || parent.type !== 'AssignmentExpression') {
528
+ return null;
529
+ }
530
+ const { operator, left } = parent;
531
+ // Only plain `=` assignments to a bare identifier. Member-expression LHS
532
+ // (`obj.result = blaze(...)`) is a property write, not a bare binding we
533
+ // can track by name.
534
+ if (operator !== '=' || !left || left.type !== 'Identifier') {
535
+ return null;
536
+ }
537
+ return identifierName(left);
538
+ };
539
+ const resolveBlazeAssignment = (blazeCall, parents) => {
540
+ const outer = skipParensAndBranchConditionals(blazeCall, parents);
541
+ const parent = parents.get(outer);
542
+ const name = extractPlainIdentifierAssignmentName(parent);
543
+ return name && parent ? { assignment: parent, name } : null;
544
+ };
545
+ /**
546
+ * True when `declarator` is a `VariableDeclarator` whose parent
547
+ * `VariableDeclaration` uses the `var` kind. Such declarators re-initialize
548
+ * a same-named function parameter rather than shadowing it, because `var`
549
+ * and parameters share the function's VariableEnvironment.
550
+ */
551
+ const isVarDeclaratorOfParamName = (declarator, parents) => {
552
+ if (declarator.type !== 'VariableDeclarator') {
553
+ return false;
554
+ }
555
+ const decl = parents.get(declarator);
556
+ return isVarDeclaration(decl);
557
+ };
558
+ /**
559
+ * True when `node` is a plain `=` `AssignmentExpression` with an `Identifier`
560
+ * LHS. Such an assignment writes to the existing binding for that name — if
561
+ * that name is a function parameter, the assignment re-initializes the
562
+ * parameter's slot in the VariableEnvironment, just like `var <name> = ...`.
563
+ * Compound assignments (`+=`, `??=`, etc.) are excluded because they do not
564
+ * unconditionally replace the slot with the blaze result.
565
+ */
566
+ const isAssignmentToParamName = (node) => {
567
+ if (node.type !== 'AssignmentExpression') {
568
+ return false;
569
+ }
570
+ const { operator, left } = node;
571
+ return operator === '=' && left?.type === 'Identifier';
572
+ };
573
+ const recordPendingBinding = (blazeCall, state) => {
574
+ const binding = resolveBlazeBinding(blazeCall, state.parents) ??
575
+ (() => {
576
+ const asn = resolveBlazeAssignment(blazeCall, state.parents);
577
+ return asn ? { declarator: asn.assignment, name: asn.name } : null;
578
+ })();
579
+ if (!binding) {
580
+ return;
581
+ }
582
+ const { name, declarator } = binding;
583
+ // The pending binding lives in the nearest scope that declares `name`.
584
+ // That is always the innermost scope in the current stack, because the
585
+ // variable declaration's id was contributed to its enclosing scope's
586
+ // bindings when that scope was entered.
587
+ const owningFrame = resolveNearestScope(name, state.scopeStack);
588
+ if (!owningFrame) {
589
+ return;
590
+ }
591
+ // If the name resolves to a function parameter, the `var` that visually
592
+ // appears to declare it is redundant — the parameter is the real binding,
593
+ // and parameters are not pending `.blaze()` results.
594
+ //
595
+ // Carve-out: a `var <name> = blaze(...)` *initializer* inside the same
596
+ // function body legitimately re-binds the parameter at that point. `var`
597
+ // and parameters share the function's VariableEnvironment, so the `var`
598
+ // writes to the existing parameter slot and the subsequent use resolves
599
+ // to the freshly-assigned `.blaze()` result. Treat that as a pending
600
+ // binding.
601
+ //
602
+ // The same logic applies to a bare `result = blaze(...)` assignment: it
603
+ // writes to the parameter's existing slot in the same VariableEnvironment,
604
+ // so the subsequent `result.isOk()` observes the blaze result. Only
605
+ // compound assignments (`+=`, `??=`, etc.) and member-expression LHS fall
606
+ // through the param-shadow suppression, because they do not
607
+ // unconditionally replace the parameter slot with the blaze result.
608
+ if (owningFrame.paramBindings?.has(name) &&
609
+ !isVarDeclaratorOfParamName(declarator, state.parents) &&
610
+ !isAssignmentToParamName(declarator)) {
611
+ return;
612
+ }
613
+ state.pendingByScopeAndName.set(pendingKey(owningFrame.id, name), {
614
+ declarationNode: declarator,
615
+ name,
616
+ scopeId: owningFrame.id,
617
+ });
618
+ };
619
+ const CARRIER_CHILDREN = {
620
+ ConditionalExpression: (expr) => {
621
+ const { consequent, alternate } = expr;
622
+ return [consequent, alternate];
623
+ },
624
+ LogicalExpression: (expr) => {
625
+ const { left, right } = expr;
626
+ return [left, right];
627
+ },
628
+ };
629
+ const unwrapTransparentWrapper = (expr) => expr.expression;
630
+ // biome-ignore lint/style/useConst: hoisted for recursive call
631
+ // eslint-disable-next-line func-style
632
+ function rhsCarriesBlazeReinit(expr) {
633
+ if (!expr) {
634
+ return false;
635
+ }
636
+ if (TRANSPARENT_WRAPPER_TYPES.has(expr.type)) {
637
+ return rhsCarriesBlazeReinit(unwrapTransparentWrapper(expr));
638
+ }
639
+ const extractor = CARRIER_CHILDREN[expr.type];
640
+ if (extractor) {
641
+ return extractor(expr).some(rhsCarriesBlazeReinit);
642
+ }
643
+ return isBlazeCall(expr);
644
+ }
645
+ /**
646
+ * Nullish/falsy-skip compound assignments (`??=`, `||=`) only write to the slot
647
+ * when the LHS is nullish or falsy. A pending `.blaze()` binding holds a
648
+ * truthy `Promise<Result>`, so the RHS never runs and the pending binding must
649
+ * survive them.
650
+ *
651
+ * `&&=` is intentionally excluded: it writes when the LHS is truthy, so a
652
+ * pending `Promise<Result>` is *always* overwritten by the RHS. That matches
653
+ * the clearing behavior of mathematical compound operators (`+=`, `-=`, ...).
654
+ */
655
+ const NULLISH_SKIP_OPERATORS = new Set(['??=', '||=']);
656
+ const extractIdentifierAssignment = (node) => {
657
+ if (node.type !== 'AssignmentExpression') {
658
+ return null;
58
659
  }
660
+ const { operator, left, right } = node;
661
+ if (!(operator && left) || left.type !== 'Identifier') {
662
+ return null;
663
+ }
664
+ const name = identifierName(left);
665
+ return name ? { name, operator, right } : null;
666
+ };
667
+ const resolvePendingKeyFor = (name, state) => {
668
+ const frame = resolveNearestScope(name, state.scopeStack);
669
+ if (!frame) {
670
+ return null;
671
+ }
672
+ const key = pendingKey(frame.id, name);
673
+ return state.pendingByScopeAndName.has(key) ? key : null;
674
+ };
675
+ /**
676
+ * Handle a plain `=` assignment (or clearing compound assignment) to a bare
677
+ * identifier whose name currently has a pending `.blaze()` binding in scope.
678
+ *
679
+ * A plain `=` whose RHS carries another blaze call leaves the pending entry
680
+ * alone — `recordPendingBinding` will re-register it when the blaze call
681
+ * itself is visited. Otherwise, clear the pending entry: the identifier has
682
+ * been overwritten with a non-Result value, so the original
683
+ * `result.isOk()`-style diagnostic no longer applies.
684
+ *
685
+ * Nullish/falsy-skip compound assignments (`??=`, `||=`) are ignored — a
686
+ * truthy pending `Promise<Result>` causes the RHS to be skipped, so the
687
+ * pending binding is preserved. `&&=` is *not* in this set: a truthy LHS
688
+ * causes the RHS to always run, overwriting the pending slot, so it falls
689
+ * through to the clearing path alongside `+=`, `-=`, etc. Member-expression
690
+ * LHS is ignored because it writes a property, not the tracked identifier.
691
+ */
692
+ const handleAssignmentReassignment = (node, state) => {
693
+ const assignment = extractIdentifierAssignment(node);
694
+ if (!assignment || NULLISH_SKIP_OPERATORS.has(assignment.operator)) {
695
+ return;
696
+ }
697
+ const key = resolvePendingKeyFor(assignment.name, state);
698
+ if (!key) {
699
+ return;
700
+ }
701
+ // Plain `=` with a blaze-carrying RHS will re-register via
702
+ // `recordPendingBinding` when the blaze call itself is visited. Other
703
+ // compound operators (`+=`, `-=`, `*=`, etc.) produce a primitive value
704
+ // from the existing slot, so they always clear.
705
+ if (assignment.operator === '=' && rhsCarriesBlazeReinit(assignment.right)) {
706
+ return;
707
+ }
708
+ state.pendingByScopeAndName.delete(key);
709
+ };
710
+ const reportMissingAwait = (node, state) => {
711
+ if (state.reportedAt.has(node.start)) {
712
+ return;
713
+ }
714
+ state.reportedAt.add(node.start);
715
+ state.diagnostics.push(createMissingAwaitDiagnostic(state.filePath, offsetToLine(state.sourceCode, node.start)));
716
+ };
717
+ const findPendingBindingForUse = (node, state) => {
718
+ if (!isResultAccessorMember(node)) {
719
+ return null;
720
+ }
721
+ const name = getIdentifierObjectName(node);
722
+ if (!name) {
723
+ return null;
724
+ }
725
+ const frame = resolveNearestScope(name, state.scopeStack);
726
+ if (!frame) {
727
+ return null;
728
+ }
729
+ return state.pendingByScopeAndName.get(pendingKey(frame.id, name)) ?? null;
730
+ };
731
+ const checkPendingAccess = (node, state) => {
732
+ const binding = findPendingBindingForUse(node, state);
733
+ if (!binding) {
734
+ return;
735
+ }
736
+ // Declaration must precede the use. Use source offsets for ordering.
737
+ if (node.start < binding.declarationNode.end) {
738
+ return;
739
+ }
740
+ reportMissingAwait(node, state);
741
+ };
742
+ /**
743
+ * If the blaze call is the init of a VariableDeclarator whose id is an
744
+ * ObjectPattern that destructures any known Result accessor property,
745
+ * return the declarator node. Otherwise null.
746
+ *
747
+ * Catches the core missing-await shape when written as destructuring:
748
+ * `const { isOk } = entityShow.blaze(input, ctx)` — no await, immediate
749
+ * access to a Result accessor, should fire.
750
+ */
751
+ const propertyDestructuresResultAccessor = (prop) => {
752
+ if (prop.type === 'RestElement') {
753
+ return false;
754
+ }
755
+ const { key } = prop;
756
+ const keyName = identifierName(key);
757
+ return keyName !== null && RESULT_ACCESSOR_PROPERTIES.has(keyName);
59
758
  };
60
- const processLine = (line, filePath, lineNumber, pendingCalls, diagnostics) => {
61
- if (isDirectResultAccess(line)) {
62
- diagnostics.push(createMissingAwaitDiagnostic(filePath, lineNumber));
759
+ const objectPatternHasResultAccessorKey = (pattern) => {
760
+ const { properties } = pattern;
761
+ return properties?.some(propertyDestructuresResultAccessor) ?? false;
762
+ };
763
+ const getDestructuredResultAccessorDeclarator = (blazeCall, parents) => {
764
+ // Unwrap any wrapping parentheses and branch-position conditionals so
765
+ // `const { isOk } = (trail.blaze(...));` and
766
+ // `const { isOk } = cond ? trail.blaze(...) : fallback;` are treated as
767
+ // `const { isOk } = trail.blaze(...);`.
768
+ const outer = skipParensAndBranchConditionals(blazeCall, parents);
769
+ const parent = parents.get(outer);
770
+ if (!parent || parent.type !== 'VariableDeclarator') {
771
+ return null;
772
+ }
773
+ const { id } = parent;
774
+ if (!id || id.type !== 'ObjectPattern') {
775
+ return null;
776
+ }
777
+ return objectPatternHasResultAccessorKey(id) ? parent : null;
778
+ };
779
+ const visitBlazeCall = (node, state) => {
780
+ if (!isBlazeCall(node) || isAwaited(node, state.parents)) {
781
+ return;
782
+ }
783
+ if (hasDirectResultAccess(node, state.parents)) {
784
+ reportMissingAwait(node, state);
63
785
  return;
64
786
  }
65
- const variableName = trackPendingCall(line);
66
- if (variableName) {
67
- addPendingCall(pendingCalls, variableName, lineNumber);
787
+ const destructuredDeclarator = getDestructuredResultAccessorDeclarator(node, state.parents);
788
+ if (destructuredDeclarator) {
789
+ reportMissingAwait(destructuredDeclarator, state);
790
+ return;
68
791
  }
69
- advancePendingCalls(line, filePath, lineNumber, pendingCalls, diagnostics);
792
+ recordPendingBinding(node, state);
70
793
  };
71
- const scanSourceCode = (sourceCode, filePath) => {
72
- const diagnostics = [];
73
- const lines = sourceCode.split('\n');
74
- const pendingCalls = [];
75
- for (let i = 0; i < lines.length; i += 1) {
76
- const line = lines[i];
77
- if (!line) {
78
- continue;
794
+ const visitNode = (node, state) => {
795
+ visitBlazeCall(node, state);
796
+ checkPendingAccess(node, state);
797
+ };
798
+ /**
799
+ * Post-order visitor for assignment re-assignment clearing.
800
+ *
801
+ * `handleAssignmentReassignment` must run *after* the RHS subtree has been
802
+ * walked. Otherwise a self-referential `result = result.value` would clear
803
+ * the pending entry before the RHS `result.value` access is observed — the
804
+ * missing-await diagnostic would disappear even though the write produced
805
+ * a non-Result value from the same pending slot.
806
+ */
807
+ const visitNodePost = (node, state) => {
808
+ handleAssignmentReassignment(node, state);
809
+ };
810
+ const pushScopeIfBoundary = (node, state) => {
811
+ if (!isScopeBoundary(node)) {
812
+ return false;
813
+ }
814
+ const kind = scopeKindForNode(node);
815
+ if (kind === 'function') {
816
+ const { bindings, paramBindings } = collectFunctionScopeBindingsEx(node);
817
+ state.scopeStack.push({
818
+ bindings,
819
+ id: state.nextScopeId,
820
+ kind,
821
+ paramBindings,
822
+ });
823
+ }
824
+ else {
825
+ state.scopeStack.push({
826
+ bindings: collectScopeBindings(node),
827
+ id: state.nextScopeId,
828
+ kind,
829
+ });
830
+ }
831
+ state.nextScopeId += 1;
832
+ return true;
833
+ };
834
+ const walkChild = (child, state) => {
835
+ if (child && typeof child === 'object' && child.type) {
836
+ // eslint-disable-next-line no-use-before-define
837
+ walkWithScopes(child, state);
838
+ }
839
+ };
840
+ const walkChildren = (node, state) => {
841
+ for (const val of Object.values(node)) {
842
+ if (Array.isArray(val)) {
843
+ for (const item of val) {
844
+ walkChild(item, state);
845
+ }
79
846
  }
80
- processLine(line, filePath, i + 1, pendingCalls, diagnostics);
847
+ else {
848
+ walkChild(val, state);
849
+ }
850
+ }
851
+ };
852
+ // biome-ignore lint/style/useConst: hoisted for mutual recursion with walkChildren
853
+ // eslint-disable-next-line func-style
854
+ function walkWithScopes(node, state) {
855
+ const pushed = pushScopeIfBoundary(node, state);
856
+ visitNode(node, state);
857
+ walkChildren(node, state);
858
+ visitNodePost(node, state);
859
+ if (pushed) {
860
+ state.scopeStack.pop();
81
861
  }
82
- return diagnostics;
862
+ }
863
+ const collectProgramBindings = (ast) => {
864
+ const bindings = new Set();
865
+ const programBody = ast.body;
866
+ // Top-level `let`/`const`/function declarations.
867
+ collectBlockScopedStatementListBindings(programBody, bindings);
868
+ // Top-level `var`s are program-scoped; also hoist any `var`s nested
869
+ // inside blocks/loops at program level.
870
+ collectHoistedVarBindings(ast, bindings);
871
+ return bindings;
872
+ };
873
+ const analyze = (ast, sourceCode, filePath) => {
874
+ const state = {
875
+ diagnostics: [],
876
+ filePath,
877
+ nextScopeId: 1,
878
+ parents: buildParentMap(ast),
879
+ pendingByScopeAndName: new Map(),
880
+ reportedAt: new Set(),
881
+ scopeStack: [
882
+ { bindings: collectProgramBindings(ast), id: 0, kind: 'program' },
883
+ ],
884
+ sourceCode,
885
+ };
886
+ walkWithScopes(ast, state);
887
+ return state.diagnostics;
83
888
  };
84
889
  /**
85
890
  * Flags code that assumes `.blaze()` returns a synchronous result.
@@ -89,7 +894,11 @@ export const noSyncResultAssumption = {
89
894
  if (isTestFile(filePath) || isFrameworkInternalFile(filePath)) {
90
895
  return [];
91
896
  }
92
- return scanSourceCode(stripQuotedContent(sourceCode), filePath);
897
+ const ast = parse(filePath, sourceCode);
898
+ if (!ast) {
899
+ return [];
900
+ }
901
+ return analyze(ast, sourceCode, filePath);
93
902
  },
94
903
  description: 'Disallow treating .blaze() as synchronous after normalization. Always await the returned Promise<Result>.',
95
904
  name: 'no-sync-result-assumption',