@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,468 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { resolve, sep } from 'node:path';
3
+ import { describe, expect, test } from 'bun:test';
4
+
5
+ import {
6
+ replaceLastDistSegmentWithSrc,
7
+ wardenRulesUseAst,
8
+ } from '../rules/warden-rules-use-ast.js';
9
+
10
+ const RULES_DIR = resolve(
11
+ Bun.fileURLToPath(new URL('../rules/', import.meta.url))
12
+ );
13
+
14
+ const ruleFilePath = (basename: string): string => resolve(RULES_DIR, basename);
15
+
16
+ const isNonTestTypeScriptFile = (name: string): boolean =>
17
+ name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts');
18
+
19
+ const diagnoseRuleFile = async (
20
+ name: string
21
+ ): Promise<{ readonly file: string; readonly count: number }> => {
22
+ const filePath = ruleFilePath(name);
23
+ const source = await Bun.file(filePath).text();
24
+ return {
25
+ count: wardenRulesUseAst.check(source, filePath).length,
26
+ file: name,
27
+ };
28
+ };
29
+
30
+ const collectBaselineFailures = async (): Promise<
31
+ readonly {
32
+ readonly file: string;
33
+ readonly count: number;
34
+ }[]
35
+ > => {
36
+ const entries = await readdir(RULES_DIR);
37
+ const ruleFiles = entries.filter(isNonTestTypeScriptFile);
38
+ const results = await Promise.all(ruleFiles.map(diagnoseRuleFile));
39
+ return results.filter((r) => r.count > 0);
40
+ };
41
+
42
+ describe('warden-rules-use-ast', () => {
43
+ describe('scope', () => {
44
+ test('ignores files outside packages/warden/src/rules/', () => {
45
+ const foreign = resolve('/tmp/other-pkg/src/foo.ts');
46
+ const diagnostics = wardenRulesUseAst.check(
47
+ `const lines = sourceCode.split('\\n');\n`,
48
+ foreign
49
+ );
50
+ expect(diagnostics).toEqual([]);
51
+ });
52
+
53
+ test('ignores ast.ts itself even if it contains raw-text patterns', () => {
54
+ const diagnostics = wardenRulesUseAst.check(
55
+ `export const parse = (s: string) => s.split('\\n');\nconst x = sourceCode.split('\\n');\n`,
56
+ ruleFilePath('ast.ts')
57
+ );
58
+ expect(diagnostics).toEqual([]);
59
+ });
60
+
61
+ test('ignores support modules (types.ts, index.ts, registry-names.ts)', () => {
62
+ const src = `const lines = sourceCode.split('\\n');\n`;
63
+ expect(wardenRulesUseAst.check(src, ruleFilePath('types.ts'))).toEqual(
64
+ []
65
+ );
66
+ expect(wardenRulesUseAst.check(src, ruleFilePath('index.ts'))).toEqual(
67
+ []
68
+ );
69
+ expect(
70
+ wardenRulesUseAst.check(src, ruleFilePath('registry-names.ts'))
71
+ ).toEqual([]);
72
+ });
73
+
74
+ test('ignores dist-layout support modules (ast.js, index.js, types.js, etc.)', () => {
75
+ // When this rule is bundled to packages/warden/dist/rules/, the support
76
+ // modules ship alongside it as `.js` files. `ast.js` in particular is
77
+ // the raw-text interface to the parser and would false-positive if
78
+ // scanned, so EXCLUDED_BASENAMES must cover both `.ts` and `.js` stems.
79
+ const raw = `export const parse = (s: string) => s.split('\\n');\nconst x = sourceCode.split('\\n');\n`;
80
+ expect(wardenRulesUseAst.check(raw, ruleFilePath('ast.js'))).toEqual([]);
81
+ expect(wardenRulesUseAst.check(raw, ruleFilePath('index.js'))).toEqual(
82
+ []
83
+ );
84
+ expect(wardenRulesUseAst.check(raw, ruleFilePath('types.js'))).toEqual(
85
+ []
86
+ );
87
+ expect(
88
+ wardenRulesUseAst.check(raw, ruleFilePath('registry-names.js'))
89
+ ).toEqual([]);
90
+ expect(wardenRulesUseAst.check(raw, ruleFilePath('scan.js'))).toEqual([]);
91
+ expect(wardenRulesUseAst.check(raw, ruleFilePath('specs.js'))).toEqual(
92
+ []
93
+ );
94
+ expect(
95
+ wardenRulesUseAst.check(raw, ruleFilePath('structure.js'))
96
+ ).toEqual([]);
97
+ });
98
+
99
+ test('ignores test files colocated under the rules directory', () => {
100
+ const diagnostics = wardenRulesUseAst.check(
101
+ `const lines = sourceCode.split('\\n');\n`,
102
+ ruleFilePath('some-rule.test.ts')
103
+ );
104
+ expect(diagnostics).toEqual([]);
105
+ });
106
+ });
107
+
108
+ describe('baseline', () => {
109
+ test('every real rule file in packages/warden/src/rules/ is clean', async () => {
110
+ const failures = await collectBaselineFailures();
111
+ expect(failures).toEqual([]);
112
+ });
113
+ });
114
+
115
+ describe('positive fixtures: string-method scans', () => {
116
+ const targetFile = ruleFilePath('fake-rule.ts');
117
+
118
+ test('flags sourceCode.split("\\n")', () => {
119
+ const source = `export const r = { check(sourceCode: string) { const lines = sourceCode.split('\\n'); return lines; } };\n`;
120
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
121
+ expect(diagnostics.length).toBe(1);
122
+ expect(diagnostics[0]?.rule).toBe('warden-rules-use-ast');
123
+ expect(diagnostics[0]?.severity).toBe('error');
124
+ expect(diagnostics[0]?.message).toContain('split');
125
+ });
126
+
127
+ test('flags sourceCode.matchAll(/.../ g)', () => {
128
+ const source = `export const r = { check(sourceCode: string) { for (const m of sourceCode.matchAll(/foo/g)) { void m; } return []; } };\n`;
129
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
130
+ expect(diagnostics.length).toBe(1);
131
+ expect(diagnostics[0]?.message).toContain('matchAll');
132
+ });
133
+
134
+ test('flags text.split(/\\n/)', () => {
135
+ const source = `export const r = { check(text: string) { const lines = text.split(/\\n/); return lines; } };\n`;
136
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
137
+ expect(diagnostics.length).toBe(1);
138
+ expect(diagnostics[0]?.message).toContain('split');
139
+ });
140
+
141
+ test('flags sourceCode.match(/.../)', () => {
142
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.match(/foo/) ?? []; } };\n`;
143
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
144
+ expect(diagnostics.length).toBe(1);
145
+ expect(diagnostics[0]?.message).toContain('match');
146
+ });
147
+
148
+ test('flags rawText.matchAll(/.../ g)', () => {
149
+ const source = `export const r = { check(rawText: string) { return [...rawText.matchAll(/foo/g)]; } };\n`;
150
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
151
+ expect(diagnostics.length).toBe(1);
152
+ expect(diagnostics[0]?.message).toContain('matchAll');
153
+ });
154
+
155
+ test('sourceCode.split(/regex/) fires once, not twice', () => {
156
+ // sourceCode.split(/.../) is covered by the receiver.method path;
157
+ // confirm the regex-arg path does not double-fire on the same site.
158
+ const source = `export const r = { check(sourceCode: string) { const parts = sourceCode.split(/foo/); return parts; } };\n`;
159
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
160
+ expect(diagnostics.length).toBe(1);
161
+ expect(diagnostics[0]?.message).toContain('split');
162
+ });
163
+
164
+ // oxc-parser can emit either `MemberExpression` or `StaticMemberExpression`
165
+ // for non-computed member access depending on context. The rule must flow
166
+ // through both shapes.
167
+ test('flags sourceCode.split regardless of MemberExpression vs StaticMemberExpression shape', () => {
168
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.split('\\n'); } };\n`;
169
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
170
+ expect(diagnostics.length).toBe(1);
171
+ });
172
+
173
+ test('flags /regex/.test(sourceCode) regardless of MemberExpression vs StaticMemberExpression shape', () => {
174
+ const source = `export const r = { check(sourceCode: string) { return /foo/.test(sourceCode) ? [] : []; } };\n`;
175
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
176
+ expect(diagnostics.length).toBe(1);
177
+ });
178
+ });
179
+
180
+ describe('positive fixtures: replace/search scans', () => {
181
+ const targetFile = ruleFilePath('fake-rule.ts');
182
+
183
+ test('flags sourceCode.replace(/.../, ...)', () => {
184
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.replace(/foo/, 'bar'); } };\n`;
185
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
186
+ expect(diagnostics.length).toBe(1);
187
+ expect(diagnostics[0]?.message).toContain('replace');
188
+ });
189
+
190
+ test('flags sourceCode.replaceAll(/.../, ...)', () => {
191
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.replaceAll(/foo/g, 'bar'); } };\n`;
192
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
193
+ expect(diagnostics.length).toBe(1);
194
+ expect(diagnostics[0]?.message).toContain('replaceAll');
195
+ });
196
+
197
+ test('flags sourceCode.search(/.../)', () => {
198
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.search(/foo/); } };\n`;
199
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
200
+ expect(diagnostics.length).toBe(1);
201
+ expect(diagnostics[0]?.message).toContain('search');
202
+ });
203
+
204
+ test('flags sourceCode.replace("literal", "other") — consistent with split', () => {
205
+ // Mirrors split(string) behavior: the method name alone is enough to
206
+ // flag, because any replace-on-source-text indicates raw scanning.
207
+ const source = `export const r = { check(sourceCode: string) { return sourceCode.replace('foo', 'bar'); } };\n`;
208
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
209
+ expect(diagnostics.length).toBe(1);
210
+ expect(diagnostics[0]?.message).toContain('replace');
211
+ });
212
+ });
213
+
214
+ describe('positive fixtures: regex-method scans', () => {
215
+ const targetFile = ruleFilePath('fake-rule.ts');
216
+
217
+ test('flags /regex/.test(sourceCode)', () => {
218
+ const source = `export const r = { check(sourceCode: string) { const has = /\\btrail\\s*\\(/.test(sourceCode); return has ? [] : []; } };\n`;
219
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
220
+ expect(diagnostics.length).toBe(1);
221
+ expect(diagnostics[0]?.message).toContain('test');
222
+ expect(diagnostics[0]?.rule).toBe('warden-rules-use-ast');
223
+ });
224
+
225
+ test('flags /regex/.exec(sourceCode)', () => {
226
+ const source = `export const r = { check(sourceCode: string) { const m = /foo/.exec(sourceCode); return m ? [] : []; } };\n`;
227
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
228
+ expect(diagnostics.length).toBe(1);
229
+ expect(diagnostics[0]?.message).toContain('exec');
230
+ });
231
+
232
+ test('flags new RegExp(...).test(sourceCode)', () => {
233
+ const source = `export const r = { check(sourceCode: string) { const has = new RegExp('foo').test(sourceCode); return has ? [] : []; } };\n`;
234
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
235
+ expect(diagnostics.length).toBe(1);
236
+ expect(diagnostics[0]?.message).toContain('test');
237
+ });
238
+
239
+ test('flags /regex/.test(text)', () => {
240
+ const source = `export const r = { check(text: string) { return /foo/.test(text) ? [] : []; } };\n`;
241
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
242
+ expect(diagnostics.length).toBe(1);
243
+ expect(diagnostics[0]?.message).toContain('test');
244
+ });
245
+ });
246
+
247
+ describe('positive fixtures: regex construction from raw source', () => {
248
+ const targetFile = ruleFilePath('fake-rule.ts');
249
+
250
+ test('flags new RegExp(sourceCode)', () => {
251
+ const source = `export const r = { check(sourceCode: string) { const pattern = new RegExp(sourceCode); return pattern ? [] : []; } };\n`;
252
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
253
+ expect(diagnostics.length).toBe(1);
254
+ expect(diagnostics[0]?.rule).toBe('warden-rules-use-ast');
255
+ expect(diagnostics[0]?.severity).toBe('error');
256
+ expect(diagnostics[0]?.message).toContain('new RegExp(sourceCode)');
257
+ expect(diagnostics[0]?.message).toContain('constructs a regex');
258
+ });
259
+
260
+ test('flags new RegExp(rawText, "g")', () => {
261
+ const source = `export const r = { check(rawText: string) { const pattern = new RegExp(rawText, 'g'); return pattern ? [] : []; } };\n`;
262
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
263
+ expect(diagnostics.length).toBe(1);
264
+ expect(diagnostics[0]?.message).toContain('new RegExp(rawText)');
265
+ });
266
+
267
+ test('flags RegExp(sourceCode) without new', () => {
268
+ const source = `export const r = { check(sourceCode: string) { const pattern = RegExp(sourceCode); return pattern ? [] : []; } };\n`;
269
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
270
+ expect(diagnostics.length).toBe(1);
271
+ expect(diagnostics[0]?.message).toContain('RegExp(sourceCode)');
272
+ expect(diagnostics[0]?.message).not.toContain('new RegExp');
273
+ });
274
+
275
+ test('does not flag new RegExp("literal") with string literal arg', () => {
276
+ const source = `export const r = { check() { const pattern = new RegExp('foo'); return pattern ? [] : []; } };\n`;
277
+ expect(wardenRulesUseAst.check(source, targetFile)).toEqual([]);
278
+ });
279
+
280
+ test('flags new RegExp(userInput) when userInput is the first param of check (custom name)', () => {
281
+ // Under parameter-origin tracking (TRL-346 / Option A), the *binding
282
+ // identity* of the first `check` parameter is what matters — not its
283
+ // spelling. A rule author who renames the source parameter cannot
284
+ // silently opt out of the diagnostic by picking an unusual name.
285
+ const source = `export const r = { check(userInput: string) { const pattern = new RegExp(userInput); return pattern ? [] : []; } };\n`;
286
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
287
+ expect(diagnostics.length).toBe(1);
288
+ expect(diagnostics[0]?.message).toContain('new RegExp(userInput)');
289
+ });
290
+ });
291
+
292
+ describe('parameter-origin tracking (TRL-346 / Option A)', () => {
293
+ // The pre-TRL-346 detectors gated on identifier spelling alone (a fixed
294
+ // set of raw-source names). That over-fired on unrelated locals whose
295
+ // names happened to appear in the list, and under-fired when a rule
296
+ // author picked a different name for the source parameter. These tests
297
+ // exercise the scope-aware replacement.
298
+ const targetFile = ruleFilePath('fake-rule.ts');
299
+
300
+ test('does not flag inner const that shadows the check parameter', () => {
301
+ // The inner `const sourceCode` is a local, not the rule's source
302
+ // parameter — flagging would be a false positive.
303
+ const source = `export const r = { check(sourceCode: string, filePath: string) { const inner: string = filePath; { const sourceCode = inner; const pattern = new RegExp(sourceCode); return pattern ? [] : []; } } };\n`;
304
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
305
+ expect(diagnostics).toEqual([]);
306
+ });
307
+
308
+ test('scope: does not flag a local named `text` unrelated to the source param', () => {
309
+ // Pre-Option A, the old RAW_SOURCE_IDENTIFIERS heuristic would fire
310
+ // here because `text` is in the set. Under parameter-origin tracking
311
+ // the local `text` shadows nothing and does not resolve to the rule's
312
+ // first parameter, so no diagnostic.
313
+ const source = `export const r = { check(src: string, filePath: string) { const text: string = filePath; const pattern = new RegExp(text); return pattern ? [] : []; } };\n`;
314
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
315
+ expect(diagnostics).toEqual([]);
316
+ });
317
+
318
+ test('scope: flags new RegExp(src) when src is the first param of check (custom name)', () => {
319
+ // The param binding, not the spelling, drives the diagnostic.
320
+ const source = `export const r = { check(src: string) { const pattern = new RegExp(src); return pattern ? [] : []; } };\n`;
321
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
322
+ expect(diagnostics.length).toBe(1);
323
+ expect(diagnostics[0]?.message).toContain('new RegExp(src)');
324
+ });
325
+
326
+ test('scope: flags sourceCode.split when sourceCode is a checkWithContext param', () => {
327
+ // `checkWithContext` is the project-aware sibling of `check`; its
328
+ // first parameter is also raw source text and must be tracked.
329
+ const source = `export const r = { checkWithContext(sourceCode: string, filePath: string, ctx: unknown) { return sourceCode.split('\\n'); } };\n`;
330
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
331
+ expect(diagnostics.length).toBe(1);
332
+ expect(diagnostics[0]?.message).toContain('split');
333
+ });
334
+
335
+ test('scope: does not flag sourceCode.split inside checkTopo (no raw source param)', () => {
336
+ // `checkTopo(topo)` does not receive raw source text. A local named
337
+ // `sourceCode` inside it is just a domain string and must not fire
338
+ // — this was the core false-positive scenario TRL-346 fixes.
339
+ const source = `export const r = { checkTopo(topo: unknown) { const sourceCode = 'arbitrary-data'; return sourceCode.split(','); } };\n`;
340
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
341
+ expect(diagnostics).toEqual([]);
342
+ });
343
+
344
+ test('scope: does not flag source-like identifiers at module scope', () => {
345
+ // Free identifiers with no enclosing `check` method cannot resolve to
346
+ // a tracked source-param binding, so detection stays silent.
347
+ const source = `const sourceCode = 'abc'; const pattern = new RegExp(sourceCode);\n`;
348
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
349
+ expect(diagnostics).toEqual([]);
350
+ });
351
+
352
+ test('scope: catch-clause param shadowing does not fire', () => {
353
+ // The `catch (sourceCode)` binding is a different scope from the
354
+ // enclosing `check` parameter. The `.split()` inside the catch block
355
+ // reads the catch param, not the rule's source-text parameter.
356
+ const source =
357
+ "export const r = { check(sourceCode: string, filePath: string) { try { return []; } catch (sourceCode) { return sourceCode.split('\\n'); } } };\n";
358
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
359
+ expect(diagnostics).toEqual([]);
360
+ });
361
+
362
+ test('scope: hoisted var inside a block shadows the check param', () => {
363
+ // `var sourceCode` inside `if (cond) { ... }` hoists to the function
364
+ // body and shadows the parameter. The `.split(...)` after the block
365
+ // reads the hoisted var, not the param, so no diagnostic.
366
+ const source =
367
+ "export const r = { check(sourceCode: string, filePath: string) { if (filePath) { var sourceCode = 'local'; } return sourceCode.split('\\n'); } };\n";
368
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
369
+ expect(diagnostics).toEqual([]);
370
+ });
371
+ });
372
+
373
+ describe('idiomatic patterns are not flagged', () => {
374
+ const targetFile = ruleFilePath('fake-rule.ts');
375
+
376
+ test('sourceCode.slice(node.start, node.end) is allowed (AST-guided)', () => {
377
+ const source = `export const r = { check(sourceCode: string) { const t = sourceCode.slice(0, 10); return t; } };\n`;
378
+ expect(wardenRulesUseAst.check(source, targetFile)).toEqual([]);
379
+ });
380
+
381
+ test('sourceCode.includes("marker") is allowed (fast-bail)', () => {
382
+ const source = `export const r = { check(sourceCode: string) { if (!sourceCode.includes('marker')) return []; return []; } };\n`;
383
+ expect(wardenRulesUseAst.check(source, targetFile)).toEqual([]);
384
+ });
385
+
386
+ test('sourceCode.indexOf is allowed', () => {
387
+ const source = `export const r = { check(sourceCode: string) { const i = sourceCode.indexOf('x'); return i; } };\n`;
388
+ expect(wardenRulesUseAst.check(source, targetFile)).toEqual([]);
389
+ });
390
+ });
391
+
392
+ describe('negative fixtures', () => {
393
+ const targetFile = ruleFilePath('fake-rule.ts');
394
+
395
+ test('does not flag AST helpers (findStringLiterals, walk)', () => {
396
+ const source = `import { findStringLiterals, parse, walk } from './ast.js';
397
+ export const r = {
398
+ check(sourceCode: string, filePath: string) {
399
+ const ast = parse(filePath, sourceCode);
400
+ if (!ast) return [];
401
+ const hits = findStringLiterals(ast, /foo/);
402
+ walk(ast, (node) => { void node; });
403
+ return hits.map((h) => ({ line: 1, rule: 'x', message: '', severity: 'error', filePath })) ;
404
+ }
405
+ };
406
+ `;
407
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
408
+ expect(diagnostics).toEqual([]);
409
+ });
410
+
411
+ test('does not flag findTrailDefinitions walker usage', () => {
412
+ const source = `import { findTrailDefinitions } from './ast.js';
413
+ export const r = { check(sourceCode: string, filePath: string) { const defs = findTrailDefinitions(sourceCode as unknown as never); return defs.length ? [] : []; } };
414
+ `;
415
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
416
+ expect(diagnostics).toEqual([]);
417
+ });
418
+
419
+ test('does not flag string methods on unrelated identifiers', () => {
420
+ const source = `export const r = { check() { const arr = ['a', 'b']; const s = arr.join(',').split(','); const path = 'foo.ts'; path.includes('foo'); return s; } };\n`;
421
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
422
+ expect(diagnostics).toEqual([]);
423
+ });
424
+
425
+ test('does not flag /regex/.test on non-raw-text identifier', () => {
426
+ const source = `export const r = { check() { const name = 'foo'; return /bar/.test(name) ? [] : []; } };\n`;
427
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
428
+ expect(diagnostics).toEqual([]);
429
+ });
430
+
431
+ test('does not flag computed member access', () => {
432
+ // Defensive: computed member access is too ambiguous to flag reliably.
433
+ const source = `export const r = { check(sourceCode: string) { const m = 'split'; return (sourceCode as unknown as { [k: string]: Function })[m]('\\n'); } };\n`;
434
+ const diagnostics = wardenRulesUseAst.check(source, targetFile);
435
+ expect(diagnostics).toEqual([]);
436
+ });
437
+ });
438
+
439
+ describe('replaceLastDistSegmentWithSrc', () => {
440
+ test('replaces a single /dist/ segment', () => {
441
+ const input = `${sep}home${sep}user${sep}pkg${sep}dist${sep}rules`;
442
+ const expected = `${sep}home${sep}user${sep}pkg${sep}src${sep}rules`;
443
+ expect(replaceLastDistSegmentWithSrc(input)).toBe(expected);
444
+ });
445
+
446
+ test('replaces only the LAST /dist/ segment when multiple are present', () => {
447
+ // Simulates a CI path like /home/runner/dist-artifacts/... except with
448
+ // a real /dist/ higher up the tree. A blanket replaceAll would mangle
449
+ // both segments and yield a nonexistent directory.
450
+ const input = `${sep}srv${sep}dist${sep}warden${sep}dist${sep}rules`;
451
+ const expected = `${sep}srv${sep}dist${sep}warden${sep}src${sep}rules`;
452
+ expect(replaceLastDistSegmentWithSrc(input)).toBe(expected);
453
+ });
454
+
455
+ test('returns the path unchanged when no /dist/ segment is present', () => {
456
+ const input = `${sep}home${sep}user${sep}pkg${sep}src${sep}rules`;
457
+ expect(replaceLastDistSegmentWithSrc(input)).toBe(input);
458
+ });
459
+
460
+ test('does not substitute inside /dist-artifacts/ lookalikes', () => {
461
+ // The delimiter requires /dist/ surrounded by separators, so
462
+ // `dist-artifacts` must be preserved verbatim.
463
+ const input = `${sep}runner${sep}dist-artifacts${sep}pkg${sep}dist${sep}rules`;
464
+ const expected = `${sep}runner${sep}dist-artifacts${sep}pkg${sep}src${sep}rules`;
465
+ expect(replaceLastDistSegmentWithSrc(input)).toBe(expected);
466
+ });
467
+ });
468
+ });
@@ -6,10 +6,10 @@ import { wrapRule } from '../trails/wrap-rule.js';
6
6
  import type { ProjectAwareWardenRule } from '../rules/types.js';
7
7
 
8
8
  describe('wrapRule', () => {
9
- test('preserves undefined knownProvisionIds and defaults knownTrailIds to empty set', async () => {
9
+ test('omits absent resource ids and defaults knownTrailIds to empty set', async () => {
10
10
  let capturedContext:
11
11
  | {
12
- readonly knownProvisionIds?: ReadonlySet<string>;
12
+ readonly knownResourceIds?: ReadonlySet<string>;
13
13
  readonly knownTrailIds?: ReadonlySet<string>;
14
14
  }
15
15
  | undefined;
@@ -34,8 +34,8 @@ describe('wrapRule', () => {
34
34
  expect(result.isOk()).toBe(true);
35
35
  expect(result.unwrap()).toEqual({ diagnostics: [] });
36
36
  expect(capturedContext).toEqual({
37
- knownProvisionIds: undefined,
38
37
  knownTrailIds: new Set<string>(),
39
38
  });
39
+ expect(capturedContext?.knownResourceIds).toBeUndefined();
40
40
  });
41
41
  });