@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,160 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
3
+ import { isDraftMarkedFile } from '../draft.js';
4
+ import {
5
+ collectFrameworkDraftPrefixConstantOffsets,
6
+ findStringLiterals,
7
+ hasIgnoreCommentOnLine,
8
+ offsetToLine,
9
+ parse,
10
+ splitSourceLines,
11
+ } from './ast.js';
12
+ import type { StringLiteralMatch } from './ast.js';
13
+ import type { WardenDiagnostic, WardenRule } from './types.js';
14
+
15
+ const messageForMissingMarker = (draftId: string): string =>
16
+ `Draft id "${draftId}" appears in source, but the file is not draft-marked. ` +
17
+ 'Rename it with an _draft. prefix or a .draft. trailing segment.';
18
+
19
+ const makeDiagnostic = (
20
+ sourceCode: string,
21
+ filePath: string,
22
+ start: number,
23
+ message: string,
24
+ severity: WardenDiagnostic['severity']
25
+ ): WardenDiagnostic => ({
26
+ filePath,
27
+ line: offsetToLine(sourceCode, start),
28
+ message,
29
+ rule: 'draft-file-marking',
30
+ severity,
31
+ });
32
+
33
+ const collectDraftMatches = (
34
+ sourceCode: string,
35
+ filePath: string,
36
+ ast: NonNullable<ReturnType<typeof parse>>
37
+ ): StringLiteralMatch[] => {
38
+ const frameworkConstantOffsets = collectFrameworkDraftPrefixConstantOffsets(
39
+ ast,
40
+ filePath
41
+ );
42
+ const lines = splitSourceLines(sourceCode);
43
+ return findStringLiterals(ast, (value) => isDraftId(value)).filter(
44
+ (match) => {
45
+ if (frameworkConstantOffsets.has(match.start)) {
46
+ return false;
47
+ }
48
+ if (
49
+ hasIgnoreCommentOnLine(lines, offsetToLine(sourceCode, match.start))
50
+ ) {
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ );
56
+ };
57
+
58
+ const draftMissingMarkerDiagnostic = (
59
+ sourceCode: string,
60
+ filePath: string,
61
+ ast: NonNullable<ReturnType<typeof parse>>
62
+ ): WardenDiagnostic | null => {
63
+ const draftMatches = collectDraftMatches(sourceCode, filePath, ast);
64
+ if (!draftMatches.length || isDraftMarkedFile(filePath)) {
65
+ return null;
66
+ }
67
+
68
+ const [first] = draftMatches;
69
+ if (!first) {
70
+ return null;
71
+ }
72
+
73
+ return makeDiagnostic(
74
+ sourceCode,
75
+ filePath,
76
+ first.start,
77
+ messageForMissingMarker(first.value),
78
+ 'error'
79
+ );
80
+ };
81
+
82
+ const draftMarkedWithoutIdsDiagnostic = (
83
+ filePath: string,
84
+ ast: NonNullable<ReturnType<typeof parse>>
85
+ ): WardenDiagnostic | null => {
86
+ // Deciding whether the file's `_draft.` marker is still warranted is a
87
+ // question about *all* draft ids present in source, not just the unsuppressed
88
+ // ones. Pragma-suppressed ids still justify a draft-marked filename — a user
89
+ // intentionally silencing them has not removed the draft content. We
90
+ // therefore filter only the framework-constant declarations (which are not
91
+ // draft ids at all) and bypass the pragma filter that `collectDraftMatches`
92
+ // applies.
93
+ const frameworkConstantOffsets = collectFrameworkDraftPrefixConstantOffsets(
94
+ ast,
95
+ filePath
96
+ );
97
+ const unsuppressedDraftIds = findStringLiterals(ast, (value) =>
98
+ isDraftId(value)
99
+ ).filter((match) => !frameworkConstantOffsets.has(match.start));
100
+
101
+ if (unsuppressedDraftIds.length > 0) {
102
+ return null;
103
+ }
104
+
105
+ if (!isDraftMarkedFile(filePath)) {
106
+ return null;
107
+ }
108
+
109
+ return {
110
+ filePath,
111
+ line: 1,
112
+ message:
113
+ 'File is draft-marked but no longer contains draft ids. Remove the draft filename marker or finish the promotion cleanup.',
114
+ rule: 'draft-file-marking',
115
+ severity: 'warn',
116
+ };
117
+ };
118
+
119
+ const collectDraftFileMarkingDiagnostics = (
120
+ sourceCode: string,
121
+ filePath: string,
122
+ ast: NonNullable<ReturnType<typeof parse>>
123
+ ): WardenDiagnostic[] => {
124
+ const missingMarkerDiagnostic = draftMissingMarkerDiagnostic(
125
+ sourceCode,
126
+ filePath,
127
+ ast
128
+ );
129
+ if (missingMarkerDiagnostic) {
130
+ return [missingMarkerDiagnostic];
131
+ }
132
+
133
+ const markedWithoutIdsDiagnostic = draftMarkedWithoutIdsDiagnostic(
134
+ filePath,
135
+ ast
136
+ );
137
+ if (markedWithoutIdsDiagnostic) {
138
+ return [markedWithoutIdsDiagnostic];
139
+ }
140
+
141
+ return [];
142
+ };
143
+
144
+ /**
145
+ * Ensures files containing draft ids are visibly marked as draft-bearing files.
146
+ */
147
+ export const draftFileMarking: WardenRule = {
148
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
149
+ const ast = parse(filePath, sourceCode);
150
+ if (!ast) {
151
+ return [];
152
+ }
153
+
154
+ return collectDraftFileMarkingDiagnostics(sourceCode, filePath, ast);
155
+ },
156
+ description:
157
+ 'Require draft-bearing files to use _draft.* or *.draft.* filename markers.',
158
+ name: 'draft-file-marking',
159
+ severity: 'error',
160
+ };
@@ -0,0 +1,87 @@
1
+ import { isDraftId } from '@ontrails/core';
2
+
3
+ import {
4
+ collectFrameworkDraftPrefixConstantOffsets,
5
+ findStringLiterals,
6
+ hasIgnoreCommentOnLine,
7
+ offsetToLine,
8
+ parse,
9
+ splitSourceLines,
10
+ } from './ast.js';
11
+ import type { WardenDiagnostic, WardenRule } from './types.js';
12
+
13
+ const createDiagnostic = (
14
+ sourceCode: string,
15
+ filePath: string,
16
+ match: { start: number; value: string }
17
+ ): WardenDiagnostic => ({
18
+ filePath,
19
+ line: offsetToLine(sourceCode, match.start),
20
+ message:
21
+ `Draft id "${match.value}" is still visible debt. ` +
22
+ 'Established trailheads, lock export, and OpenAPI generation will reject it until it is promoted.',
23
+ rule: 'draft-visible-debt',
24
+ severity: 'warn',
25
+ });
26
+
27
+ const isSuppressedMatch = (
28
+ match: { start: number },
29
+ sourceCode: string,
30
+ lines: readonly string[],
31
+ frameworkConstantOffsets: ReadonlySet<number>
32
+ ): boolean =>
33
+ frameworkConstantOffsets.has(match.start) ||
34
+ hasIgnoreCommentOnLine(lines, offsetToLine(sourceCode, match.start));
35
+
36
+ const collectDraftVisibleDebtDiagnostics = (
37
+ sourceCode: string,
38
+ filePath: string,
39
+ ast: NonNullable<ReturnType<typeof parse>>
40
+ ): WardenDiagnostic[] => {
41
+ const frameworkConstantOffsets = collectFrameworkDraftPrefixConstantOffsets(
42
+ ast,
43
+ filePath
44
+ );
45
+ const lines = splitSourceLines(sourceCode);
46
+ const seen = new Set<string>();
47
+
48
+ return findStringLiterals(ast, (value) => isDraftId(value)).flatMap(
49
+ (match) => {
50
+ if (
51
+ isSuppressedMatch(match, sourceCode, lines, frameworkConstantOffsets)
52
+ ) {
53
+ return [];
54
+ }
55
+ const key = `${match.value}:${String(match.start)}`;
56
+ if (seen.has(key)) {
57
+ return [];
58
+ }
59
+ seen.add(key);
60
+ return [createDiagnostic(sourceCode, filePath, match)];
61
+ }
62
+ );
63
+ };
64
+
65
+ /**
66
+ * Warns when draft ids are still present so the debt stays visible during
67
+ * review even when the file is correctly marked.
68
+ *
69
+ * Severity is intentionally `warn`, not `error`. The hard rejection layer for
70
+ * draft state leaking into established outputs is `validateEstablishedTopo` at
71
+ * runtime — it blocks topo export, trailhead projection, and lockfile writes.
72
+ * This rule surfaces the debt for human reviewers without duplicating that layer.
73
+ */
74
+ export const draftVisibleDebt: WardenRule = {
75
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
76
+ const ast = parse(filePath, sourceCode);
77
+ if (!ast) {
78
+ return [];
79
+ }
80
+
81
+ return collectDraftVisibleDebtDiagnostics(sourceCode, filePath, ast);
82
+ },
83
+ description:
84
+ 'Warn when draft ids remain in source so the debt stays visible during review.',
85
+ name: 'draft-visible-debt',
86
+ severity: 'warn',
87
+ };
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Validates that registered transport error mappers cover every error category.
3
+ *
4
+ * Scans `createTransportErrorMapper(...)` calls and resolves simple object
5
+ * literals, identifier bindings, and object-property references in the same
6
+ * file so incomplete mapper registrations are caught before they ship.
7
+ */
8
+
9
+ import { errorCategories } from '@ontrails/core';
10
+
11
+ import {
12
+ getStringValue,
13
+ identifierName,
14
+ isStringLiteral,
15
+ offsetToLine,
16
+ parse,
17
+ walk,
18
+ } from './ast.js';
19
+ import type { AstNode } from './ast.js';
20
+ import { isTestFile } from './scan.js';
21
+ import type { WardenDiagnostic, WardenRule } from './types.js';
22
+
23
+ const MEMBER_EXPRESSION_TYPES = new Set([
24
+ 'MemberExpression',
25
+ 'StaticMemberExpression',
26
+ ]);
27
+
28
+ const getPropertyName = (node: AstNode | undefined): string | null => {
29
+ if (!node) {
30
+ return null;
31
+ }
32
+
33
+ return (
34
+ identifierName(node) ??
35
+ (isStringLiteral(node) ? getStringValue(node) : null)
36
+ );
37
+ };
38
+
39
+ const collectObjectBindings = (ast: AstNode): ReadonlyMap<string, AstNode> => {
40
+ const bindings = new Map<string, AstNode>();
41
+
42
+ walk(ast, (node) => {
43
+ if (node.type !== 'VariableDeclarator') {
44
+ return;
45
+ }
46
+
47
+ const { id, init } = node as { id?: AstNode; init?: AstNode };
48
+ const bindingName = identifierName(id);
49
+
50
+ if (bindingName && init?.type === 'ObjectExpression') {
51
+ bindings.set(bindingName, init);
52
+ }
53
+ });
54
+
55
+ return bindings;
56
+ };
57
+
58
+ const getObjectProperties = (objectNode: AstNode): readonly AstNode[] =>
59
+ objectNode.type === 'ObjectExpression'
60
+ ? ((objectNode['properties'] as readonly AstNode[] | undefined) ?? [])
61
+ : [];
62
+
63
+ const findObjectPropertyValue = (
64
+ objectNode: AstNode,
65
+ propertyName: string
66
+ ): AstNode | null => {
67
+ for (const property of getObjectProperties(objectNode)) {
68
+ if (property.type !== 'Property') {
69
+ continue;
70
+ }
71
+
72
+ const key = getPropertyName((property as unknown as { key?: AstNode }).key);
73
+ if (key === propertyName) {
74
+ return (property as unknown as { value?: AstNode }).value ?? null;
75
+ }
76
+ }
77
+
78
+ return null;
79
+ };
80
+
81
+ const resolveIdentifierObject = (
82
+ node: AstNode,
83
+ bindings: ReadonlyMap<string, AstNode>
84
+ ): AstNode | null =>
85
+ bindings.get((node as { name?: string }).name ?? '') ?? null;
86
+
87
+ const resolveMemberObject = (
88
+ node: AstNode,
89
+ bindings: ReadonlyMap<string, AstNode>,
90
+ depth: number,
91
+ resolve: (
92
+ node: AstNode | undefined,
93
+ bindings: ReadonlyMap<string, AstNode>,
94
+ depth?: number
95
+ ) => AstNode | null
96
+ ): AstNode | null => {
97
+ const { object, property } = node as { object?: AstNode; property?: AstNode };
98
+ const propertyName = getPropertyName(property);
99
+ if (!propertyName) {
100
+ return null;
101
+ }
102
+
103
+ const objectNode = resolve(object, bindings, depth + 1);
104
+ return objectNode
105
+ ? resolve(
106
+ findObjectPropertyValue(objectNode, propertyName) ?? undefined,
107
+ bindings,
108
+ depth + 1
109
+ )
110
+ : null;
111
+ };
112
+
113
+ const resolveObjectExpression = function resolveObjectExpression(
114
+ node: AstNode | undefined,
115
+ bindings: ReadonlyMap<string, AstNode>,
116
+ depth = 0
117
+ ): AstNode | null {
118
+ if (!node || depth > 4) {
119
+ return null;
120
+ }
121
+
122
+ if (node.type === 'ObjectExpression') {
123
+ return node;
124
+ }
125
+
126
+ if (node.type === 'Identifier') {
127
+ return resolveIdentifierObject(node, bindings);
128
+ }
129
+
130
+ return MEMBER_EXPRESSION_TYPES.has(node.type)
131
+ ? resolveMemberObject(node, bindings, depth, resolveObjectExpression)
132
+ : null;
133
+ };
134
+
135
+ const addMappedCategory = (
136
+ categories: Set<string>,
137
+ property: AstNode
138
+ ): boolean => {
139
+ if (property.type === 'SpreadElement') {
140
+ return false;
141
+ }
142
+
143
+ if (property.type !== 'Property') {
144
+ return true;
145
+ }
146
+
147
+ const key = getPropertyName((property as unknown as { key?: AstNode }).key);
148
+ if (key) {
149
+ categories.add(key);
150
+ }
151
+
152
+ return true;
153
+ };
154
+
155
+ const collectMappedCategories = (
156
+ mapperObject: AstNode
157
+ ): ReadonlySet<string> | null => {
158
+ if (mapperObject.type !== 'ObjectExpression') {
159
+ return null;
160
+ }
161
+
162
+ const categories = new Set<string>();
163
+ for (const property of getObjectProperties(mapperObject)) {
164
+ if (!addMappedCategory(categories, property)) {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ return categories;
170
+ };
171
+
172
+ const createDiagnostic = (
173
+ filePath: string,
174
+ line: number,
175
+ missingCategories: readonly string[]
176
+ ): WardenDiagnostic => ({
177
+ filePath,
178
+ line,
179
+ message: `Transport error mapper is missing mappings for: ${missingCategories.join(', ')}. Registered createTransportErrorMapper() calls must cover every ErrorCategory.`,
180
+ rule: 'error-mapping-completeness',
181
+ severity: 'error',
182
+ });
183
+
184
+ const getCallArgs = (node: AstNode): readonly AstNode[] =>
185
+ (node as { arguments?: readonly AstNode[] }).arguments ?? [];
186
+
187
+ const getCallCallee = (node: AstNode): AstNode | undefined =>
188
+ (node as { callee?: AstNode }).callee;
189
+
190
+ const isMapperFactoryCall = (node: AstNode): boolean =>
191
+ node.type === 'CallExpression' &&
192
+ identifierName(getCallCallee(node)) === 'createTransportErrorMapper';
193
+
194
+ const findMissingCategories = (
195
+ mappedCategories: ReadonlySet<string>
196
+ ): readonly string[] =>
197
+ errorCategories.filter((category) => !mappedCategories.has(category));
198
+
199
+ const resolveMappedCategories = (
200
+ node: AstNode,
201
+ bindings: ReadonlyMap<string, AstNode>
202
+ ): ReadonlySet<string> | null => {
203
+ const [firstArg] = getCallArgs(node);
204
+ const mapperObject = resolveObjectExpression(firstArg, bindings);
205
+ return mapperObject ? collectMappedCategories(mapperObject) : null;
206
+ };
207
+
208
+ const inspectMapperCall = (
209
+ node: AstNode,
210
+ bindings: ReadonlyMap<string, AstNode>,
211
+ filePath: string,
212
+ sourceCode: string
213
+ ): WardenDiagnostic | null => {
214
+ if (!isMapperFactoryCall(node)) {
215
+ return null;
216
+ }
217
+
218
+ const mappedCategories = resolveMappedCategories(node, bindings);
219
+ if (!mappedCategories) {
220
+ return null;
221
+ }
222
+
223
+ const missingCategories = findMissingCategories(mappedCategories);
224
+ if (missingCategories.length === 0) {
225
+ return null;
226
+ }
227
+
228
+ return createDiagnostic(
229
+ filePath,
230
+ offsetToLine(sourceCode, node.start),
231
+ missingCategories
232
+ );
233
+ };
234
+
235
+ /**
236
+ * Flags `createTransportErrorMapper()` registrations that omit error categories.
237
+ */
238
+ export const errorMappingCompleteness: WardenRule = {
239
+ check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
240
+ if (
241
+ isTestFile(filePath) ||
242
+ !sourceCode.includes('createTransportErrorMapper')
243
+ ) {
244
+ return [];
245
+ }
246
+
247
+ const ast = parse(filePath, sourceCode);
248
+ if (!ast) {
249
+ return [];
250
+ }
251
+
252
+ const bindings = collectObjectBindings(ast);
253
+ const diagnostics: WardenDiagnostic[] = [];
254
+
255
+ walk(ast, (node) => {
256
+ const diagnostic = inspectMapperCall(
257
+ node,
258
+ bindings,
259
+ filePath,
260
+ sourceCode
261
+ );
262
+ if (diagnostic) {
263
+ diagnostics.push(diagnostic);
264
+ }
265
+ });
266
+
267
+ return diagnostics;
268
+ },
269
+ description:
270
+ 'Require registered transport error mappers to cover every ErrorCategory.',
271
+ name: 'error-mapping-completeness',
272
+ severity: 'error',
273
+ };