@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
@@ -0,0 +1,778 @@
1
+ /**
2
+ * Self-governance rule: warden rules must inspect the AST via the helpers in
3
+ * `./ast.ts` rather than regex-scanning raw source text. Raw-text scans
4
+ * produce false positives on string literals, template payloads, and
5
+ * docstrings — see TRL-335 and ADR-0036.
6
+ *
7
+ * Three detection families are enforced:
8
+ *
9
+ * 1. `rawScanSite` — string methods on a raw-source identifier, e.g.
10
+ * `sourceCode.split(/\n/)`, `rawText.match(...)`, `text.replace(...)`.
11
+ * 2. `regexScanSite` — regex-receiver methods consuming a raw-source
12
+ * argument, e.g. `/re/.test(sourceCode)`, `new RegExp(...).exec(text)`.
13
+ * 3. `regexConstructionSite` — constructing a regex directly from a raw
14
+ * source identifier, e.g. `new RegExp(sourceCode)`, `RegExp(rawText, 'g')`.
15
+ * Interpolating raw source into a regex constructor is the same class of
16
+ * bug as scanning with one — see TRL-345.
17
+ *
18
+ * This rule is path-anchored to this package's own `src/rules/` directory so
19
+ * it never fires against a consumer repo that happens to share the same
20
+ * folder layout. `ast.ts` itself is excluded because it IS the raw-text
21
+ * interface to the parser; `types.ts`, `index.ts`, `registry-names.ts`, and
22
+ * anything under `__tests__` are also excluded.
23
+ */
24
+ import { basename as pathBasename, dirname, resolve, sep } from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
26
+ import { offsetToLine, parse, walk } from './ast.js';
27
+ const RULE_NAME = 'warden-rules-use-ast';
28
+ /**
29
+ * Absolute path to this package's rules directory, resolved from the rule's
30
+ * own module URL. Anchoring to the real on-disk location prevents the rule
31
+ * from firing against a foreign `packages/warden/src/rules/` in a consumer
32
+ * repository that happens to share the same folder structure.
33
+ *
34
+ * Dist-layout safeguard: when this module is bundled/transpiled to `dist/`
35
+ * (e.g. `packages/warden/dist/rules/warden-rules-use-ast.js`), the files
36
+ * being linted still live under `src/rules/`. A strict equality check
37
+ * against only the dist directory would cause the rule to silently emit
38
+ * zero diagnostics — a silent no-op. To keep the anchor robust, we compute
39
+ * a source-equivalent dir by substituting `/dist/` with `/src/` on the
40
+ * resolved path and accept either. This preserves the anti-false-positive
41
+ * guarantee from TRL-341 (we still require an exact directory match, not a
42
+ * suffix match) while surviving a future bundling change.
43
+ */
44
+ const SELF_MODULE_DIR = resolve(dirname(fileURLToPath(import.meta.url)));
45
+ /**
46
+ * Replace only the LAST occurrence of `/dist/` with `/src/`. A blanket
47
+ * `replaceAll` over-substitutes on paths that contain other `/dist/`
48
+ * segments higher up (e.g. `/home/runner/dist-artifacts/warden/dist/rules/`
49
+ * would incorrectly become `/home/runner/src-artifacts/warden/src/rules/`,
50
+ * a nonexistent directory — silently defeating the rule).
51
+ *
52
+ * Exported for unit testing. Not part of the public rule API.
53
+ */
54
+ export const replaceLastDistSegmentWithSrc = (path) => {
55
+ const distSegment = `${sep}dist${sep}`;
56
+ const srcSegment = `${sep}src${sep}`;
57
+ const lastIdx = path.lastIndexOf(distSegment);
58
+ if (lastIdx === -1) {
59
+ return path;
60
+ }
61
+ return (path.slice(0, lastIdx) +
62
+ srcSegment +
63
+ path.slice(lastIdx + distSegment.length));
64
+ };
65
+ const SELF_RULES_DIRS = new Set(SELF_MODULE_DIR.includes(`${sep}dist${sep}`)
66
+ ? [SELF_MODULE_DIR, replaceLastDistSegmentWithSrc(SELF_MODULE_DIR)]
67
+ : [SELF_MODULE_DIR]);
68
+ /**
69
+ * Stems of files in `src/rules/` (and their bundled `dist/rules/` twins) that
70
+ * are NOT themselves warden rules and therefore must not be checked. `ast` is
71
+ * the raw-text interface to the parser and legitimately touches source text;
72
+ * the others are support modules without a `check()` function.
73
+ */
74
+ const EXCLUDED_STEMS = [
75
+ 'ast',
76
+ 'index',
77
+ 'registry-names',
78
+ 'scan',
79
+ 'specs',
80
+ 'structure',
81
+ 'types',
82
+ ];
83
+ /**
84
+ * Both `.ts` (source layout) and `.js` (dist layout) basenames must be
85
+ * excluded so the rule stays silent when pointed at a bundled tree. The
86
+ * dist-layout `ast.js` contains the same raw-text parser entry point as
87
+ * `ast.ts` and would false-positive if scanned.
88
+ */
89
+ const EXCLUDED_BASENAMES = new Set(EXCLUDED_STEMS.flatMap((stem) => [`${stem}.ts`, `${stem}.js`]));
90
+ const isTargetFile = (filePath) => {
91
+ const absolute = resolve(filePath);
92
+ if (!SELF_RULES_DIRS.has(dirname(absolute))) {
93
+ return false;
94
+ }
95
+ const basename = pathBasename(absolute);
96
+ if (EXCLUDED_BASENAMES.has(basename)) {
97
+ return false;
98
+ }
99
+ if (basename.endsWith('.test.ts') || basename.endsWith('.test.js')) {
100
+ return false;
101
+ }
102
+ return true;
103
+ };
104
+ /**
105
+ * Names of the WardenRule methods that receive raw source text as their
106
+ * first parameter. The first parameter's *actual binding name* (not a fixed
107
+ * list of names) is what we track via scope analysis — see `buildSourceParamIndex`.
108
+ *
109
+ * `checkTopo` does not receive raw source text (it takes a `Topo`) so it is
110
+ * intentionally excluded.
111
+ */
112
+ const SOURCE_PARAM_METHOD_NAMES = new Set([
113
+ 'check',
114
+ 'checkWithContext',
115
+ ]);
116
+ /**
117
+ * String methods that indicate raw-text *scanning* when called on a
118
+ * source-text identifier. Deliberately narrow: these are the patterns that
119
+ * produce false positives on string literals, template payloads, and
120
+ * docstrings — the regression TRL-333/TRL-334/TRL-335 fixed.
121
+ *
122
+ * Not flagged: `.slice`, `.substring`, `.indexOf`, `.includes`. These also
123
+ * take source text as input, but have legitimate AST-adjacent uses — e.g.
124
+ * `sourceCode.slice(node.start, node.end)` to recover a node's original
125
+ * text from an AST-resolved range, or `sourceCode.includes('marker')` as a
126
+ * fast-bail check before parsing. Flagging them would produce false
127
+ * positives on idiomatic rules.
128
+ */
129
+ const RAW_SCAN_METHODS = new Set([
130
+ 'match',
131
+ 'matchAll',
132
+ 'replace',
133
+ 'replaceAll',
134
+ 'search',
135
+ 'split',
136
+ ]);
137
+ /**
138
+ * Methods on a regex receiver that consume a raw-text argument. Flagged
139
+ * when the argument is a raw-source identifier (`sourceCode`, `text`, etc.).
140
+ */
141
+ const REGEX_SCAN_METHODS = new Set(['exec', 'test']);
142
+ const getIdentifierName = (node) => {
143
+ if (!node || node.type !== 'Identifier') {
144
+ return null;
145
+ }
146
+ const { name } = node;
147
+ return typeof name === 'string' ? name : null;
148
+ };
149
+ /**
150
+ * Extract the `object`/`property` of a non-computed member call, or null
151
+ * for anything else. Keeps `rawScanSite` under the max-statements budget.
152
+ */
153
+ const memberCallParts = (node) => {
154
+ if (node.type !== 'CallExpression') {
155
+ return null;
156
+ }
157
+ const { callee } = node;
158
+ if (!callee ||
159
+ (callee.type !== 'MemberExpression' &&
160
+ callee.type !== 'StaticMemberExpression')) {
161
+ return null;
162
+ }
163
+ const { object, property, computed } = callee;
164
+ return computed ? null : { object, property };
165
+ };
166
+ const rawScanSite = (node) => {
167
+ const parts = memberCallParts(node);
168
+ if (!parts || !parts.object) {
169
+ return null;
170
+ }
171
+ const receiver = getIdentifierName(parts.object);
172
+ if (!receiver) {
173
+ return null;
174
+ }
175
+ const methodName = getIdentifierName(parts.property);
176
+ if (!methodName || !RAW_SCAN_METHODS.has(methodName)) {
177
+ return null;
178
+ }
179
+ return {
180
+ identifier: parts.object,
181
+ identifierName: receiver,
182
+ methodName,
183
+ start: node.start,
184
+ };
185
+ };
186
+ /**
187
+ * True when `node` is a regex-producing expression: a regex literal
188
+ * (`/foo/`), `new RegExp(...)`, or a plain `RegExp(...)` call.
189
+ */
190
+ const isRegexProducer = (node) => {
191
+ if (!node) {
192
+ return false;
193
+ }
194
+ if (node.type === 'Literal' && 'regex' in node && node['regex']) {
195
+ return true;
196
+ }
197
+ if (node.type === 'RegExpLiteral') {
198
+ return true;
199
+ }
200
+ if (node.type === 'NewExpression' || node.type === 'CallExpression') {
201
+ const { callee } = node;
202
+ return getIdentifierName(callee) === 'RegExp';
203
+ }
204
+ return false;
205
+ };
206
+ /**
207
+ * First identifier argument of a call expression, or null. The returned
208
+ * identifier must still resolve to a tracked source-param binding (see
209
+ * `resolvesToSourceParam`) before a diagnostic is emitted — this pre-filter
210
+ * only narrows the candidate arg.
211
+ */
212
+ const firstIdentifierArgument = (node) => {
213
+ const args = node
214
+ .arguments;
215
+ if (!args) {
216
+ return null;
217
+ }
218
+ for (const arg of args) {
219
+ const name = getIdentifierName(arg);
220
+ if (name) {
221
+ return { identifier: arg, name };
222
+ }
223
+ }
224
+ return null;
225
+ };
226
+ const regexScanMethodName = (parts) => {
227
+ if (!isRegexProducer(parts.object)) {
228
+ return null;
229
+ }
230
+ const methodName = getIdentifierName(parts.property);
231
+ if (!methodName || !REGEX_SCAN_METHODS.has(methodName)) {
232
+ return null;
233
+ }
234
+ return methodName;
235
+ };
236
+ /**
237
+ * Detects `/regex/.test(sourceCode)`, `new RegExp(...).exec(text)`, and
238
+ * similar regex-receiver calls that consume a raw-source identifier.
239
+ */
240
+ const regexScanSite = (node) => {
241
+ const parts = memberCallParts(node);
242
+ if (!parts) {
243
+ return null;
244
+ }
245
+ const methodName = regexScanMethodName(parts);
246
+ if (!methodName) {
247
+ return null;
248
+ }
249
+ const arg = firstIdentifierArgument(node);
250
+ if (!arg) {
251
+ return null;
252
+ }
253
+ return {
254
+ identifier: arg.identifier,
255
+ identifierName: arg.name,
256
+ methodName,
257
+ start: node.start,
258
+ };
259
+ };
260
+ /**
261
+ * Detects `new RegExp(sourceCode)` / `RegExp(rawText, 'g')` — constructing a
262
+ * regex from raw source text. Same anti-pattern family as
263
+ * `sourceCode.match(...)` and `/re/.test(sourceCode)`: raw source fed into a
264
+ * scanner. Fires when the callee is an `Identifier` named `RegExp` and at
265
+ * least one argument is an identifier that resolves, via scope analysis, to
266
+ * the enclosing `check` / `checkWithContext` method's first parameter.
267
+ */
268
+ const regexConstructionSite = (node) => {
269
+ if (node.type !== 'NewExpression' && node.type !== 'CallExpression') {
270
+ return null;
271
+ }
272
+ const { callee } = node;
273
+ if (getIdentifierName(callee) !== 'RegExp') {
274
+ return null;
275
+ }
276
+ const arg = firstIdentifierArgument(node);
277
+ if (!arg) {
278
+ return null;
279
+ }
280
+ return {
281
+ identifier: arg.identifier,
282
+ identifierName: arg.name,
283
+ kind: node.type === 'NewExpression' ? 'new' : 'call',
284
+ start: node.start,
285
+ };
286
+ };
287
+ const DIAGNOSTIC_ADVICE = 'Warden rules must inspect the AST via packages/warden/src/rules/ast.ts helpers, not regex-scan raw source text. ' +
288
+ 'Use findStringLiterals, findTrailDefinitions, findConfigProperty, or a similar AST walker. ' +
289
+ 'Raw-text scanning produces false positives on string literals, template payloads, and docstrings — see TRL-335, ADR-0036.';
290
+ const detectRawScan = (node) => {
291
+ const scan = rawScanSite(node);
292
+ if (!scan) {
293
+ return null;
294
+ }
295
+ return {
296
+ identifier: scan.identifier,
297
+ message: `${RULE_NAME}: ${scan.identifierName}.${scan.methodName}(...) treats source text as a string. ${DIAGNOSTIC_ADVICE}`,
298
+ start: scan.start,
299
+ };
300
+ };
301
+ const detectRegexScan = (node) => {
302
+ const regex = regexScanSite(node);
303
+ if (!regex) {
304
+ return null;
305
+ }
306
+ return {
307
+ identifier: regex.identifier,
308
+ message: `${RULE_NAME}: regex.${regex.methodName}(${regex.identifierName}) scans raw source text. ${DIAGNOSTIC_ADVICE}`,
309
+ start: regex.start,
310
+ };
311
+ };
312
+ const detectRegexConstruction = (node) => {
313
+ const construction = regexConstructionSite(node);
314
+ if (!construction) {
315
+ return null;
316
+ }
317
+ const prefix = construction.kind === 'new' ? 'new RegExp' : 'RegExp';
318
+ return {
319
+ identifier: construction.identifier,
320
+ message: `${RULE_NAME}: ${prefix}(${construction.identifierName}) constructs a regex from raw source text. ${DIAGNOSTIC_ADVICE}`,
321
+ start: construction.start,
322
+ };
323
+ };
324
+ /**
325
+ * Dispatch chain for per-node detectors. Each detector tries one family in
326
+ * priority order. First match wins; descent into children still happens so
327
+ * nested offenses (e.g. a regex scan inside a callback passed to a raw-text
328
+ * scan) are still caught.
329
+ */
330
+ const DETECTORS = [
331
+ detectRawScan,
332
+ detectRegexScan,
333
+ detectRegexConstruction,
334
+ ];
335
+ const detectSite = (node) => {
336
+ for (const detector of DETECTORS) {
337
+ const site = detector(node);
338
+ if (site) {
339
+ return site;
340
+ }
341
+ }
342
+ return null;
343
+ };
344
+ /**
345
+ * Walk inner→outer. The first scope that declares `name` is the binding; the
346
+ * identifier resolves to a tracked source-param only when that declaring
347
+ * scope's `sourceParamName` matches. An identifier with no declaring scope
348
+ * (e.g. a free variable) is not a source-param binding.
349
+ */
350
+ const resolvesToSourceParam = (name, scopes) => {
351
+ for (let i = scopes.length - 1; i >= 0; i -= 1) {
352
+ const scope = scopes[i];
353
+ if (scope && scope.declaredNames.has(name)) {
354
+ return scope.sourceParamName === name;
355
+ }
356
+ }
357
+ return false;
358
+ };
359
+ const FUNCTION_NODE_TYPES = new Set([
360
+ 'ArrowFunctionExpression',
361
+ 'FunctionDeclaration',
362
+ 'FunctionExpression',
363
+ ]);
364
+ /**
365
+ * Name of a function-like node when it is a recognized WardenRule method
366
+ * (`check` or `checkWithContext`). Returns null otherwise.
367
+ *
368
+ * Handles three shapes:
369
+ * - object-literal method shorthand: `{ check(sc) { ... } }`
370
+ * (Property with `method: true`, or MethodDefinition)
371
+ * - arrow/function property: `{ check: (sc) => { ... } }`
372
+ * - top-level function declaration: `function check(sc) { ... }`
373
+ *
374
+ * The context-to-function link is resolved by the caller via the
375
+ * `methodFunctionStarts` map: we pre-walk the AST once to map the start
376
+ * offset of every recognized function to its method name, then consult the
377
+ * map when the scope walker enters that function.
378
+ */
379
+ const methodNameFromKey = (key) => {
380
+ if (!key) {
381
+ return null;
382
+ }
383
+ if (key.type === 'Identifier') {
384
+ return key.name ?? null;
385
+ }
386
+ // String-literal keys like `'check': (sc) => { ... }`.
387
+ if ((key.type === 'Literal' || key.type === 'StringLiteral') &&
388
+ typeof key.value === 'string') {
389
+ return key.value;
390
+ }
391
+ return null;
392
+ };
393
+ const firstParamIdentifierName = (fn) => {
394
+ const { params } = fn;
395
+ const [first] = params ?? [];
396
+ if (!first) {
397
+ return null;
398
+ }
399
+ if (first.type === 'Identifier') {
400
+ return getIdentifierName(first);
401
+ }
402
+ if (first.type === 'AssignmentPattern') {
403
+ const { left } = first;
404
+ return left?.type === 'Identifier' ? getIdentifierName(left) : null;
405
+ }
406
+ return null;
407
+ };
408
+ /**
409
+ * Collect start offsets of function-like AST nodes that represent the body
410
+ * of a recognized WardenRule source-receiving method. Value is the declared
411
+ * first-parameter name, used as `sourceParamName` when the scope walker
412
+ * pushes that function's scope.
413
+ */
414
+ const methodPropertyFunction = (node) => {
415
+ const { key, value } = node;
416
+ const name = methodNameFromKey(key);
417
+ if (!name || !value || !FUNCTION_NODE_TYPES.has(value.type)) {
418
+ return null;
419
+ }
420
+ return SOURCE_PARAM_METHOD_NAMES.has(name) ? { fn: value, name } : null;
421
+ };
422
+ const namedFunctionDeclaration = (node) => {
423
+ const name = getIdentifierName(node.id);
424
+ if (!name || !SOURCE_PARAM_METHOD_NAMES.has(name)) {
425
+ return null;
426
+ }
427
+ return { fn: node, name };
428
+ };
429
+ const recognizedMethodFunction = (node) => {
430
+ if (node.type === 'Property' || node.type === 'MethodDefinition') {
431
+ return methodPropertyFunction(node);
432
+ }
433
+ if (node.type === 'FunctionDeclaration') {
434
+ return namedFunctionDeclaration(node);
435
+ }
436
+ return null;
437
+ };
438
+ const buildSourceParamIndex = (ast) => {
439
+ const index = new Map();
440
+ walk(ast, (node) => {
441
+ const recognized = recognizedMethodFunction(node);
442
+ if (!recognized) {
443
+ return;
444
+ }
445
+ const paramName = firstParamIdentifierName(recognized.fn);
446
+ if (paramName) {
447
+ index.set(recognized.fn.start, paramName);
448
+ }
449
+ });
450
+ return index;
451
+ };
452
+ /**
453
+ * Collect identifier names introduced at this scope by
454
+ * `const`/`let`/`var`/function declarations or function params. We only
455
+ * inspect direct children — nested block statements and nested functions
456
+ * have their own scopes.
457
+ */
458
+ const expandObjectPatternProperty = (property) => {
459
+ if (property.type === 'Property') {
460
+ const { value } = property;
461
+ return value ? [value] : [];
462
+ }
463
+ if (property.type === 'RestElement') {
464
+ const { argument } = property;
465
+ return argument ? [argument] : [];
466
+ }
467
+ return [];
468
+ };
469
+ const PATTERN_EXPANDERS = {
470
+ ArrayPattern: (pattern) => {
471
+ const elements = pattern
472
+ .elements ?? [];
473
+ return elements.filter((el) => el !== null);
474
+ },
475
+ AssignmentPattern: (pattern) => {
476
+ const { left } = pattern;
477
+ return left ? [left] : [];
478
+ },
479
+ ObjectPattern: (pattern) => {
480
+ const properties = pattern.properties ??
481
+ [];
482
+ return properties.flatMap(expandObjectPatternProperty);
483
+ },
484
+ RestElement: (pattern) => {
485
+ const { argument } = pattern;
486
+ return argument ? [argument] : [];
487
+ },
488
+ };
489
+ /**
490
+ * Collect identifier names introduced by a binding pattern (function
491
+ * parameter, destructuring target, etc.). Iterative worklist over
492
+ * {@link PATTERN_EXPANDERS}: each expander yields one level of child
493
+ * patterns, and the loop bottoms out at `Identifier` nodes. The iterative
494
+ * shape avoids mutual recursion so every helper stays under the
495
+ * `max-statements` budget.
496
+ */
497
+ const visitPatternNode = (current, into, worklist) => {
498
+ if (current.type === 'Identifier') {
499
+ const name = getIdentifierName(current);
500
+ if (name) {
501
+ into.add(name);
502
+ }
503
+ return;
504
+ }
505
+ const expand = PATTERN_EXPANDERS[current.type];
506
+ if (expand) {
507
+ worklist.push(...expand(current));
508
+ }
509
+ };
510
+ const collectBindingIdsFromPattern = (pattern, into) => {
511
+ if (!pattern) {
512
+ return;
513
+ }
514
+ const worklist = [pattern];
515
+ while (worklist.length > 0) {
516
+ const current = worklist.pop();
517
+ if (current) {
518
+ visitPatternNode(current, into, worklist);
519
+ }
520
+ }
521
+ };
522
+ const collectParamBindingsFromFunction = (fn) => {
523
+ const paramNames = new Set();
524
+ const params = fn.params ?? [];
525
+ for (const param of params) {
526
+ collectBindingIdsFromPattern(param, paramNames);
527
+ }
528
+ return paramNames;
529
+ };
530
+ const collectHoistedVarsFromFunctionBody = (fn) => {
531
+ const hoistedVarNames = new Set();
532
+ const { body } = fn;
533
+ if (body && typeof body === 'object' && body.type) {
534
+ // eslint-disable-next-line no-use-before-define
535
+ collectHoistedVarBindings(body, hoistedVarNames);
536
+ }
537
+ return hoistedVarNames;
538
+ };
539
+ /**
540
+ * Combine param names and body-level hoisted `var` names into a single
541
+ * declared-names set, while keeping the two subsets addressable. The
542
+ * hoisted set is kept separate so the scope walker can tell whether a
543
+ * source-param identity has been overwritten by a same-named hoisted local —
544
+ * a shadow that would otherwise be invisible because both names live in the
545
+ * same function-scope binding set.
546
+ */
547
+ const collectFunctionScopeBindingsEx = (fn) => {
548
+ const paramNames = collectParamBindingsFromFunction(fn);
549
+ const hoistedVarNames = collectHoistedVarsFromFunctionBody(fn);
550
+ const declaredNames = new Set(paramNames);
551
+ for (const name of hoistedVarNames) {
552
+ declaredNames.add(name);
553
+ }
554
+ return { declaredNames, hoistedVarNames, paramNames };
555
+ };
556
+ const addVariableDeclarationNames = (stmt, names) => {
557
+ const declarations = stmt.declarations ??
558
+ [];
559
+ for (const decl of declarations) {
560
+ collectBindingIdsFromPattern(decl.id, names);
561
+ }
562
+ };
563
+ const isVarVariableDeclaration = (stmt) => stmt.type === 'VariableDeclaration' &&
564
+ stmt.kind === 'var';
565
+ /**
566
+ * True when `node` owns its own VariableEnvironment and therefore stops `var`
567
+ * hoisting from crossing into the enclosing function/program scope.
568
+ */
569
+ const ownsVariableEnvironmentForHoisting = (node) => FUNCTION_NODE_TYPES.has(node.type) || node.type === 'StaticBlock';
570
+ /**
571
+ * Collect `var` declarations that hoist to the nearest function (or program)
572
+ * scope from anywhere inside `root`, without crossing a nested function or
573
+ * static-block boundary. Mirrors the hoisting semantics used by
574
+ * {@link ./no-sync-result-assumption.ts} so `if (cond) { var sourceCode = ... }`
575
+ * inside a `check()` body correctly shadows the method's first parameter.
576
+ */
577
+ const collectHoistedVarBindings = (root, out) => {
578
+ const walkVar = (node, isRoot) => {
579
+ if (!isRoot && ownsVariableEnvironmentForHoisting(node)) {
580
+ return;
581
+ }
582
+ if (isVarVariableDeclaration(node)) {
583
+ addVariableDeclarationNames(node, out);
584
+ }
585
+ for (const val of Object.values(node)) {
586
+ if (Array.isArray(val)) {
587
+ for (const item of val) {
588
+ if (item && typeof item === 'object' && item.type) {
589
+ walkVar(item, false);
590
+ }
591
+ }
592
+ }
593
+ else if (val && typeof val === 'object' && val.type) {
594
+ walkVar(val, false);
595
+ }
596
+ }
597
+ };
598
+ walkVar(root, true);
599
+ };
600
+ const addFunctionDeclarationName = (stmt, names) => {
601
+ const name = getIdentifierName(stmt.id);
602
+ if (name) {
603
+ names.add(name);
604
+ }
605
+ };
606
+ const collectBlockDeclarationNames = (block) => {
607
+ const names = new Set();
608
+ const body = block.body ?? [];
609
+ for (const stmt of body) {
610
+ // `var` is function-scoped, not block-scoped — hoisted into the nearest
611
+ // enclosing function (or program) scope by
612
+ // {@link collectHoistedVarBindings}. Registering it here would make
613
+ // `if (cond) { var x = ... }` look block-local and fail to shadow a
614
+ // same-named outer binding when the write escapes the block.
615
+ if (stmt.type === 'VariableDeclaration' &&
616
+ !isVarVariableDeclaration(stmt)) {
617
+ addVariableDeclarationNames(stmt, names);
618
+ }
619
+ else if (stmt.type === 'FunctionDeclaration') {
620
+ addFunctionDeclarationName(stmt, names);
621
+ }
622
+ }
623
+ return names;
624
+ };
625
+ /**
626
+ * Collect the names a `CatchClause` parameter introduces into its body. The
627
+ * catch clause has its own binding scope distinct from the surrounding block;
628
+ * without this, `try {} catch (sourceCode) { sourceCode.split(...) }` would
629
+ * resolve `sourceCode` to the enclosing `check()` parameter and fire.
630
+ */
631
+ const collectCatchClauseDeclarationNames = (node) => {
632
+ const names = new Set();
633
+ const { param } = node;
634
+ collectBindingIdsFromPattern(param, names);
635
+ return names;
636
+ };
637
+ const recordDiagnostic = (ctx, site) => {
638
+ ctx.diagnostics.push({
639
+ filePath: ctx.filePath,
640
+ line: offsetToLine(ctx.sourceCode, site.start),
641
+ message: site.message,
642
+ rule: RULE_NAME,
643
+ severity: 'error',
644
+ });
645
+ };
646
+ /**
647
+ * Emit a diagnostic if `node` is a detected site whose candidate identifier
648
+ * resolves (via the current scope stack) to a tracked source-param binding.
649
+ */
650
+ const maybeRecordDetection = (node, scopes, ctx) => {
651
+ const site = detectSite(node);
652
+ if (!site) {
653
+ return;
654
+ }
655
+ const name = getIdentifierName(site.identifier);
656
+ if (name && resolvesToSourceParam(name, scopes)) {
657
+ recordDiagnostic(ctx, site);
658
+ }
659
+ };
660
+ /**
661
+ * Resolve the effective source-param name for a function scope. A hoisted
662
+ * `var` with the same name as the source param overwrites the param's slot
663
+ * in the function's VariableEnvironment, so the enclosing identifier no
664
+ * longer resolves to the raw-source binding.
665
+ */
666
+ const resolveScopeSourceParamName = (methodParamName, hoistedVarNames) => methodParamName && hoistedVarNames.has(methodParamName)
667
+ ? null
668
+ : methodParamName;
669
+ const pushFunctionScope = (node, ctx, scopes) => {
670
+ const methodParamName = ctx.methodFunctionStarts.get(node.start) ?? null;
671
+ const { declaredNames, hoistedVarNames } = collectFunctionScopeBindingsEx(node);
672
+ scopes.push({
673
+ declaredNames,
674
+ sourceParamName: resolveScopeSourceParamName(methodParamName, hoistedVarNames),
675
+ });
676
+ };
677
+ /** Collector for scope frames that carry no source-param identity. */
678
+ const SIMPLE_SCOPE_COLLECTORS = {
679
+ BlockStatement: collectBlockDeclarationNames,
680
+ CatchClause: collectCatchClauseDeclarationNames,
681
+ };
682
+ /**
683
+ * Push the scope a function node introduces, or null when the node is not
684
+ * scope-introducing. Returning a dispose function keeps `visitWithScopes`
685
+ * small and keeps the scope stack strictly paired.
686
+ */
687
+ const enterScopeForNode = (node, ctx, scopes) => {
688
+ if (FUNCTION_NODE_TYPES.has(node.type)) {
689
+ pushFunctionScope(node, ctx, scopes);
690
+ return true;
691
+ }
692
+ const collector = SIMPLE_SCOPE_COLLECTORS[node.type];
693
+ if (collector) {
694
+ scopes.push({
695
+ declaredNames: collector(node),
696
+ sourceParamName: null,
697
+ });
698
+ return true;
699
+ }
700
+ return false;
701
+ };
702
+ /**
703
+ * Build "enter" frames for every AST child of `node`. Returned reversed so
704
+ * consumers can `Array#push(...frames)` onto a stack and still visit
705
+ * children in source order via `Array#pop`.
706
+ */
707
+ const collectChildFrames = (node) => {
708
+ const frames = [];
709
+ for (const value of Object.values(node)) {
710
+ if (Array.isArray(value)) {
711
+ for (const item of value) {
712
+ if (item && typeof item === 'object' && item.type) {
713
+ frames.push({ kind: 'enter', node: item });
714
+ }
715
+ }
716
+ continue;
717
+ }
718
+ if (value && typeof value === 'object' && value.type) {
719
+ frames.push({ kind: 'enter', node: value });
720
+ }
721
+ }
722
+ return frames.toReversed();
723
+ };
724
+ const processFrame = (frame, scopes, ctx, stack) => {
725
+ if (frame.kind === 'leave') {
726
+ if (frame.pushed) {
727
+ scopes.pop();
728
+ }
729
+ return;
730
+ }
731
+ const { node } = frame;
732
+ maybeRecordDetection(node, scopes, ctx);
733
+ const pushed = enterScopeForNode(node, ctx, scopes);
734
+ stack.push({ kind: 'leave', pushed });
735
+ stack.push(...collectChildFrames(node));
736
+ };
737
+ /**
738
+ * Scope-aware AST walker. Iterative DFS: each enter frame schedules the
739
+ * node's children in source order and queues a matching leave frame so
740
+ * scope pops stay balanced with their pushes.
741
+ */
742
+ const analyze = (sourceCode, filePath, ast) => {
743
+ const ctx = {
744
+ diagnostics: [],
745
+ filePath,
746
+ methodFunctionStarts: buildSourceParamIndex(ast),
747
+ sourceCode,
748
+ };
749
+ const scopes = [];
750
+ const stack = [{ kind: 'enter', node: ast }];
751
+ while (stack.length > 0) {
752
+ const frame = stack.pop();
753
+ if (frame) {
754
+ processFrame(frame, scopes, ctx, stack);
755
+ }
756
+ }
757
+ return ctx.diagnostics;
758
+ };
759
+ /**
760
+ * Warden rule enforcing that warden rules themselves walk the AST rather than
761
+ * regex-scan raw source text.
762
+ */
763
+ export const wardenRulesUseAst = {
764
+ check(sourceCode, filePath) {
765
+ if (!isTargetFile(filePath)) {
766
+ return [];
767
+ }
768
+ const ast = parse(filePath, sourceCode);
769
+ if (!ast) {
770
+ return [];
771
+ }
772
+ return analyze(sourceCode, filePath, ast);
773
+ },
774
+ description: 'Enforces that warden rules inspect the AST via packages/warden/src/rules/ast.ts helpers rather than regex-scanning raw source text.',
775
+ name: RULE_NAME,
776
+ severity: 'error',
777
+ };
778
+ //# sourceMappingURL=warden-rules-use-ast.js.map