@opensip-cli/checks-universal 0.1.0

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 (620) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +31 -0
  4. package/dist/__tests__/all-checks-execute.test.d.ts +17 -0
  5. package/dist/__tests__/all-checks-execute.test.d.ts.map +1 -0
  6. package/dist/__tests__/all-checks-execute.test.js +452 -0
  7. package/dist/__tests__/all-checks-execute.test.js.map +1 -0
  8. package/dist/__tests__/behavior-fixtures-10.test.d.ts +8 -0
  9. package/dist/__tests__/behavior-fixtures-10.test.d.ts.map +1 -0
  10. package/dist/__tests__/behavior-fixtures-10.test.js +200 -0
  11. package/dist/__tests__/behavior-fixtures-10.test.js.map +1 -0
  12. package/dist/__tests__/behavior-fixtures-11.test.d.ts +8 -0
  13. package/dist/__tests__/behavior-fixtures-11.test.d.ts.map +1 -0
  14. package/dist/__tests__/behavior-fixtures-11.test.js +120 -0
  15. package/dist/__tests__/behavior-fixtures-11.test.js.map +1 -0
  16. package/dist/__tests__/behavior-fixtures-12.test.d.ts +8 -0
  17. package/dist/__tests__/behavior-fixtures-12.test.d.ts.map +1 -0
  18. package/dist/__tests__/behavior-fixtures-12.test.js +157 -0
  19. package/dist/__tests__/behavior-fixtures-12.test.js.map +1 -0
  20. package/dist/__tests__/behavior-fixtures-2.test.d.ts +8 -0
  21. package/dist/__tests__/behavior-fixtures-2.test.d.ts.map +1 -0
  22. package/dist/__tests__/behavior-fixtures-2.test.js +785 -0
  23. package/dist/__tests__/behavior-fixtures-2.test.js.map +1 -0
  24. package/dist/__tests__/behavior-fixtures-3.test.d.ts +6 -0
  25. package/dist/__tests__/behavior-fixtures-3.test.d.ts.map +1 -0
  26. package/dist/__tests__/behavior-fixtures-3.test.js +663 -0
  27. package/dist/__tests__/behavior-fixtures-3.test.js.map +1 -0
  28. package/dist/__tests__/behavior-fixtures-4.test.d.ts +5 -0
  29. package/dist/__tests__/behavior-fixtures-4.test.d.ts.map +1 -0
  30. package/dist/__tests__/behavior-fixtures-4.test.js +612 -0
  31. package/dist/__tests__/behavior-fixtures-4.test.js.map +1 -0
  32. package/dist/__tests__/behavior-fixtures-5.test.d.ts +5 -0
  33. package/dist/__tests__/behavior-fixtures-5.test.d.ts.map +1 -0
  34. package/dist/__tests__/behavior-fixtures-5.test.js +469 -0
  35. package/dist/__tests__/behavior-fixtures-5.test.js.map +1 -0
  36. package/dist/__tests__/behavior-fixtures-6.test.d.ts +8 -0
  37. package/dist/__tests__/behavior-fixtures-6.test.d.ts.map +1 -0
  38. package/dist/__tests__/behavior-fixtures-6.test.js +591 -0
  39. package/dist/__tests__/behavior-fixtures-6.test.js.map +1 -0
  40. package/dist/__tests__/behavior-fixtures-7.test.d.ts +5 -0
  41. package/dist/__tests__/behavior-fixtures-7.test.d.ts.map +1 -0
  42. package/dist/__tests__/behavior-fixtures-7.test.js +662 -0
  43. package/dist/__tests__/behavior-fixtures-7.test.js.map +1 -0
  44. package/dist/__tests__/behavior-fixtures-8.test.d.ts +11 -0
  45. package/dist/__tests__/behavior-fixtures-8.test.d.ts.map +1 -0
  46. package/dist/__tests__/behavior-fixtures-8.test.js +634 -0
  47. package/dist/__tests__/behavior-fixtures-8.test.js.map +1 -0
  48. package/dist/__tests__/behavior-fixtures-9.test.d.ts +11 -0
  49. package/dist/__tests__/behavior-fixtures-9.test.d.ts.map +1 -0
  50. package/dist/__tests__/behavior-fixtures-9.test.js +271 -0
  51. package/dist/__tests__/behavior-fixtures-9.test.js.map +1 -0
  52. package/dist/__tests__/behavior-fixtures.test.d.ts +14 -0
  53. package/dist/__tests__/behavior-fixtures.test.d.ts.map +1 -0
  54. package/dist/__tests__/behavior-fixtures.test.js +1423 -0
  55. package/dist/__tests__/behavior-fixtures.test.js.map +1 -0
  56. package/dist/__tests__/checks.test.d.ts +2 -0
  57. package/dist/__tests__/checks.test.d.ts.map +1 -0
  58. package/dist/__tests__/checks.test.js +61 -0
  59. package/dist/__tests__/checks.test.js.map +1 -0
  60. package/dist/__tests__/env-var-validation.test.d.ts +14 -0
  61. package/dist/__tests__/env-var-validation.test.d.ts.map +1 -0
  62. package/dist/__tests__/env-var-validation.test.js +53 -0
  63. package/dist/__tests__/env-var-validation.test.js.map +1 -0
  64. package/dist/__tests__/file-length-limit.test.d.ts +2 -0
  65. package/dist/__tests__/file-length-limit.test.d.ts.map +1 -0
  66. package/dist/__tests__/file-length-limit.test.js +29 -0
  67. package/dist/__tests__/file-length-limit.test.js.map +1 -0
  68. package/dist/__tests__/fixture-coverage.allowlist.d.ts +18 -0
  69. package/dist/__tests__/fixture-coverage.allowlist.d.ts.map +1 -0
  70. package/dist/__tests__/fixture-coverage.allowlist.js +35 -0
  71. package/dist/__tests__/fixture-coverage.allowlist.js.map +1 -0
  72. package/dist/__tests__/fixture-coverage.test.d.ts +13 -0
  73. package/dist/__tests__/fixture-coverage.test.d.ts.map +1 -0
  74. package/dist/__tests__/fixture-coverage.test.js +57 -0
  75. package/dist/__tests__/fixture-coverage.test.js.map +1 -0
  76. package/dist/__tests__/iic.test.d.ts +15 -0
  77. package/dist/__tests__/iic.test.d.ts.map +1 -0
  78. package/dist/__tests__/iic.test.js +316 -0
  79. package/dist/__tests__/iic.test.js.map +1 -0
  80. package/dist/__tests__/no-skipped-tests.test.d.ts +14 -0
  81. package/dist/__tests__/no-skipped-tests.test.d.ts.map +1 -0
  82. package/dist/__tests__/no-skipped-tests.test.js +144 -0
  83. package/dist/__tests__/no-skipped-tests.test.js.map +1 -0
  84. package/dist/__tests__/no-todo-comments.test.d.ts +2 -0
  85. package/dist/__tests__/no-todo-comments.test.d.ts.map +1 -0
  86. package/dist/__tests__/no-todo-comments.test.js +31 -0
  87. package/dist/__tests__/no-todo-comments.test.js.map +1 -0
  88. package/dist/__tests__/no-unimplemented-markers.test.d.ts +2 -0
  89. package/dist/__tests__/no-unimplemented-markers.test.d.ts.map +1 -0
  90. package/dist/__tests__/no-unimplemented-markers.test.js +140 -0
  91. package/dist/__tests__/no-unimplemented-markers.test.js.map +1 -0
  92. package/dist/__tests__/public-api-jsdoc-scope.test.d.ts +10 -0
  93. package/dist/__tests__/public-api-jsdoc-scope.test.d.ts.map +1 -0
  94. package/dist/__tests__/public-api-jsdoc-scope.test.js +176 -0
  95. package/dist/__tests__/public-api-jsdoc-scope.test.js.map +1 -0
  96. package/dist/__tests__/resilience-fp.test.d.ts +14 -0
  97. package/dist/__tests__/resilience-fp.test.d.ts.map +1 -0
  98. package/dist/__tests__/resilience-fp.test.js +110 -0
  99. package/dist/__tests__/resilience-fp.test.js.map +1 -0
  100. package/dist/checks/architecture/__tests__/no-kebab-option-indexing.test.d.ts +2 -0
  101. package/dist/checks/architecture/__tests__/no-kebab-option-indexing.test.d.ts.map +1 -0
  102. package/dist/checks/architecture/__tests__/no-kebab-option-indexing.test.js +32 -0
  103. package/dist/checks/architecture/__tests__/no-kebab-option-indexing.test.js.map +1 -0
  104. package/dist/checks/architecture/__tests__/tool-has-manifest.test.d.ts +2 -0
  105. package/dist/checks/architecture/__tests__/tool-has-manifest.test.d.ts.map +1 -0
  106. package/dist/checks/architecture/__tests__/tool-has-manifest.test.js +152 -0
  107. package/dist/checks/architecture/__tests__/tool-has-manifest.test.js.map +1 -0
  108. package/dist/checks/architecture/__tests__/vitest-config-required-with-tests.test.d.ts +2 -0
  109. package/dist/checks/architecture/__tests__/vitest-config-required-with-tests.test.d.ts.map +1 -0
  110. package/dist/checks/architecture/__tests__/vitest-config-required-with-tests.test.js +129 -0
  111. package/dist/checks/architecture/__tests__/vitest-config-required-with-tests.test.js.map +1 -0
  112. package/dist/checks/architecture/_yaml-doc-bindings.d.ts +23 -0
  113. package/dist/checks/architecture/_yaml-doc-bindings.d.ts.map +1 -0
  114. package/dist/checks/architecture/_yaml-doc-bindings.js +29 -0
  115. package/dist/checks/architecture/_yaml-doc-bindings.js.map +1 -0
  116. package/dist/checks/architecture/dependencies/index.d.ts +2 -0
  117. package/dist/checks/architecture/dependencies/index.d.ts.map +1 -0
  118. package/dist/checks/architecture/dependencies/index.js +2 -0
  119. package/dist/checks/architecture/dependencies/index.js.map +1 -0
  120. package/dist/checks/architecture/dependencies/no-duplicate-packages.d.ts +11 -0
  121. package/dist/checks/architecture/dependencies/no-duplicate-packages.d.ts.map +1 -0
  122. package/dist/checks/architecture/dependencies/no-duplicate-packages.js +171 -0
  123. package/dist/checks/architecture/dependencies/no-duplicate-packages.js.map +1 -0
  124. package/dist/checks/architecture/docker-best-practices.d.ts +23 -0
  125. package/dist/checks/architecture/docker-best-practices.d.ts.map +1 -0
  126. package/dist/checks/architecture/docker-best-practices.js +427 -0
  127. package/dist/checks/architecture/docker-best-practices.js.map +1 -0
  128. package/dist/checks/architecture/docker-ignore-validation.d.ts +18 -0
  129. package/dist/checks/architecture/docker-ignore-validation.d.ts.map +1 -0
  130. package/dist/checks/architecture/docker-ignore-validation.js +117 -0
  131. package/dist/checks/architecture/docker-ignore-validation.js.map +1 -0
  132. package/dist/checks/architecture/docker-version-sync.d.ts +16 -0
  133. package/dist/checks/architecture/docker-version-sync.d.ts.map +1 -0
  134. package/dist/checks/architecture/docker-version-sync.js +193 -0
  135. package/dist/checks/architecture/docker-version-sync.js.map +1 -0
  136. package/dist/checks/architecture/env-var-validation.d.ts +14 -0
  137. package/dist/checks/architecture/env-var-validation.d.ts.map +1 -0
  138. package/dist/checks/architecture/env-var-validation.js +289 -0
  139. package/dist/checks/architecture/env-var-validation.js.map +1 -0
  140. package/dist/checks/architecture/heavy-import-detection.d.ts +11 -0
  141. package/dist/checks/architecture/heavy-import-detection.d.ts.map +1 -0
  142. package/dist/checks/architecture/heavy-import-detection.js +91 -0
  143. package/dist/checks/architecture/heavy-import-detection.js.map +1 -0
  144. package/dist/checks/architecture/index.d.ts +16 -0
  145. package/dist/checks/architecture/index.d.ts.map +1 -0
  146. package/dist/checks/architecture/index.js +16 -0
  147. package/dist/checks/architecture/index.js.map +1 -0
  148. package/dist/checks/architecture/modules/empty-package-detection.d.ts +11 -0
  149. package/dist/checks/architecture/modules/empty-package-detection.d.ts.map +1 -0
  150. package/dist/checks/architecture/modules/empty-package-detection.js +277 -0
  151. package/dist/checks/architecture/modules/empty-package-detection.js.map +1 -0
  152. package/dist/checks/architecture/modules/index.d.ts +3 -0
  153. package/dist/checks/architecture/modules/index.d.ts.map +1 -0
  154. package/dist/checks/architecture/modules/index.js +3 -0
  155. package/dist/checks/architecture/modules/index.js.map +1 -0
  156. package/dist/checks/architecture/modules/interface-implementation-consistency.d.ts +12 -0
  157. package/dist/checks/architecture/modules/interface-implementation-consistency.d.ts.map +1 -0
  158. package/dist/checks/architecture/modules/interface-implementation-consistency.js +555 -0
  159. package/dist/checks/architecture/modules/interface-implementation-consistency.js.map +1 -0
  160. package/dist/checks/architecture/no-custom-event-emitter.d.ts +11 -0
  161. package/dist/checks/architecture/no-custom-event-emitter.d.ts.map +1 -0
  162. package/dist/checks/architecture/no-custom-event-emitter.js +123 -0
  163. package/dist/checks/architecture/no-custom-event-emitter.js.map +1 -0
  164. package/dist/checks/architecture/no-kebab-option-indexing.d.ts +33 -0
  165. package/dist/checks/architecture/no-kebab-option-indexing.d.ts.map +1 -0
  166. package/dist/checks/architecture/no-kebab-option-indexing.js +81 -0
  167. package/dist/checks/architecture/no-kebab-option-indexing.js.map +1 -0
  168. package/dist/checks/architecture/node-version-consistency.d.ts +22 -0
  169. package/dist/checks/architecture/node-version-consistency.d.ts.map +1 -0
  170. package/dist/checks/architecture/node-version-consistency.js +225 -0
  171. package/dist/checks/architecture/node-version-consistency.js.map +1 -0
  172. package/dist/checks/architecture/project-readme-existence.d.ts +13 -0
  173. package/dist/checks/architecture/project-readme-existence.d.ts.map +1 -0
  174. package/dist/checks/architecture/project-readme-existence.js +55 -0
  175. package/dist/checks/architecture/project-readme-existence.js.map +1 -0
  176. package/dist/checks/architecture/stale-build-artifacts.d.ts +10 -0
  177. package/dist/checks/architecture/stale-build-artifacts.d.ts.map +1 -0
  178. package/dist/checks/architecture/stale-build-artifacts.js +55 -0
  179. package/dist/checks/architecture/stale-build-artifacts.js.map +1 -0
  180. package/dist/checks/architecture/tool-has-manifest.d.ts +27 -0
  181. package/dist/checks/architecture/tool-has-manifest.d.ts.map +1 -0
  182. package/dist/checks/architecture/tool-has-manifest.js +135 -0
  183. package/dist/checks/architecture/tool-has-manifest.js.map +1 -0
  184. package/dist/checks/architecture/vitest-config-extends-base.d.ts +15 -0
  185. package/dist/checks/architecture/vitest-config-extends-base.d.ts.map +1 -0
  186. package/dist/checks/architecture/vitest-config-extends-base.js +104 -0
  187. package/dist/checks/architecture/vitest-config-extends-base.js.map +1 -0
  188. package/dist/checks/architecture/vitest-config-required-with-tests.d.ts +49 -0
  189. package/dist/checks/architecture/vitest-config-required-with-tests.d.ts.map +1 -0
  190. package/dist/checks/architecture/vitest-config-required-with-tests.js +199 -0
  191. package/dist/checks/architecture/vitest-config-required-with-tests.js.map +1 -0
  192. package/dist/checks/documentation/_directives/eslint.d.ts +9 -0
  193. package/dist/checks/documentation/_directives/eslint.d.ts.map +1 -0
  194. package/dist/checks/documentation/_directives/eslint.js +168 -0
  195. package/dist/checks/documentation/_directives/eslint.js.map +1 -0
  196. package/dist/checks/documentation/_directives/fitness.d.ts +9 -0
  197. package/dist/checks/documentation/_directives/fitness.d.ts.map +1 -0
  198. package/dist/checks/documentation/_directives/fitness.js +64 -0
  199. package/dist/checks/documentation/_directives/fitness.js.map +1 -0
  200. package/dist/checks/documentation/_directives/graph.d.ts +10 -0
  201. package/dist/checks/documentation/_directives/graph.d.ts.map +1 -0
  202. package/dist/checks/documentation/_directives/graph.js +65 -0
  203. package/dist/checks/documentation/_directives/graph.js.map +1 -0
  204. package/dist/checks/documentation/_directives/graph.test.d.ts +2 -0
  205. package/dist/checks/documentation/_directives/graph.test.d.ts.map +1 -0
  206. package/dist/checks/documentation/_directives/graph.test.js +54 -0
  207. package/dist/checks/documentation/_directives/graph.test.js.map +1 -0
  208. package/dist/checks/documentation/_directives/semgrep.d.ts +8 -0
  209. package/dist/checks/documentation/_directives/semgrep.d.ts.map +1 -0
  210. package/dist/checks/documentation/_directives/semgrep.js +72 -0
  211. package/dist/checks/documentation/_directives/semgrep.js.map +1 -0
  212. package/dist/checks/documentation/_directives/types.d.ts +21 -0
  213. package/dist/checks/documentation/_directives/types.d.ts.map +1 -0
  214. package/dist/checks/documentation/_directives/types.js +9 -0
  215. package/dist/checks/documentation/_directives/types.js.map +1 -0
  216. package/dist/checks/documentation/_directives/typescript.d.ts +10 -0
  217. package/dist/checks/documentation/_directives/typescript.d.ts.map +1 -0
  218. package/dist/checks/documentation/_directives/typescript.js +54 -0
  219. package/dist/checks/documentation/_directives/typescript.js.map +1 -0
  220. package/dist/checks/documentation/_public-api-graph.d.ts +30 -0
  221. package/dist/checks/documentation/_public-api-graph.d.ts.map +1 -0
  222. package/dist/checks/documentation/_public-api-graph.js +304 -0
  223. package/dist/checks/documentation/_public-api-graph.js.map +1 -0
  224. package/dist/checks/documentation/directive-audit.d.ts +26 -0
  225. package/dist/checks/documentation/directive-audit.d.ts.map +1 -0
  226. package/dist/checks/documentation/directive-audit.js +144 -0
  227. package/dist/checks/documentation/directive-audit.js.map +1 -0
  228. package/dist/checks/documentation/index.d.ts +3 -0
  229. package/dist/checks/documentation/index.d.ts.map +1 -0
  230. package/dist/checks/documentation/index.js +3 -0
  231. package/dist/checks/documentation/index.js.map +1 -0
  232. package/dist/checks/documentation/public-api-jsdoc.d.ts +10 -0
  233. package/dist/checks/documentation/public-api-jsdoc.d.ts.map +1 -0
  234. package/dist/checks/documentation/public-api-jsdoc.js +131 -0
  235. package/dist/checks/documentation/public-api-jsdoc.js.map +1 -0
  236. package/dist/checks/file-length-limit.d.ts +16 -0
  237. package/dist/checks/file-length-limit.d.ts.map +1 -0
  238. package/dist/checks/file-length-limit.js +47 -0
  239. package/dist/checks/file-length-limit.js.map +1 -0
  240. package/dist/checks/index.d.ts +16 -0
  241. package/dist/checks/index.d.ts.map +1 -0
  242. package/dist/checks/index.js +16 -0
  243. package/dist/checks/index.js.map +1 -0
  244. package/dist/checks/no-todo-comments.d.ts +18 -0
  245. package/dist/checks/no-todo-comments.d.ts.map +1 -0
  246. package/dist/checks/no-todo-comments.js +79 -0
  247. package/dist/checks/no-todo-comments.js.map +1 -0
  248. package/dist/checks/no-unimplemented-markers.d.ts +24 -0
  249. package/dist/checks/no-unimplemented-markers.d.ts.map +1 -0
  250. package/dist/checks/no-unimplemented-markers.js +198 -0
  251. package/dist/checks/no-unimplemented-markers.js.map +1 -0
  252. package/dist/checks/quality/api/graphql-offset-pagination.d.ts +9 -0
  253. package/dist/checks/quality/api/graphql-offset-pagination.d.ts.map +1 -0
  254. package/dist/checks/quality/api/graphql-offset-pagination.js +63 -0
  255. package/dist/checks/quality/api/graphql-offset-pagination.js.map +1 -0
  256. package/dist/checks/quality/api/index.d.ts +3 -0
  257. package/dist/checks/quality/api/index.d.ts.map +1 -0
  258. package/dist/checks/quality/api/index.js +3 -0
  259. package/dist/checks/quality/api/index.js.map +1 -0
  260. package/dist/checks/quality/api/zod-openapi-sync.d.ts +13 -0
  261. package/dist/checks/quality/api/zod-openapi-sync.d.ts.map +1 -0
  262. package/dist/checks/quality/api/zod-openapi-sync.js +88 -0
  263. package/dist/checks/quality/api/zod-openapi-sync.js.map +1 -0
  264. package/dist/checks/quality/code-structure/dead-code.d.ts +12 -0
  265. package/dist/checks/quality/code-structure/dead-code.d.ts.map +1 -0
  266. package/dist/checks/quality/code-structure/dead-code.js +238 -0
  267. package/dist/checks/quality/code-structure/dead-code.js.map +1 -0
  268. package/dist/checks/quality/code-structure/index.d.ts +5 -0
  269. package/dist/checks/quality/code-structure/index.d.ts.map +1 -0
  270. package/dist/checks/quality/code-structure/index.js +5 -0
  271. package/dist/checks/quality/code-structure/index.js.map +1 -0
  272. package/dist/checks/quality/code-structure/no-ai-attribution.d.ts +25 -0
  273. package/dist/checks/quality/code-structure/no-ai-attribution.d.ts.map +1 -0
  274. package/dist/checks/quality/code-structure/no-ai-attribution.js +76 -0
  275. package/dist/checks/quality/code-structure/no-ai-attribution.js.map +1 -0
  276. package/dist/checks/quality/code-structure/no-console-log.d.ts +17 -0
  277. package/dist/checks/quality/code-structure/no-console-log.d.ts.map +1 -0
  278. package/dist/checks/quality/code-structure/no-console-log.js +106 -0
  279. package/dist/checks/quality/code-structure/no-console-log.js.map +1 -0
  280. package/dist/checks/quality/code-structure/no-process-artifacts.d.ts +25 -0
  281. package/dist/checks/quality/code-structure/no-process-artifacts.d.ts.map +1 -0
  282. package/dist/checks/quality/code-structure/no-process-artifacts.js +104 -0
  283. package/dist/checks/quality/code-structure/no-process-artifacts.js.map +1 -0
  284. package/dist/checks/quality/dependency-version-consistency.d.ts +20 -0
  285. package/dist/checks/quality/dependency-version-consistency.d.ts.map +1 -0
  286. package/dist/checks/quality/dependency-version-consistency.js +266 -0
  287. package/dist/checks/quality/dependency-version-consistency.js.map +1 -0
  288. package/dist/checks/quality/fitness-ignore-hygiene.d.ts +10 -0
  289. package/dist/checks/quality/fitness-ignore-hygiene.d.ts.map +1 -0
  290. package/dist/checks/quality/fitness-ignore-hygiene.js +93 -0
  291. package/dist/checks/quality/fitness-ignore-hygiene.js.map +1 -0
  292. package/dist/checks/quality/frontend/expo-vector-icons.d.ts +13 -0
  293. package/dist/checks/quality/frontend/expo-vector-icons.d.ts.map +1 -0
  294. package/dist/checks/quality/frontend/expo-vector-icons.js +80 -0
  295. package/dist/checks/quality/frontend/expo-vector-icons.js.map +1 -0
  296. package/dist/checks/quality/frontend/image-optimization.d.ts +13 -0
  297. package/dist/checks/quality/frontend/image-optimization.d.ts.map +1 -0
  298. package/dist/checks/quality/frontend/image-optimization.js +166 -0
  299. package/dist/checks/quality/frontend/image-optimization.js.map +1 -0
  300. package/dist/checks/quality/frontend/index.d.ts +4 -0
  301. package/dist/checks/quality/frontend/index.d.ts.map +1 -0
  302. package/dist/checks/quality/frontend/index.js +4 -0
  303. package/dist/checks/quality/frontend/index.js.map +1 -0
  304. package/dist/checks/quality/frontend/navigation-typing.d.ts +12 -0
  305. package/dist/checks/quality/frontend/navigation-typing.d.ts.map +1 -0
  306. package/dist/checks/quality/frontend/navigation-typing.js +77 -0
  307. package/dist/checks/quality/frontend/navigation-typing.js.map +1 -0
  308. package/dist/checks/quality/graph-ignore-hygiene.d.ts +10 -0
  309. package/dist/checks/quality/graph-ignore-hygiene.d.ts.map +1 -0
  310. package/dist/checks/quality/graph-ignore-hygiene.js +95 -0
  311. package/dist/checks/quality/graph-ignore-hygiene.js.map +1 -0
  312. package/dist/checks/quality/graph-ignore-hygiene.test.d.ts +14 -0
  313. package/dist/checks/quality/graph-ignore-hygiene.test.d.ts.map +1 -0
  314. package/dist/checks/quality/graph-ignore-hygiene.test.js +58 -0
  315. package/dist/checks/quality/graph-ignore-hygiene.test.js.map +1 -0
  316. package/dist/checks/quality/index.d.ts +16 -0
  317. package/dist/checks/quality/index.d.ts.map +1 -0
  318. package/dist/checks/quality/index.js +16 -0
  319. package/dist/checks/quality/index.js.map +1 -0
  320. package/dist/checks/quality/linting/eslint-justifications.d.ts +12 -0
  321. package/dist/checks/quality/linting/eslint-justifications.d.ts.map +1 -0
  322. package/dist/checks/quality/linting/eslint-justifications.js +328 -0
  323. package/dist/checks/quality/linting/eslint-justifications.js.map +1 -0
  324. package/dist/checks/quality/linting/index.d.ts +4 -0
  325. package/dist/checks/quality/linting/index.d.ts.map +1 -0
  326. package/dist/checks/quality/linting/index.js +4 -0
  327. package/dist/checks/quality/linting/index.js.map +1 -0
  328. package/dist/checks/quality/linting/semgrep-justifications.d.ts +16 -0
  329. package/dist/checks/quality/linting/semgrep-justifications.d.ts.map +1 -0
  330. package/dist/checks/quality/linting/semgrep-justifications.js +229 -0
  331. package/dist/checks/quality/linting/semgrep-justifications.js.map +1 -0
  332. package/dist/checks/quality/linting/typescript-directive-hygiene.d.ts +12 -0
  333. package/dist/checks/quality/linting/typescript-directive-hygiene.d.ts.map +1 -0
  334. package/dist/checks/quality/linting/typescript-directive-hygiene.js +142 -0
  335. package/dist/checks/quality/linting/typescript-directive-hygiene.js.map +1 -0
  336. package/dist/checks/quality/no-compatibility-layer-names.d.ts +13 -0
  337. package/dist/checks/quality/no-compatibility-layer-names.d.ts.map +1 -0
  338. package/dist/checks/quality/no-compatibility-layer-names.js +100 -0
  339. package/dist/checks/quality/no-compatibility-layer-names.js.map +1 -0
  340. package/dist/checks/quality/no-deprecated-tags.d.ts +11 -0
  341. package/dist/checks/quality/no-deprecated-tags.d.ts.map +1 -0
  342. package/dist/checks/quality/no-deprecated-tags.js +76 -0
  343. package/dist/checks/quality/no-deprecated-tags.js.map +1 -0
  344. package/dist/checks/quality/no-markdown-references.d.ts +16 -0
  345. package/dist/checks/quality/no-markdown-references.d.ts.map +1 -0
  346. package/dist/checks/quality/no-markdown-references.js +145 -0
  347. package/dist/checks/quality/no-markdown-references.js.map +1 -0
  348. package/dist/checks/quality/no-raw-regex-on-code.d.ts +9 -0
  349. package/dist/checks/quality/no-raw-regex-on-code.d.ts.map +1 -0
  350. package/dist/checks/quality/no-raw-regex-on-code.js +61 -0
  351. package/dist/checks/quality/no-raw-regex-on-code.js.map +1 -0
  352. package/dist/checks/quality/no-temporary-workarounds.d.ts +11 -0
  353. package/dist/checks/quality/no-temporary-workarounds.d.ts.map +1 -0
  354. package/dist/checks/quality/no-temporary-workarounds.js +69 -0
  355. package/dist/checks/quality/no-temporary-workarounds.js.map +1 -0
  356. package/dist/checks/quality/no-window-alert.d.ts +19 -0
  357. package/dist/checks/quality/no-window-alert.d.ts.map +1 -0
  358. package/dist/checks/quality/no-window-alert.js +74 -0
  359. package/dist/checks/quality/no-window-alert.js.map +1 -0
  360. package/dist/checks/quality/observability/index.d.ts +2 -0
  361. package/dist/checks/quality/observability/index.d.ts.map +1 -0
  362. package/dist/checks/quality/observability/index.js +2 -0
  363. package/dist/checks/quality/observability/index.js.map +1 -0
  364. package/dist/checks/quality/observability/pino-serializer-coverage.d.ts +15 -0
  365. package/dist/checks/quality/observability/pino-serializer-coverage.d.ts.map +1 -0
  366. package/dist/checks/quality/observability/pino-serializer-coverage.js +209 -0
  367. package/dist/checks/quality/observability/pino-serializer-coverage.js.map +1 -0
  368. package/dist/checks/quality/patterns/async-state-pattern.d.ts +14 -0
  369. package/dist/checks/quality/patterns/async-state-pattern.d.ts.map +1 -0
  370. package/dist/checks/quality/patterns/async-state-pattern.js +80 -0
  371. package/dist/checks/quality/patterns/async-state-pattern.js.map +1 -0
  372. package/dist/checks/quality/patterns/index.d.ts +4 -0
  373. package/dist/checks/quality/patterns/index.d.ts.map +1 -0
  374. package/dist/checks/quality/patterns/index.js +4 -0
  375. package/dist/checks/quality/patterns/index.js.map +1 -0
  376. package/dist/checks/quality/patterns/no-non-null-assertions.d.ts +10 -0
  377. package/dist/checks/quality/patterns/no-non-null-assertions.d.ts.map +1 -0
  378. package/dist/checks/quality/patterns/no-non-null-assertions.js +97 -0
  379. package/dist/checks/quality/patterns/no-non-null-assertions.js.map +1 -0
  380. package/dist/checks/quality/patterns/performance-anti-patterns.d.ts +16 -0
  381. package/dist/checks/quality/patterns/performance-anti-patterns.d.ts.map +1 -0
  382. package/dist/checks/quality/patterns/performance-anti-patterns.js +239 -0
  383. package/dist/checks/quality/patterns/performance-anti-patterns.js.map +1 -0
  384. package/dist/checks/resilience/_helpers/config-validation.d.ts +27 -0
  385. package/dist/checks/resilience/_helpers/config-validation.d.ts.map +1 -0
  386. package/dist/checks/resilience/_helpers/config-validation.js +61 -0
  387. package/dist/checks/resilience/_helpers/config-validation.js.map +1 -0
  388. package/dist/checks/resilience/batch-operations.d.ts +22 -0
  389. package/dist/checks/resilience/batch-operations.d.ts.map +1 -0
  390. package/dist/checks/resilience/batch-operations.js +422 -0
  391. package/dist/checks/resilience/batch-operations.js.map +1 -0
  392. package/dist/checks/resilience/cache-ttl-validation.d.ts +13 -0
  393. package/dist/checks/resilience/cache-ttl-validation.d.ts.map +1 -0
  394. package/dist/checks/resilience/cache-ttl-validation.js +222 -0
  395. package/dist/checks/resilience/cache-ttl-validation.js.map +1 -0
  396. package/dist/checks/resilience/catch-clause-safety.d.ts +12 -0
  397. package/dist/checks/resilience/catch-clause-safety.d.ts.map +1 -0
  398. package/dist/checks/resilience/catch-clause-safety.js +110 -0
  399. package/dist/checks/resilience/catch-clause-safety.js.map +1 -0
  400. package/dist/checks/resilience/dangerous-config-defaults.d.ts +11 -0
  401. package/dist/checks/resilience/dangerous-config-defaults.d.ts.map +1 -0
  402. package/dist/checks/resilience/dangerous-config-defaults.js +304 -0
  403. package/dist/checks/resilience/dangerous-config-defaults.js.map +1 -0
  404. package/dist/checks/resilience/error-code-registration.d.ts +11 -0
  405. package/dist/checks/resilience/error-code-registration.d.ts.map +1 -0
  406. package/dist/checks/resilience/error-code-registration.js +88 -0
  407. package/dist/checks/resilience/error-code-registration.js.map +1 -0
  408. package/dist/checks/resilience/event-patterns.d.ts +21 -0
  409. package/dist/checks/resilience/event-patterns.d.ts.map +1 -0
  410. package/dist/checks/resilience/event-patterns.js +232 -0
  411. package/dist/checks/resilience/event-patterns.js.map +1 -0
  412. package/dist/checks/resilience/exit-code-correctness.d.ts +12 -0
  413. package/dist/checks/resilience/exit-code-correctness.d.ts.map +1 -0
  414. package/dist/checks/resilience/exit-code-correctness.js +107 -0
  415. package/dist/checks/resilience/exit-code-correctness.js.map +1 -0
  416. package/dist/checks/resilience/index.d.ts +18 -0
  417. package/dist/checks/resilience/index.d.ts.map +1 -0
  418. package/dist/checks/resilience/index.js +18 -0
  419. package/dist/checks/resilience/index.js.map +1 -0
  420. package/dist/checks/resilience/no-hardcoded-timeouts.d.ts +10 -0
  421. package/dist/checks/resilience/no-hardcoded-timeouts.d.ts.map +1 -0
  422. package/dist/checks/resilience/no-hardcoded-timeouts.js +291 -0
  423. package/dist/checks/resilience/no-hardcoded-timeouts.js.map +1 -0
  424. package/dist/checks/resilience/no-process-exit-in-finally.d.ts +11 -0
  425. package/dist/checks/resilience/no-process-exit-in-finally.d.ts.map +1 -0
  426. package/dist/checks/resilience/no-process-exit-in-finally.js +89 -0
  427. package/dist/checks/resilience/no-process-exit-in-finally.js.map +1 -0
  428. package/dist/checks/resilience/readline-cleanup.d.ts +11 -0
  429. package/dist/checks/resilience/readline-cleanup.d.ts.map +1 -0
  430. package/dist/checks/resilience/readline-cleanup.js +107 -0
  431. package/dist/checks/resilience/readline-cleanup.js.map +1 -0
  432. package/dist/checks/resilience/recovery-patterns.d.ts +25 -0
  433. package/dist/checks/resilience/recovery-patterns.d.ts.map +1 -0
  434. package/dist/checks/resilience/recovery-patterns.js +273 -0
  435. package/dist/checks/resilience/recovery-patterns.js.map +1 -0
  436. package/dist/checks/resilience/reentrancy-guard.d.ts +12 -0
  437. package/dist/checks/resilience/reentrancy-guard.d.ts.map +1 -0
  438. package/dist/checks/resilience/reentrancy-guard.js +86 -0
  439. package/dist/checks/resilience/reentrancy-guard.js.map +1 -0
  440. package/dist/checks/resilience/retry-config-validation.d.ts +13 -0
  441. package/dist/checks/resilience/retry-config-validation.d.ts.map +1 -0
  442. package/dist/checks/resilience/retry-config-validation.js +159 -0
  443. package/dist/checks/resilience/retry-config-validation.js.map +1 -0
  444. package/dist/checks/resilience/sentry/_helpers/sentry.d.ts +25 -0
  445. package/dist/checks/resilience/sentry/_helpers/sentry.d.ts.map +1 -0
  446. package/dist/checks/resilience/sentry/_helpers/sentry.js +68 -0
  447. package/dist/checks/resilience/sentry/_helpers/sentry.js.map +1 -0
  448. package/dist/checks/resilience/sentry/index.d.ts +8 -0
  449. package/dist/checks/resilience/sentry/index.d.ts.map +1 -0
  450. package/dist/checks/resilience/sentry/index.js +8 -0
  451. package/dist/checks/resilience/sentry/index.js.map +1 -0
  452. package/dist/checks/resilience/sentry/sentry-dsn-configured.d.ts +12 -0
  453. package/dist/checks/resilience/sentry/sentry-dsn-configured.d.ts.map +1 -0
  454. package/dist/checks/resilience/sentry/sentry-dsn-configured.js +55 -0
  455. package/dist/checks/resilience/sentry/sentry-dsn-configured.js.map +1 -0
  456. package/dist/checks/resilience/sentry/sentry-environment-set.d.ts +12 -0
  457. package/dist/checks/resilience/sentry/sentry-environment-set.d.ts.map +1 -0
  458. package/dist/checks/resilience/sentry/sentry-environment-set.js +51 -0
  459. package/dist/checks/resilience/sentry/sentry-environment-set.js.map +1 -0
  460. package/dist/checks/resilience/sentry/sentry-error-boundary.d.ts +12 -0
  461. package/dist/checks/resilience/sentry/sentry-error-boundary.d.ts.map +1 -0
  462. package/dist/checks/resilience/sentry/sentry-error-boundary.js +75 -0
  463. package/dist/checks/resilience/sentry/sentry-error-boundary.js.map +1 -0
  464. package/dist/checks/resilience/sentry/sentry-pii-scrubbing.d.ts +13 -0
  465. package/dist/checks/resilience/sentry/sentry-pii-scrubbing.d.ts.map +1 -0
  466. package/dist/checks/resilience/sentry/sentry-pii-scrubbing.js +125 -0
  467. package/dist/checks/resilience/sentry/sentry-pii-scrubbing.js.map +1 -0
  468. package/dist/checks/resilience/sentry/sentry-release-set.d.ts +12 -0
  469. package/dist/checks/resilience/sentry/sentry-release-set.d.ts.map +1 -0
  470. package/dist/checks/resilience/sentry/sentry-release-set.js +51 -0
  471. package/dist/checks/resilience/sentry/sentry-release-set.js.map +1 -0
  472. package/dist/checks/resilience/sentry/sentry-sample-rate.d.ts +12 -0
  473. package/dist/checks/resilience/sentry/sentry-sample-rate.d.ts.map +1 -0
  474. package/dist/checks/resilience/sentry/sentry-sample-rate.js +78 -0
  475. package/dist/checks/resilience/sentry/sentry-sample-rate.js.map +1 -0
  476. package/dist/checks/resilience/sentry/sentry-source-maps.d.ts +12 -0
  477. package/dist/checks/resilience/sentry/sentry-source-maps.d.ts.map +1 -0
  478. package/dist/checks/resilience/sentry/sentry-source-maps.js +83 -0
  479. package/dist/checks/resilience/sentry/sentry-source-maps.js.map +1 -0
  480. package/dist/checks/resilience/service-patterns.d.ts +18 -0
  481. package/dist/checks/resilience/service-patterns.d.ts.map +1 -0
  482. package/dist/checks/resilience/service-patterns.js +230 -0
  483. package/dist/checks/resilience/service-patterns.js.map +1 -0
  484. package/dist/checks/resilience/timer-lifecycle.d.ts +10 -0
  485. package/dist/checks/resilience/timer-lifecycle.d.ts.map +1 -0
  486. package/dist/checks/resilience/timer-lifecycle.js +78 -0
  487. package/dist/checks/resilience/timer-lifecycle.js.map +1 -0
  488. package/dist/checks/resilience/transaction-patterns.d.ts +21 -0
  489. package/dist/checks/resilience/transaction-patterns.d.ts.map +1 -0
  490. package/dist/checks/resilience/transaction-patterns.js +258 -0
  491. package/dist/checks/resilience/transaction-patterns.js.map +1 -0
  492. package/dist/checks/security/__tests__/no-hardcoded-secrets.test.d.ts +9 -0
  493. package/dist/checks/security/__tests__/no-hardcoded-secrets.test.d.ts.map +1 -0
  494. package/dist/checks/security/__tests__/no-hardcoded-secrets.test.js +37 -0
  495. package/dist/checks/security/__tests__/no-hardcoded-secrets.test.js.map +1 -0
  496. package/dist/checks/security/__tests__/package-supply-chain-policy.test.d.ts +2 -0
  497. package/dist/checks/security/__tests__/package-supply-chain-policy.test.d.ts.map +1 -0
  498. package/dist/checks/security/__tests__/package-supply-chain-policy.test.js +128 -0
  499. package/dist/checks/security/__tests__/package-supply-chain-policy.test.js.map +1 -0
  500. package/dist/checks/security/api-key-rotation.d.ts +10 -0
  501. package/dist/checks/security/api-key-rotation.d.ts.map +1 -0
  502. package/dist/checks/security/api-key-rotation.js +186 -0
  503. package/dist/checks/security/api-key-rotation.js.map +1 -0
  504. package/dist/checks/security/auth-middleware-coverage.d.ts +11 -0
  505. package/dist/checks/security/auth-middleware-coverage.d.ts.map +1 -0
  506. package/dist/checks/security/auth-middleware-coverage.js +210 -0
  507. package/dist/checks/security/auth-middleware-coverage.js.map +1 -0
  508. package/dist/checks/security/auth-route-guard.d.ts +12 -0
  509. package/dist/checks/security/auth-route-guard.d.ts.map +1 -0
  510. package/dist/checks/security/auth-route-guard.js +70 -0
  511. package/dist/checks/security/auth-route-guard.js.map +1 -0
  512. package/dist/checks/security/cors-configuration.d.ts +11 -0
  513. package/dist/checks/security/cors-configuration.d.ts.map +1 -0
  514. package/dist/checks/security/cors-configuration.js +126 -0
  515. package/dist/checks/security/cors-configuration.js.map +1 -0
  516. package/dist/checks/security/csp-headers.d.ts +11 -0
  517. package/dist/checks/security/csp-headers.d.ts.map +1 -0
  518. package/dist/checks/security/csp-headers.js +192 -0
  519. package/dist/checks/security/csp-headers.js.map +1 -0
  520. package/dist/checks/security/dependency-vulnerability-audit.d.ts +15 -0
  521. package/dist/checks/security/dependency-vulnerability-audit.d.ts.map +1 -0
  522. package/dist/checks/security/dependency-vulnerability-audit.js +184 -0
  523. package/dist/checks/security/dependency-vulnerability-audit.js.map +1 -0
  524. package/dist/checks/security/env-secret-exposure.d.ts +11 -0
  525. package/dist/checks/security/env-secret-exposure.d.ts.map +1 -0
  526. package/dist/checks/security/env-secret-exposure.js +127 -0
  527. package/dist/checks/security/env-secret-exposure.js.map +1 -0
  528. package/dist/checks/security/hasura-production-config.d.ts +11 -0
  529. package/dist/checks/security/hasura-production-config.d.ts.map +1 -0
  530. package/dist/checks/security/hasura-production-config.js +122 -0
  531. package/dist/checks/security/hasura-production-config.js.map +1 -0
  532. package/dist/checks/security/index.d.ts +17 -0
  533. package/dist/checks/security/index.d.ts.map +1 -0
  534. package/dist/checks/security/index.js +17 -0
  535. package/dist/checks/security/index.js.map +1 -0
  536. package/dist/checks/security/jwt-validation.d.ts +11 -0
  537. package/dist/checks/security/jwt-validation.d.ts.map +1 -0
  538. package/dist/checks/security/jwt-validation.js +294 -0
  539. package/dist/checks/security/jwt-validation.js.map +1 -0
  540. package/dist/checks/security/no-eval.d.ts +16 -0
  541. package/dist/checks/security/no-eval.d.ts.map +1 -0
  542. package/dist/checks/security/no-eval.js +83 -0
  543. package/dist/checks/security/no-eval.js.map +1 -0
  544. package/dist/checks/security/no-hardcoded-secrets.d.ts +28 -0
  545. package/dist/checks/security/no-hardcoded-secrets.d.ts.map +1 -0
  546. package/dist/checks/security/no-hardcoded-secrets.js +209 -0
  547. package/dist/checks/security/no-hardcoded-secrets.js.map +1 -0
  548. package/dist/checks/security/package-supply-chain-policy.d.ts +12 -0
  549. package/dist/checks/security/package-supply-chain-policy.d.ts.map +1 -0
  550. package/dist/checks/security/package-supply-chain-policy.js +534 -0
  551. package/dist/checks/security/package-supply-chain-policy.js.map +1 -0
  552. package/dist/checks/security/rate-limit-coverage.d.ts +10 -0
  553. package/dist/checks/security/rate-limit-coverage.d.ts.map +1 -0
  554. package/dist/checks/security/rate-limit-coverage.js +143 -0
  555. package/dist/checks/security/rate-limit-coverage.js.map +1 -0
  556. package/dist/checks/security/semgrep-scan.d.ts +13 -0
  557. package/dist/checks/security/semgrep-scan.d.ts.map +1 -0
  558. package/dist/checks/security/semgrep-scan.js +86 -0
  559. package/dist/checks/security/semgrep-scan.js.map +1 -0
  560. package/dist/checks/security/use-centralized-crypto.d.ts +11 -0
  561. package/dist/checks/security/use-centralized-crypto.d.ts.map +1 -0
  562. package/dist/checks/security/use-centralized-crypto.js +129 -0
  563. package/dist/checks/security/use-centralized-crypto.js.map +1 -0
  564. package/dist/checks/security/webhook-signature-verification.d.ts +10 -0
  565. package/dist/checks/security/webhook-signature-verification.d.ts.map +1 -0
  566. package/dist/checks/security/webhook-signature-verification.js +183 -0
  567. package/dist/checks/security/webhook-signature-verification.js.map +1 -0
  568. package/dist/checks/testing/index.d.ts +6 -0
  569. package/dist/checks/testing/index.d.ts.map +1 -0
  570. package/dist/checks/testing/index.js +6 -0
  571. package/dist/checks/testing/index.js.map +1 -0
  572. package/dist/checks/testing/no-skipped-tests.d.ts +40 -0
  573. package/dist/checks/testing/no-skipped-tests.d.ts.map +1 -0
  574. package/dist/checks/testing/no-skipped-tests.js +174 -0
  575. package/dist/checks/testing/no-skipped-tests.js.map +1 -0
  576. package/dist/checks/testing/no-stub-tests.d.ts +11 -0
  577. package/dist/checks/testing/no-stub-tests.d.ts.map +1 -0
  578. package/dist/checks/testing/no-stub-tests.js +103 -0
  579. package/dist/checks/testing/no-stub-tests.js.map +1 -0
  580. package/dist/checks/testing/test-convention-consistency.d.ts +14 -0
  581. package/dist/checks/testing/test-convention-consistency.d.ts.map +1 -0
  582. package/dist/checks/testing/test-convention-consistency.js +93 -0
  583. package/dist/checks/testing/test-convention-consistency.js.map +1 -0
  584. package/dist/checks/testing/test-file-naming.d.ts +13 -0
  585. package/dist/checks/testing/test-file-naming.d.ts.map +1 -0
  586. package/dist/checks/testing/test-file-naming.js +218 -0
  587. package/dist/checks/testing/test-file-naming.js.map +1 -0
  588. package/dist/checks/testing/test-file-pairing.d.ts +13 -0
  589. package/dist/checks/testing/test-file-pairing.d.ts.map +1 -0
  590. package/dist/checks/testing/test-file-pairing.js +274 -0
  591. package/dist/checks/testing/test-file-pairing.js.map +1 -0
  592. package/dist/display/architecture.d.ts +9 -0
  593. package/dist/display/architecture.d.ts.map +1 -0
  594. package/dist/display/architecture.js +29 -0
  595. package/dist/display/architecture.js.map +1 -0
  596. package/dist/display/index.d.ts +20 -0
  597. package/dist/display/index.d.ts.map +1 -0
  598. package/dist/display/index.js +30 -0
  599. package/dist/display/index.js.map +1 -0
  600. package/dist/display/quality.d.ts +7 -0
  601. package/dist/display/quality.d.ts.map +1 -0
  602. package/dist/display/quality.js +34 -0
  603. package/dist/display/quality.js.map +1 -0
  604. package/dist/display/resilience.d.ts +7 -0
  605. package/dist/display/resilience.d.ts.map +1 -0
  606. package/dist/display/resilience.js +36 -0
  607. package/dist/display/resilience.js.map +1 -0
  608. package/dist/display/security-testing.d.ts +9 -0
  609. package/dist/display/security-testing.d.ts.map +1 -0
  610. package/dist/display/security-testing.js +31 -0
  611. package/dist/display/security-testing.js.map +1 -0
  612. package/dist/display/types.d.ts +6 -0
  613. package/dist/display/types.d.ts.map +1 -0
  614. package/dist/display/types.js +6 -0
  615. package/dist/display/types.js.map +1 -0
  616. package/dist/index.d.ts +19 -0
  617. package/dist/index.d.ts.map +1 -0
  618. package/dist/index.js +21 -0
  619. package/dist/index.js.map +1 -0
  620. package/package.json +52 -0
@@ -0,0 +1,1423 @@
1
+ // @fitness-ignore-file file-length-limit -- behavior fixture suite; related scenarios stay together while checks are split into focused tests.
2
+ /**
3
+ * @fileoverview Targeted fixture-based tests for universal check behavior.
4
+ *
5
+ * Each `describe` block targets a specific check behavior that the
6
+ * all-checks-execute parametric run does not assert directly. The fixtures are
7
+ * crafted to drive the analyze function past its bail-out conditions and into
8
+ * its violation-creation branches.
9
+ *
10
+ * Pattern: build a tmpdir cwd, write fixture files, run the check via
11
+ * `check.run(cwd, { targetFiles })`, then assert on the structured
12
+ * `signals` array.
13
+ */
14
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { dirname, join } from 'node:path';
17
+ import { fileCache } from '@opensip-cli/fitness';
18
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
19
+ import { checks } from '../index.js';
20
+ function findCheck(slug) {
21
+ const check = checks.find((c) => c.config.slug === slug);
22
+ if (!check)
23
+ throw new Error(`check not found: ${slug}`);
24
+ return check;
25
+ }
26
+ function makeFixtureDir(prefix) {
27
+ return mkdtempSync(join(tmpdir(), `cu-cov-${prefix}-`));
28
+ }
29
+ function writeFixture(cwd, rel, content) {
30
+ const abs = join(cwd, rel);
31
+ mkdirSync(dirname(abs), { recursive: true });
32
+ writeFileSync(abs, content);
33
+ return abs;
34
+ }
35
+ afterEach(() => {
36
+ // Each suite manages its own fileCache prewarming; ensure no
37
+ // cross-test contamination.
38
+ fileCache.clear();
39
+ });
40
+ // =============================================================================
41
+ // transaction-patterns: transaction-boundary-validation + transaction-timeout
42
+ // =============================================================================
43
+ describe('transaction-boundary-validation', () => {
44
+ let cwd;
45
+ const files = [];
46
+ beforeAll(() => {
47
+ cwd = makeFixtureDir('tx-boundary');
48
+ files.push(writeFixture(cwd, 'src/uncommitted.ts', [
49
+ 'export async function transfer(db: any) {',
50
+ ' await db.beginTransaction();',
51
+ ' await db.users.update({ id: 1 });',
52
+ '}',
53
+ ].join('\n')), writeFixture(cwd, 'src/properly-handled.ts', [
54
+ 'export async function transfer(db: any) {',
55
+ ' await db.startTransaction();',
56
+ ' try { await db.users.update({ id: 1 }); await db.commit(); }',
57
+ ' catch (e) { await db.rollback(); }',
58
+ '}',
59
+ ].join('\n')), writeFixture(cwd, 'src/async-in-tx.ts', [
60
+ 'export async function risky(db: any, http: any) {',
61
+ ' await db.beginTransaction();',
62
+ ' await http.fetch("https://other-service");',
63
+ ' await db.users.update({ id: 1 });',
64
+ '}',
65
+ ].join('\n')), writeFixture(cwd, 'src/no-tx.ts', ['export async function plain() { return 1; }'].join('\n')), writeFixture(cwd, 'src/delegation.ts', [
66
+ 'export async function delegated(repo: any, work: any) {',
67
+ ' return await this.repository.transaction(work);',
68
+ '}',
69
+ ].join('\n')));
70
+ });
71
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
72
+ it('flags an uncommitted transaction', async () => {
73
+ const result = await findCheck('transaction-boundary-validation').run(cwd, {
74
+ targetFiles: files,
75
+ });
76
+ const types = result.signals.map((s) => s.metadata.type);
77
+ expect(types).toContain('uncommitted-transaction');
78
+ });
79
+ it('flags async operations inside a transaction', async () => {
80
+ const result = await findCheck('transaction-boundary-validation').run(cwd, {
81
+ targetFiles: files,
82
+ });
83
+ const types = result.signals.map((s) => s.metadata.type);
84
+ expect(types).toContain('async-in-transaction');
85
+ });
86
+ it('does not fire on a delegation pattern (`return this.repo.transaction(...)`)', async () => {
87
+ const result = await findCheck('transaction-boundary-validation').run(cwd, {
88
+ targetFiles: [join(cwd, 'src/delegation.ts'), join(cwd, 'src/no-tx.ts')],
89
+ });
90
+ const types = result.signals.map((s) => s.metadata.type);
91
+ expect(types).not.toContain('uncommitted-transaction');
92
+ });
93
+ });
94
+ describe('transaction-timeout', () => {
95
+ let cwd;
96
+ const files = [];
97
+ beforeAll(() => {
98
+ cwd = makeFixtureDir('tx-timeout');
99
+ files.push(writeFixture(cwd, 'src/no-timeout.ts', [
100
+ 'export async function update(queryRunner: any) {',
101
+ ' await queryRunner.startTransaction();',
102
+ ' await queryRunner.update();',
103
+ '}',
104
+ ].join('\n')), writeFixture(cwd, 'src/with-timeout.ts', [
105
+ 'export async function update(queryRunner: any) {',
106
+ ' const transactionTimeout = 30000;',
107
+ ' await queryRunner.startTransaction();',
108
+ '}',
109
+ ].join('\n')), writeFixture(cwd, 'src/no-tx-here.ts', 'export const x = 1;'));
110
+ });
111
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
112
+ it('flags manual transaction usage missing timeout configuration', async () => {
113
+ const result = await findCheck('transaction-timeout').run(cwd, { targetFiles: files });
114
+ const types = result.signals.map((s) => s.metadata.type);
115
+ expect(types).toContain('missing-transaction-timeout');
116
+ });
117
+ it('skips files that already declare a transaction timeout', async () => {
118
+ const result = await findCheck('transaction-timeout').run(cwd, {
119
+ targetFiles: [join(cwd, 'src/with-timeout.ts')],
120
+ });
121
+ expect(result.signals.length).toBe(0);
122
+ });
123
+ });
124
+ // =============================================================================
125
+ // reentrancy-guard
126
+ // =============================================================================
127
+ describe('reentrancy-guard', () => {
128
+ let cwd;
129
+ const files = [];
130
+ beforeAll(() => {
131
+ cwd = makeFixtureDir('reentrancy');
132
+ files.push(writeFixture(cwd, 'src/server.ts', [
133
+ 'let serverRunning = false;',
134
+ 'export function startServer() {',
135
+ ' if (serverRunning) return;',
136
+ ' serverRunning = true;',
137
+ '}',
138
+ ].join('\n')), writeFixture(cwd, 'src/no-flag.ts', 'export const x = 1;'), writeFixture(cwd, 'src/__tests__/skip.test.ts', ['let isRunning = false;', 'if (isRunning) return;'].join('\n')));
139
+ });
140
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
141
+ it('flags a module-scoped boolean reentrancy guard', async () => {
142
+ const result = await findCheck('reentrancy-guard').run(cwd, { targetFiles: files });
143
+ const types = result.signals.map((s) => s.metadata.type);
144
+ expect(types).toContain('boolean-reentrancy-guard');
145
+ });
146
+ it('skips test files', async () => {
147
+ const result = await findCheck('reentrancy-guard').run(cwd, {
148
+ targetFiles: [join(cwd, 'src/__tests__/skip.test.ts')],
149
+ });
150
+ expect(result.signals.length).toBe(0);
151
+ });
152
+ });
153
+ // =============================================================================
154
+ // readline-cleanup
155
+ // =============================================================================
156
+ describe('readline-cleanup', () => {
157
+ let cwd;
158
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
159
+ const files = [];
160
+ beforeAll(() => {
161
+ cwd = makeFixtureDir('readline');
162
+ files.push(writeFixture(cwd, 'src/no-cleanup.ts', [
163
+ 'import { createInterface } from "node:readline";',
164
+ 'export async function ask() {',
165
+ ' const rl = readline.createInterface({ input: process.stdin });',
166
+ ' return new Promise(r => rl.question("?", r));',
167
+ '}',
168
+ ].join('\n')), writeFixture(cwd, 'src/with-cleanup.ts', [
169
+ 'export async function ask() {',
170
+ ' const rl = readline.createInterface({ input: process.stdin });',
171
+ ' try { return await something(rl); } finally { rl.close(); }',
172
+ '}',
173
+ ].join('\n')), writeFixture(cwd, 'src/helper-call.ts', [
174
+ 'export async function run() {',
175
+ ' const ans = await readLine("Continue?");',
176
+ ' return ans;',
177
+ '}',
178
+ ].join('\n')), writeFixture(cwd, 'src/helper-defn.ts', ['export async function readLine(prompt: string) { return prompt; }'].join('\n')));
179
+ });
180
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
181
+ it('flags readline.createInterface without cleanup', async () => {
182
+ const result = await findCheck('readline-cleanup').run(cwd, {
183
+ targetFiles: [join(cwd, 'src/no-cleanup.ts')],
184
+ });
185
+ const types = result.signals.map((s) => s.metadata.type);
186
+ expect(types).toContain('readline-no-cleanup');
187
+ });
188
+ it('skips files that wrap readline in try/finally with rl.close()', async () => {
189
+ const result = await findCheck('readline-cleanup').run(cwd, {
190
+ targetFiles: [join(cwd, 'src/with-cleanup.ts')],
191
+ });
192
+ expect(result.signals.length).toBe(0);
193
+ });
194
+ it('does not flag the readLine() definition itself', async () => {
195
+ const result = await findCheck('readline-cleanup').run(cwd, {
196
+ targetFiles: [join(cwd, 'src/helper-defn.ts')],
197
+ });
198
+ expect(result.signals.length).toBe(0);
199
+ });
200
+ it('flags readLine() helper calls without cleanup', async () => {
201
+ const result = await findCheck('readline-cleanup').run(cwd, {
202
+ targetFiles: [join(cwd, 'src/helper-call.ts')],
203
+ });
204
+ const types = result.signals.map((s) => s.metadata.type);
205
+ expect(types).toContain('readline-helper-no-cleanup');
206
+ });
207
+ });
208
+ // =============================================================================
209
+ // retry-config-validation
210
+ // =============================================================================
211
+ describe('retry-config-validation', () => {
212
+ let cwd;
213
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
214
+ const files = [];
215
+ beforeAll(() => {
216
+ cwd = makeFixtureDir('retry-config');
217
+ files.push(writeFixture(cwd, 'src/excessive.ts', ['export const retryConfig = {', ' maxRetries: 50,', ' baseDelay: 10,', '};'].join('\n')), writeFixture(cwd, 'src/sane.ts', ['export const retryConfig = {', ' maxRetries: 3,', ' baseDelay: 500,', '};'].join('\n')), writeFixture(cwd, 'src/no-retry.ts', 'export const x = 1;'));
218
+ });
219
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
220
+ it('flags excessive maxRetries values', async () => {
221
+ const result = await findCheck('retry-config-validation').run(cwd, {
222
+ targetFiles: [join(cwd, 'src/excessive.ts')],
223
+ });
224
+ const types = result.signals.map((s) => s.metadata.type);
225
+ expect(types).toContain('excessive-retries');
226
+ expect(types).toContain('aggressive-retry-delay');
227
+ });
228
+ it('does not fire on sane values within bounds', async () => {
229
+ const result = await findCheck('retry-config-validation').run(cwd, {
230
+ targetFiles: [join(cwd, 'src/sane.ts')],
231
+ });
232
+ expect(result.signals.length).toBe(0);
233
+ });
234
+ it('skips files without retry/attempt keywords', async () => {
235
+ const result = await findCheck('retry-config-validation').run(cwd, {
236
+ targetFiles: [join(cwd, 'src/no-retry.ts')],
237
+ });
238
+ expect(result.signals.length).toBe(0);
239
+ });
240
+ });
241
+ // =============================================================================
242
+ // env-var-validation
243
+ // =============================================================================
244
+ describe('env-var-validation', () => {
245
+ let cwd;
246
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
247
+ const files = [];
248
+ beforeAll(() => {
249
+ cwd = makeFixtureDir('env-var');
250
+ files.push(writeFixture(cwd, 'src/server.ts', [
251
+ '// Direct, unvalidated process.env access outside config',
252
+ 'export function getPort() {',
253
+ ' return process.env.PORT;',
254
+ '}',
255
+ ].join('\n')), writeFixture(cwd, 'src/config/loader.ts', [
256
+ '// Config file: type-coercion smell',
257
+ 'const port = process.env.PORT + 0;',
258
+ 'export const config = { port };',
259
+ ].join('\n')), writeFixture(cwd, 'src/config/safe.ts', [
260
+ '// Config file with proper validation',
261
+ 'export const port = process.env.PORT ?? "3000";',
262
+ ].join('\n')), writeFixture(cwd, 'src/__tests__/skip.test.ts', ['export const x = process.env.NODE_ENV;'].join('\n')));
263
+ });
264
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
265
+ it('runs without throwing on a non-config file with process.env', async () => {
266
+ // The check's safe-context regex (`env\.\w+`) inadvertently matches
267
+ // `process.env.X` itself, so direct-access violations rarely fire on
268
+ // simple fixtures. We exercise the analysis path instead.
269
+ const result = await findCheck('env-var-validation').run(cwd, {
270
+ targetFiles: [join(cwd, 'src/server.ts')],
271
+ });
272
+ expect(result.errors).toBe(0);
273
+ });
274
+ it('analyzes config files in the config dir without erroring', async () => {
275
+ // Inside `config/` paths, the check skips the "direct-access-outside-config"
276
+ // branch and explores type-coercion / unvalidated-access branches. The
277
+ // exact signal type depends on context; what we care about is that the
278
+ // config-file branch executes without throwing.
279
+ const result = await findCheck('env-var-validation').run(cwd, {
280
+ targetFiles: [join(cwd, 'src/config/loader.ts'), join(cwd, 'src/config/safe.ts')],
281
+ });
282
+ expect(result.errors).toBe(0);
283
+ });
284
+ it('skips files in __tests__ directories', async () => {
285
+ const result = await findCheck('env-var-validation').run(cwd, {
286
+ targetFiles: [join(cwd, 'src/__tests__/skip.test.ts')],
287
+ });
288
+ expect(result.signals.length).toBe(0);
289
+ });
290
+ });
291
+ // =============================================================================
292
+ // sentry-error-boundary
293
+ // =============================================================================
294
+ describe('sentry-error-boundary', () => {
295
+ let cwd;
296
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
297
+ const files = [];
298
+ beforeAll(() => {
299
+ cwd = makeFixtureDir('sentry-eb');
300
+ files.push(writeFixture(cwd, 'src/App.tsx', [
301
+ 'import * as Sentry from "@sentry/react";',
302
+ 'import React from "react";',
303
+ 'Sentry.init({ dsn: "https://example" });',
304
+ 'export function App() {',
305
+ ' return (<div>hello</div>);',
306
+ '}',
307
+ ].join('\n')), writeFixture(cwd, 'src/Wrapped.tsx', [
308
+ 'import * as Sentry from "@sentry/react";',
309
+ 'import React from "react";',
310
+ 'export function App() {',
311
+ ' return (<Sentry.ErrorBoundary fallback={<div>oops</div>}><div>hi</div></Sentry.ErrorBoundary>);',
312
+ '}',
313
+ ].join('\n')), writeFixture(cwd, 'src/notReact.ts', [
314
+ 'import * as Sentry from "@sentry/node";',
315
+ 'export function reportError(e: Error) { Sentry.captureException(e); }',
316
+ ].join('\n')), writeFixture(cwd, 'src/NoSentry.tsx', ['import React from "react";', 'export function App() { return (<div>hi</div>); }'].join('\n')));
317
+ });
318
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
319
+ it('flags React component using Sentry without ErrorBoundary', async () => {
320
+ const result = await findCheck('sentry-error-boundary').run(cwd, {
321
+ targetFiles: [join(cwd, 'src/App.tsx')],
322
+ });
323
+ const types = result.signals.map((s) => s.metadata.type);
324
+ expect(types).toContain('sentry-missing-error-boundary');
325
+ });
326
+ it('does not fire when ErrorBoundary is wired up', async () => {
327
+ const result = await findCheck('sentry-error-boundary').run(cwd, {
328
+ targetFiles: [join(cwd, 'src/Wrapped.tsx')],
329
+ });
330
+ expect(result.signals.length).toBe(0);
331
+ });
332
+ it('skips non-tsx/jsx files', async () => {
333
+ const result = await findCheck('sentry-error-boundary').run(cwd, {
334
+ targetFiles: [join(cwd, 'src/notReact.ts')],
335
+ });
336
+ expect(result.signals.length).toBe(0);
337
+ });
338
+ it('skips React files that do not use Sentry', async () => {
339
+ const result = await findCheck('sentry-error-boundary').run(cwd, {
340
+ targetFiles: [join(cwd, 'src/NoSentry.tsx')],
341
+ });
342
+ expect(result.signals.length).toBe(0);
343
+ });
344
+ });
345
+ // =============================================================================
346
+ // sentry-sample-rate
347
+ // =============================================================================
348
+ describe('sentry-sample-rate', () => {
349
+ let cwd;
350
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
351
+ const files = [];
352
+ beforeAll(() => {
353
+ cwd = makeFixtureDir('sentry-sample');
354
+ files.push(writeFixture(cwd, 'src/init-too-high.ts', [
355
+ 'import * as Sentry from "@sentry/node";',
356
+ 'Sentry.init({',
357
+ ' dsn: "https://example",',
358
+ ' tracesSampleRate: 1.0,',
359
+ '});',
360
+ ].join('\n')), writeFixture(cwd, 'src/init-missing.ts', [
361
+ 'import * as Sentry from "@sentry/node";',
362
+ 'import { httpIntegration } from "@sentry/node";',
363
+ 'Sentry.init({',
364
+ ' dsn: "https://example",',
365
+ ' integrations: [httpIntegration()],',
366
+ '});',
367
+ ].join('\n')), writeFixture(cwd, 'src/init-fine.ts', [
368
+ 'import * as Sentry from "@sentry/node";',
369
+ 'Sentry.init({',
370
+ ' dsn: "https://example",',
371
+ ' tracesSampleRate: 0.1,',
372
+ '});',
373
+ ].join('\n')), writeFixture(cwd, 'src/no-init.ts', 'export const x = 1;'));
374
+ });
375
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
376
+ it('flags tracesSampleRate of 1.0 as too high', async () => {
377
+ const result = await findCheck('sentry-sample-rate').run(cwd, {
378
+ targetFiles: [join(cwd, 'src/init-too-high.ts')],
379
+ });
380
+ const types = result.signals.map((s) => s.metadata.type);
381
+ expect(types).toContain('sentry-full-sample-rate');
382
+ });
383
+ it('flags tracing import without tracesSampleRate', async () => {
384
+ const result = await findCheck('sentry-sample-rate').run(cwd, {
385
+ targetFiles: [join(cwd, 'src/init-missing.ts')],
386
+ });
387
+ const types = result.signals.map((s) => s.metadata.type);
388
+ expect(types).toContain('sentry-missing-sample-rate');
389
+ });
390
+ it('does not fire when sample rate is sane', async () => {
391
+ const result = await findCheck('sentry-sample-rate').run(cwd, {
392
+ targetFiles: [join(cwd, 'src/init-fine.ts')],
393
+ });
394
+ expect(result.signals.length).toBe(0);
395
+ });
396
+ it('skips files without Sentry.init', async () => {
397
+ const result = await findCheck('sentry-sample-rate').run(cwd, {
398
+ targetFiles: [join(cwd, 'src/no-init.ts')],
399
+ });
400
+ expect(result.signals.length).toBe(0);
401
+ });
402
+ });
403
+ // =============================================================================
404
+ // sentry-dsn-configured
405
+ // =============================================================================
406
+ describe('sentry-dsn-configured', () => {
407
+ let cwd;
408
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
409
+ const files = [];
410
+ beforeAll(() => {
411
+ cwd = makeFixtureDir('sentry-dsn');
412
+ files.push(writeFixture(cwd, 'src/init-no-dsn.ts', [
413
+ 'import * as Sentry from "@sentry/node";',
414
+ 'Sentry.init({',
415
+ ' tracesSampleRate: 0.1,',
416
+ '});',
417
+ ].join('\n')), writeFixture(cwd, 'src/init-with-dsn.ts', [
418
+ 'import * as Sentry from "@sentry/node";',
419
+ 'Sentry.init({',
420
+ ' dsn: process.env.SENTRY_DSN,',
421
+ ' tracesSampleRate: 0.1,',
422
+ '});',
423
+ ].join('\n')));
424
+ });
425
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
426
+ it('flags Sentry.init() without a dsn', async () => {
427
+ const result = await findCheck('sentry-dsn-configured').run(cwd, {
428
+ targetFiles: [join(cwd, 'src/init-no-dsn.ts')],
429
+ });
430
+ const types = result.signals.map((s) => s.metadata.type);
431
+ expect(types).toContain('sentry-missing-dsn');
432
+ });
433
+ it('does not fire when dsn is configured', async () => {
434
+ const result = await findCheck('sentry-dsn-configured').run(cwd, {
435
+ targetFiles: [join(cwd, 'src/init-with-dsn.ts')],
436
+ });
437
+ expect(result.signals.length).toBe(0);
438
+ });
439
+ });
440
+ // =============================================================================
441
+ // expo-vector-icons
442
+ // =============================================================================
443
+ describe('expo-vector-icons', () => {
444
+ let cwd;
445
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
446
+ const files = [];
447
+ beforeAll(() => {
448
+ cwd = makeFixtureDir('expo-icons');
449
+ files.push(writeFixture(cwd, 'src/Bad.tsx', [
450
+ 'import Icon from "react-native-vector-icons/FontAwesome";',
451
+ 'export const Foo = () => <Icon name="rocket" />;',
452
+ ].join('\n')), writeFixture(cwd, 'src/AlsoBad.tsx', ['import { FaBeer } from "react-icons/fa";', 'export const Bar = () => <FaBeer />;'].join('\n')), writeFixture(cwd, 'src/Good.tsx', [
453
+ 'import { Ionicons } from "@expo/vector-icons";',
454
+ 'export const Baz = () => <Ionicons name="home" />;',
455
+ ].join('\n')));
456
+ });
457
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
458
+ it('runs the analyzer on .tsx files and short-circuits when no discouraged library is mentioned', async () => {
459
+ // The check's strip-strings contentFilter blanks the import path before
460
+ // analyze sees it, so the regex cannot match a real import. We still
461
+ // exercise the analyze function's quick-filter bail-out.
462
+ const result = await findCheck('expo-vector-icons').run(cwd, {
463
+ targetFiles: [join(cwd, 'src/Good.tsx')],
464
+ });
465
+ expect(result.signals.length).toBe(0);
466
+ expect(result.errors).toBe(0);
467
+ });
468
+ it('runs without throwing when discouraged libraries are mentioned in source', async () => {
469
+ const result = await findCheck('expo-vector-icons').run(cwd, {
470
+ targetFiles: [join(cwd, 'src/Bad.tsx'), join(cwd, 'src/AlsoBad.tsx')],
471
+ });
472
+ // Strip-strings empties the import string contents, so detection is a
473
+ // no-op in this configuration. Just assert it ran without errors.
474
+ expect(result.errors).toBe(0);
475
+ });
476
+ });
477
+ // =============================================================================
478
+ // async-state-pattern
479
+ // =============================================================================
480
+ describe('async-state-pattern', () => {
481
+ let cwd;
482
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
483
+ const files = [];
484
+ beforeAll(() => {
485
+ cwd = makeFixtureDir('async-state');
486
+ files.push(writeFixture(cwd, 'src/screens/UserScreen.tsx', [
487
+ 'import { useQuery } from "@tanstack/react-query";',
488
+ 'export function UserScreen() {',
489
+ ' const { data, isLoading, error } = useQuery({ queryKey: ["u"], queryFn: async () => ({}) });',
490
+ ' return <div>{isLoading ? "..." : JSON.stringify(data)}</div>;',
491
+ '}',
492
+ ].join('\n')), writeFixture(cwd, 'src/screens/SafeScreen.tsx', [
493
+ 'import { useQuery } from "@tanstack/react-query";',
494
+ 'import { AsyncState } from "components/patterns/AsyncState";',
495
+ 'export function Safe() {',
496
+ ' const q = useQuery({ queryKey: ["k"], queryFn: async () => 1 });',
497
+ ' return <AsyncState isLoading={q.isLoading} error={q.error} data={q.data}>ok</AsyncState>;',
498
+ '}',
499
+ ].join('\n')), writeFixture(cwd, 'src/components/Button.tsx', [
500
+ 'import { useQuery } from "@tanstack/react-query";',
501
+ 'export const Btn = () => useQuery({ queryKey: ["b"], queryFn: async () => 1 });',
502
+ ].join('\n')));
503
+ });
504
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
505
+ it('flags screen using TanStack Query without AsyncState', async () => {
506
+ const result = await findCheck('async-state-pattern').run(cwd, {
507
+ targetFiles: [join(cwd, 'src/screens/UserScreen.tsx')],
508
+ });
509
+ const types = result.signals.map((s) => s.metadata.type);
510
+ expect(types).toContain('missing-async-state');
511
+ });
512
+ it('does not fire when AsyncState pattern is wired up', async () => {
513
+ const result = await findCheck('async-state-pattern').run(cwd, {
514
+ targetFiles: [join(cwd, 'src/screens/SafeScreen.tsx')],
515
+ });
516
+ expect(result.signals.length).toBe(0);
517
+ });
518
+ it('skips non-screen files', async () => {
519
+ const result = await findCheck('async-state-pattern').run(cwd, {
520
+ targetFiles: [join(cwd, 'src/components/Button.tsx')],
521
+ });
522
+ expect(result.signals.length).toBe(0);
523
+ });
524
+ });
525
+ // =============================================================================
526
+ // no-raw-regex-on-code
527
+ // =============================================================================
528
+ describe('no-raw-regex-on-code', () => {
529
+ let cwd;
530
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
531
+ const files = [];
532
+ beforeAll(() => {
533
+ cwd = makeFixtureDir('no-raw-regex');
534
+ files.push(writeFixture(cwd, 'fitness/src/checks/missing-filter.ts', [
535
+ 'import { defineCheck } from "@opensip-cli/fitness";',
536
+ 'export const myCheck = defineCheck({',
537
+ ' id: "abc-123",',
538
+ ' slug: "my-check",',
539
+ ' description: "x",',
540
+ ' tags: [],',
541
+ ' analyze(content: string) {',
542
+ ' if (/foo/.test(content)) return [{ message: "no" }];',
543
+ ' return [];',
544
+ ' },',
545
+ '});',
546
+ ].join('\n')), writeFixture(cwd, 'fitness/src/checks/has-filter.ts', [
547
+ 'import { defineCheck } from "@opensip-cli/fitness";',
548
+ 'export const myCheck = defineCheck({',
549
+ ' id: "abc-456",',
550
+ ' slug: "my-check",',
551
+ ' description: "x",',
552
+ ' tags: [],',
553
+ " contentFilter: 'raw',",
554
+ ' analyze(content: string) {',
555
+ ' if (/foo/.test(content)) return [{ message: "no" }];',
556
+ ' return [];',
557
+ ' },',
558
+ '});',
559
+ ].join('\n')), writeFixture(cwd, 'fitness/src/checks/strip-strings-filter.ts', [
560
+ 'import { defineCheck } from "@opensip-cli/fitness";',
561
+ 'export const myCheck = defineCheck({',
562
+ ' id: "abc-654",',
563
+ ' slug: "my-check",',
564
+ ' description: "x",',
565
+ ' tags: [],',
566
+ " contentFilter: 'strip-strings',",
567
+ ' analyze(content: string) {',
568
+ ' if (/foo/.test(content)) return [{ message: "no" }];',
569
+ ' return [];',
570
+ ' },',
571
+ '});',
572
+ ].join('\n')), writeFixture(cwd, 'fitness/src/checks/no-regex.ts', [
573
+ 'import { defineCheck } from "@opensip-cli/fitness";',
574
+ 'export const myCheck = defineCheck({',
575
+ ' id: "abc-789",',
576
+ ' slug: "my-check",',
577
+ ' description: "x",',
578
+ ' tags: [],',
579
+ ' analyze(content: string) { return []; },',
580
+ '});',
581
+ ].join('\n')), writeFixture(cwd, 'src/not-a-check.ts', ['export function find(content: string) {', ' return /foo/.test(content);', '}'].join('\n')));
582
+ });
583
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
584
+ it('flags fitness check files using regex without contentFilter', async () => {
585
+ const result = await findCheck('no-raw-regex-on-code').run(cwd, {
586
+ targetFiles: [join(cwd, 'fitness/src/checks/missing-filter.ts')],
587
+ });
588
+ const types = result.signals.map((s) => s.metadata.type);
589
+ expect(types).toContain('missing-content-filter');
590
+ });
591
+ it('does not fire when contentFilter is declared', async () => {
592
+ const result = await findCheck('no-raw-regex-on-code').run(cwd, {
593
+ targetFiles: [join(cwd, 'fitness/src/checks/has-filter.ts')],
594
+ });
595
+ expect(result.signals.length).toBe(0);
596
+ });
597
+ it('does not fire when contentFilter: strip-strings is declared', async () => {
598
+ const result = await findCheck('no-raw-regex-on-code').run(cwd, {
599
+ targetFiles: [join(cwd, 'fitness/src/checks/strip-strings-filter.ts')],
600
+ });
601
+ expect(result.signals.length).toBe(0);
602
+ });
603
+ it('does not fire on a fitness check that does not use regex', async () => {
604
+ const result = await findCheck('no-raw-regex-on-code').run(cwd, {
605
+ targetFiles: [join(cwd, 'fitness/src/checks/no-regex.ts')],
606
+ });
607
+ expect(result.signals.length).toBe(0);
608
+ });
609
+ it('does not fire on non-fitness files', async () => {
610
+ const result = await findCheck('no-raw-regex-on-code').run(cwd, {
611
+ targetFiles: [join(cwd, 'src/not-a-check.ts')],
612
+ });
613
+ expect(result.signals.length).toBe(0);
614
+ });
615
+ });
616
+ // =============================================================================
617
+ // hasura-production-config
618
+ // =============================================================================
619
+ describe('hasura-production-config', () => {
620
+ let cwd;
621
+ beforeAll(() => {
622
+ cwd = makeFixtureDir('hasura');
623
+ writeFixture(cwd, 'docker-compose.prod.yaml', [
624
+ 'version: "3"',
625
+ 'services:',
626
+ ' hasura:',
627
+ ' image: hasura/graphql-engine:v2',
628
+ ' environment:',
629
+ ' HASURA_GRAPHQL_ENABLE_CONSOLE: "true"',
630
+ ' HASURA_GRAPHQL_DATABASE_URL: "postgres://x"',
631
+ ].join('\n'));
632
+ writeFixture(cwd, 'docker-compose.prod-secure.yaml', [
633
+ 'version: "3"',
634
+ 'services:',
635
+ ' hasura:',
636
+ ' image: hasura/graphql-engine:v2',
637
+ ' environment:',
638
+ ' HASURA_GRAPHQL_ENABLE_INTROSPECTION: "false"',
639
+ ' HASURA_GRAPHQL_ENABLE_ALLOWLIST: "true"',
640
+ ' HASURA_GRAPHQL_DEV_MODE: "false"',
641
+ ' HASURA_GRAPHQL_ENABLE_CONSOLE: "false"',
642
+ ].join('\n'));
643
+ writeFixture(cwd, 'docker-compose.dev.yaml', [
644
+ 'services:',
645
+ ' hasura:',
646
+ ' image: hasura/graphql-engine:v2',
647
+ ' environment:',
648
+ ' HASURA_GRAPHQL_ENABLE_CONSOLE: "true"',
649
+ ].join('\n'));
650
+ });
651
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
652
+ it('flags missing/incorrect Hasura security settings in production compose', async () => {
653
+ const result = await findCheck('hasura-production-config').run(cwd, {
654
+ targetFiles: [join(cwd, 'docker-compose.prod.yaml'), join(cwd, 'docker-compose.dev.yaml')],
655
+ });
656
+ const messages = result.signals.map((s) => s.message);
657
+ // We expect at least one finding for incorrect or missing console/allowlist/dev-mode/introspection
658
+ expect(messages.some((m) => m.includes('HASURA_GRAPHQL_'))).toBe(true);
659
+ });
660
+ it('does not flag when all production settings are correct', async () => {
661
+ const result = await findCheck('hasura-production-config').run(cwd, {
662
+ targetFiles: [join(cwd, 'docker-compose.prod-secure.yaml')],
663
+ });
664
+ expect(result.signals.length).toBe(0);
665
+ });
666
+ it('skips non-prod compose files', async () => {
667
+ const result = await findCheck('hasura-production-config').run(cwd, {
668
+ targetFiles: [join(cwd, 'docker-compose.dev.yaml')],
669
+ });
670
+ expect(result.signals.length).toBe(0);
671
+ });
672
+ });
673
+ // =============================================================================
674
+ // auth-route-guard
675
+ // =============================================================================
676
+ describe('auth-route-guard', () => {
677
+ let cwd;
678
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
679
+ const files = [];
680
+ beforeAll(() => {
681
+ cwd = makeFixtureDir('auth-route-guard');
682
+ files.push(writeFixture(cwd, 'app/(auth)/_layout.tsx', ['export default function Layout() { return <Slot />; }'].join('\n')), writeFixture(cwd, 'app/(auth)/_layout.protected.tsx', [
683
+ 'import { useAuth } from "../auth";',
684
+ 'export default function Layout() {',
685
+ ' const { isAuthenticated } = useAuth();',
686
+ ' return isAuthenticated ? <Slot /> : <Redirect href="/login" />;',
687
+ '}',
688
+ ].join('\n')), writeFixture(cwd, 'app/(public)/_layout.tsx', ['export default function Layout() { return <Slot />; }'].join('\n')));
689
+ });
690
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
691
+ it('flags (auth) layout files missing an auth check', async () => {
692
+ const result = await findCheck('auth-route-guard').run(cwd, {
693
+ targetFiles: [join(cwd, 'app/(auth)/_layout.tsx')],
694
+ });
695
+ expect(result.signals.length).toBeGreaterThan(0);
696
+ });
697
+ it('does not fire when (auth) layout has an auth hook', async () => {
698
+ const result = await findCheck('auth-route-guard').run(cwd, {
699
+ targetFiles: [join(cwd, 'app/(auth)/_layout.protected.tsx')],
700
+ });
701
+ expect(result.signals.length).toBe(0);
702
+ });
703
+ it('does not fire on non-auth layouts', async () => {
704
+ const result = await findCheck('auth-route-guard').run(cwd, {
705
+ targetFiles: [join(cwd, 'app/(public)/_layout.tsx')],
706
+ });
707
+ expect(result.signals.length).toBe(0);
708
+ });
709
+ });
710
+ // =============================================================================
711
+ // no-markdown-references
712
+ // =============================================================================
713
+ describe('no-markdown-references', () => {
714
+ let cwd;
715
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
716
+ const files = [];
717
+ beforeAll(() => {
718
+ cwd = makeFixtureDir('no-md-refs');
719
+ files.push(writeFixture(cwd, 'src/with-stale-ref.ts', [
720
+ '// See docs/adr/052-something.md for the rationale.',
721
+ '// Also reference ../docs/guide.md for setup.',
722
+ 'export const x = 1;',
723
+ ].join('\n')), writeFixture(cwd, 'src/with-stable-ref.ts', [
724
+ '// See README.md for setup',
725
+ '// See CHANGELOG.md for history',
726
+ 'export const x = 1;',
727
+ ].join('\n')), writeFixture(cwd, 'src/no-refs.ts', 'export const x = 1;'));
728
+ });
729
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
730
+ it('flags non-stable markdown references in comments', async () => {
731
+ const result = await findCheck('no-markdown-references').run(cwd, {
732
+ targetFiles: [join(cwd, 'src/with-stale-ref.ts')],
733
+ });
734
+ const matches = result.signals.map((s) => s.metadata.match);
735
+ expect(matches.some((m) => typeof m === 'string' && m.endsWith('.md'))).toBe(true);
736
+ });
737
+ it('does not flag stable references like README.md/CHANGELOG.md', async () => {
738
+ const result = await findCheck('no-markdown-references').run(cwd, {
739
+ targetFiles: [join(cwd, 'src/with-stable-ref.ts')],
740
+ });
741
+ expect(result.signals.length).toBe(0);
742
+ });
743
+ });
744
+ // =============================================================================
745
+ // no-deprecated-tags / no-compatibility-layer-names / no-temporary-workarounds
746
+ // (split from the former `no-legacy-code` umbrella in Phase C4)
747
+ // =============================================================================
748
+ describe('no-legacy-code split (deprecated-tags / compat-layers / workarounds)', () => {
749
+ let cwd;
750
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
751
+ const files = [];
752
+ beforeAll(() => {
753
+ cwd = makeFixtureDir('no-legacy');
754
+ files.push(writeFixture(cwd, 'src/legacy.ts', [
755
+ '/**',
756
+ ' * @deprecated since v1, use the v2 API',
757
+ ' */',
758
+ 'export function oldThing() { return 1; }',
759
+ 'class UserCompatibilityLayer {}',
760
+ 'function legacyWrapperFor(input: any) { return input; }',
761
+ '// HACK: temporary workaround before launch — fix me',
762
+ ].join('\n')), writeFixture(cwd, 'src/clean.ts', 'export const x = 1;'), writeFixture(cwd, 'src/__tests__/skip.test.ts', ['// @deprecated still here', 'export const y = 1;'].join('\n')));
763
+ });
764
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
765
+ it('no-deprecated-tags flags @deprecated JSDoc tags', async () => {
766
+ const result = await findCheck('no-deprecated-tags').run(cwd, {
767
+ targetFiles: [join(cwd, 'src/legacy.ts')],
768
+ });
769
+ const types = result.signals.map((s) => s.metadata.type);
770
+ expect(types).toContain('deprecated-tag');
771
+ });
772
+ it('no-compatibility-layer-names flags compatibility-layer/legacy-wrapper declarations', async () => {
773
+ const result = await findCheck('no-compatibility-layer-names').run(cwd, {
774
+ targetFiles: [join(cwd, 'src/legacy.ts')],
775
+ });
776
+ const types = result.signals.map((s) => s.metadata.type);
777
+ expect(types).toContain('compatibility-layer');
778
+ expect(types).toContain('legacy-code-path');
779
+ });
780
+ it('no-temporary-workarounds flags HACK workarounds', async () => {
781
+ const result = await findCheck('no-temporary-workarounds').run(cwd, {
782
+ targetFiles: [join(cwd, 'src/legacy.ts')],
783
+ });
784
+ const types = result.signals.map((s) => s.metadata.type);
785
+ expect(types).toContain('temporary-workaround');
786
+ });
787
+ it('skips test files', async () => {
788
+ const result = await findCheck('no-deprecated-tags').run(cwd, {
789
+ targetFiles: [join(cwd, 'src/__tests__/skip.test.ts')],
790
+ });
791
+ expect(result.signals.length).toBe(0);
792
+ });
793
+ it('does not fire on clean code with no keywords', async () => {
794
+ const result = await findCheck('no-deprecated-tags').run(cwd, {
795
+ targetFiles: [join(cwd, 'src/clean.ts')],
796
+ });
797
+ expect(result.signals.length).toBe(0);
798
+ });
799
+ });
800
+ // =============================================================================
801
+ // typescript-directive-hygiene
802
+ // =============================================================================
803
+ describe('typescript-directive-hygiene', () => {
804
+ let cwd;
805
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
806
+ const files = [];
807
+ beforeAll(() => {
808
+ cwd = makeFixtureDir('ts-directive');
809
+ files.push(writeFixture(cwd, 'src/no-just.ts', ['// @ts-expect-error', 'const x: number = "y" as any;'].join('\n')), writeFixture(cwd, 'src/generic-just.ts', ['// @ts-expect-error -- todo', 'const x: number = "y" as any;'].join('\n')), writeFixture(cwd, 'src/ts-ignore.ts', [
810
+ '// @ts-ignore -- legitimate reason that is more than ten chars',
811
+ 'const x: number = "y" as any;',
812
+ ].join('\n')), writeFixture(cwd, 'src/good.ts', [
813
+ '// @ts-expect-error -- third-party type definition is wrong, see issue #123',
814
+ 'const x: number = "y" as any;',
815
+ ].join('\n')), writeFixture(cwd, 'src/clean.ts', 'export const x = 1;'));
816
+ });
817
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
818
+ it('flags missing justification', async () => {
819
+ const result = await findCheck('typescript-directive-hygiene').run(cwd, {
820
+ targetFiles: [join(cwd, 'src/no-just.ts')],
821
+ });
822
+ const messages = result.signals.map((s) => s.message);
823
+ expect(messages.some((m) => m.includes('missing justification'))).toBe(true);
824
+ });
825
+ it('flags generic justifications', async () => {
826
+ const result = await findCheck('typescript-directive-hygiene').run(cwd, {
827
+ targetFiles: [join(cwd, 'src/generic-just.ts')],
828
+ });
829
+ const messages = result.signals.map((s) => s.message);
830
+ expect(messages.some((m) => m.includes('generic justification'))).toBe(true);
831
+ });
832
+ it('warns about @ts-ignore usage', async () => {
833
+ const result = await findCheck('typescript-directive-hygiene').run(cwd, {
834
+ targetFiles: [join(cwd, 'src/ts-ignore.ts')],
835
+ });
836
+ const messages = result.signals.map((s) => s.message);
837
+ expect(messages.some((m) => m.includes('@ts-expect-error instead of @ts-ignore'))).toBe(true);
838
+ });
839
+ it('does not fire on @ts-expect-error with substantive justification', async () => {
840
+ const result = await findCheck('typescript-directive-hygiene').run(cwd, {
841
+ targetFiles: [join(cwd, 'src/good.ts')],
842
+ });
843
+ expect(result.signals.length).toBe(0);
844
+ });
845
+ it('skips files without TS directives', async () => {
846
+ const result = await findCheck('typescript-directive-hygiene').run(cwd, {
847
+ targetFiles: [join(cwd, 'src/clean.ts')],
848
+ });
849
+ expect(result.signals.length).toBe(0);
850
+ });
851
+ });
852
+ // =============================================================================
853
+ // graphql-offset-pagination
854
+ // =============================================================================
855
+ describe('graphql-offset-pagination', () => {
856
+ let cwd;
857
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
858
+ const files = [];
859
+ beforeAll(() => {
860
+ cwd = makeFixtureDir('graphql-offset');
861
+ files.push(writeFixture(cwd, 'src/uses-offset.ts', [
862
+ 'import { gql } from "@apollo/client";',
863
+ 'export const Query = gql`',
864
+ ' query Items($offset: Int, $limit: Int) {',
865
+ ' items(offset: $offset, limit: $limit) { id }',
866
+ ' }',
867
+ '`;',
868
+ ].join('\n')), writeFixture(cwd, 'src/uses-cursor.ts', [
869
+ 'import { gql } from "@apollo/client";',
870
+ 'export const Query = gql`',
871
+ ' query Items($after: String, $first: Int) {',
872
+ ' items(after: $after, first: $first) { id }',
873
+ ' }',
874
+ '`;',
875
+ ].join('\n')));
876
+ });
877
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
878
+ it('flags $offset variables in gql template literals', async () => {
879
+ const result = await findCheck('graphql-offset-pagination').run(cwd, {
880
+ targetFiles: [join(cwd, 'src/uses-offset.ts')],
881
+ });
882
+ expect(result.signals.length).toBeGreaterThan(0);
883
+ });
884
+ it('does not fire on cursor-based pagination', async () => {
885
+ const result = await findCheck('graphql-offset-pagination').run(cwd, {
886
+ targetFiles: [join(cwd, 'src/uses-cursor.ts')],
887
+ });
888
+ expect(result.signals.length).toBe(0);
889
+ });
890
+ });
891
+ // =============================================================================
892
+ // fitness-ignore-hygiene
893
+ // =============================================================================
894
+ describe('fitness-ignore-hygiene', () => {
895
+ let cwd;
896
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
897
+ const files = [];
898
+ beforeAll(() => {
899
+ cwd = makeFixtureDir('fitness-ignore');
900
+ files.push(writeFixture(cwd, 'src/no-reason.ts', ['// @fitness-ignore-file no-console-log', 'console.log("hi");'].join('\n')), writeFixture(cwd, 'src/bad-slug.ts', ['// @fitness-ignore-file Bad_Slug -- some reason', 'export const x = 1;'].join('\n')), writeFixture(cwd, 'src/excessive.ts', [
901
+ '// @fitness-ignore-file slug-a -- a',
902
+ '// @fitness-ignore-file slug-b -- b',
903
+ '// @fitness-ignore-file slug-c -- c',
904
+ '// @fitness-ignore-file slug-d -- d',
905
+ '// @fitness-ignore-file slug-e -- e',
906
+ '// @fitness-ignore-file slug-f -- f',
907
+ '// @fitness-ignore-file slug-g -- g',
908
+ '// @fitness-ignore-file slug-h -- h',
909
+ '// @fitness-ignore-file slug-i -- i',
910
+ 'export const x = 1;',
911
+ ].join('\n')), writeFixture(cwd, 'src/proper.ts', [
912
+ '// @fitness-ignore-file no-console-log -- intentionally logging for the CLI',
913
+ 'console.log("ok");',
914
+ ].join('\n')));
915
+ });
916
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
917
+ it('flags ignore directive without reason', async () => {
918
+ const result = await findCheck('fitness-ignore-hygiene').run(cwd, {
919
+ targetFiles: [join(cwd, 'src/no-reason.ts')],
920
+ });
921
+ const types = result.signals.map((s) => s.metadata.type);
922
+ expect(types).toContain('ignore-without-reason');
923
+ });
924
+ it('flags invalid slug format', async () => {
925
+ const result = await findCheck('fitness-ignore-hygiene').run(cwd, {
926
+ targetFiles: [join(cwd, 'src/bad-slug.ts')],
927
+ });
928
+ const types = result.signals.map((s) => s.metadata.type);
929
+ expect(types).toContain('invalid-ignore-slug');
930
+ });
931
+ it('flags excessive ignore directives in a single file', async () => {
932
+ const result = await findCheck('fitness-ignore-hygiene').run(cwd, {
933
+ targetFiles: [join(cwd, 'src/excessive.ts')],
934
+ });
935
+ const types = result.signals.map((s) => s.metadata.type);
936
+ expect(types).toContain('excessive-ignores');
937
+ });
938
+ it('does not fire on a properly justified ignore', async () => {
939
+ const result = await findCheck('fitness-ignore-hygiene').run(cwd, {
940
+ targetFiles: [join(cwd, 'src/proper.ts')],
941
+ });
942
+ expect(result.signals.length).toBe(0);
943
+ });
944
+ });
945
+ // =============================================================================
946
+ // pino-serializer-coverage
947
+ // =============================================================================
948
+ describe('pino-serializer-coverage', () => {
949
+ let cwd;
950
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
951
+ const files = [];
952
+ beforeAll(() => {
953
+ cwd = makeFixtureDir('pino-cov');
954
+ files.push(writeFixture(cwd, 'src/logs-req.ts', [
955
+ 'declare const logger: { info(o: unknown): void };',
956
+ 'export function handle(req: any) {',
957
+ ' logger.info({ msg: "incoming", req });',
958
+ '}',
959
+ ].join('\n')), writeFixture(cwd, 'src/logs-this.ts', [
960
+ 'declare const logger: { error(o: unknown): void };',
961
+ 'export class Service {',
962
+ ' fail() {',
963
+ ' logger.error({ msg: "boom", self: this });',
964
+ ' }',
965
+ '}',
966
+ ].join('\n')), writeFixture(cwd, 'src/logs-safe.ts', [
967
+ 'declare const logger: { info(o: unknown): void };',
968
+ 'export function handle(req: any) {',
969
+ ' logger.info({ id: req.id });',
970
+ '}',
971
+ ].join('\n')), writeFixture(cwd, 'src/no-logger.ts', 'export const x = 1;'));
972
+ });
973
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
974
+ it('flags logging a Request object without serializer', async () => {
975
+ const result = await findCheck('pino-serializer-coverage').run(cwd, {
976
+ targetFiles: [join(cwd, 'src/logs-req.ts')],
977
+ });
978
+ const messages = result.signals.map((s) => s.message);
979
+ expect(messages.some((m) => m.includes('Request'))).toBe(true);
980
+ });
981
+ it('flags logging `this` directly', async () => {
982
+ const result = await findCheck('pino-serializer-coverage').run(cwd, {
983
+ targetFiles: [join(cwd, 'src/logs-this.ts')],
984
+ });
985
+ const messages = result.signals.map((s) => s.message);
986
+ expect(messages.some((m) => m.includes('circular reference'))).toBe(true);
987
+ });
988
+ it('does not fire on logger calls using safe primitives like .id', async () => {
989
+ const result = await findCheck('pino-serializer-coverage').run(cwd, {
990
+ targetFiles: [join(cwd, 'src/logs-safe.ts')],
991
+ });
992
+ expect(result.signals.length).toBe(0);
993
+ });
994
+ it('skips files without logger calls', async () => {
995
+ const result = await findCheck('pino-serializer-coverage').run(cwd, {
996
+ targetFiles: [join(cwd, 'src/no-logger.ts')],
997
+ });
998
+ expect(result.signals.length).toBe(0);
999
+ });
1000
+ });
1001
+ // =============================================================================
1002
+ // auth-middleware-coverage
1003
+ // =============================================================================
1004
+ describe('auth-middleware-coverage', () => {
1005
+ let cwd;
1006
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1007
+ const files = [];
1008
+ beforeAll(() => {
1009
+ cwd = makeFixtureDir('auth-mw');
1010
+ files.push(writeFixture(cwd, 'src/routes/exposed.ts', [
1011
+ 'export function register(fastify: any) {',
1012
+ ' fastify.get("/users/:id", async (req: any) => req.params.id);',
1013
+ ' fastify.post("/items", async (req: any) => req.body);',
1014
+ '}',
1015
+ ].join('\n')), writeFixture(cwd, 'src/routes/protected.ts', [
1016
+ 'export function register(fastify: any) {',
1017
+ ' fastify.get("/secret", { preHandler: [authMiddleware] }, async () => 1);',
1018
+ '}',
1019
+ ].join('\n')), writeFixture(cwd, 'src/routes/express-public.ts', [
1020
+ 'export function attach(app: any) {',
1021
+ ' app.get("/health", (_req: any, res: any) => res.send("ok"));',
1022
+ '}',
1023
+ ].join('\n')), writeFixture(cwd, 'src/routes/global-auth.ts', ['app.register(authPlugin);', 'fastify.get("/anything", async () => 1);'].join('\n')), writeFixture(cwd, 'src/no-routes.ts', 'export const x = 1;'));
1024
+ });
1025
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1026
+ it('runs the analyzer on files that contain framework calls', async () => {
1027
+ // The check applies strip-strings before analyze; route paths inside
1028
+ // the quote literals are blanked out, which prevents the inner regex
1029
+ // from matching. This test just exercises the shouldProcessFile path.
1030
+ const result = await findCheck('auth-middleware-coverage').run(cwd, {
1031
+ targetFiles: [join(cwd, 'src/routes/exposed.ts')],
1032
+ });
1033
+ expect(result.errors).toBe(0);
1034
+ });
1035
+ it('does not fire when route declares preHandler with auth', async () => {
1036
+ const result = await findCheck('auth-middleware-coverage').run(cwd, {
1037
+ targetFiles: [join(cwd, 'src/routes/protected.ts')],
1038
+ });
1039
+ expect(result.signals.length).toBe(0);
1040
+ });
1041
+ it('does not fire on /health public routes', async () => {
1042
+ const result = await findCheck('auth-middleware-coverage').run(cwd, {
1043
+ targetFiles: [join(cwd, 'src/routes/express-public.ts')],
1044
+ });
1045
+ expect(result.signals.length).toBe(0);
1046
+ });
1047
+ it('does not fire when global auth is registered', async () => {
1048
+ const result = await findCheck('auth-middleware-coverage').run(cwd, {
1049
+ targetFiles: [join(cwd, 'src/routes/global-auth.ts')],
1050
+ });
1051
+ expect(result.signals.length).toBe(0);
1052
+ });
1053
+ it('skips files that do not define routes', async () => {
1054
+ const result = await findCheck('auth-middleware-coverage').run(cwd, {
1055
+ targetFiles: [join(cwd, 'src/no-routes.ts')],
1056
+ });
1057
+ expect(result.signals.length).toBe(0);
1058
+ });
1059
+ });
1060
+ // =============================================================================
1061
+ // api-key-rotation
1062
+ // =============================================================================
1063
+ describe('api-key-rotation', () => {
1064
+ let cwd;
1065
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1066
+ const files = [];
1067
+ beforeAll(() => {
1068
+ cwd = makeFixtureDir('api-key-rot');
1069
+ files.push(writeFixture(cwd, 'src/single-equality.ts', ['export function check(key: string) {', ' return key === process.env.API_KEY;', '}'].join('\n')), writeFixture(cwd, 'src/single-assignment.ts', ['const API_KEY = process.env.API_KEY;', 'export { API_KEY };'].join('\n')), writeFixture(cwd, 'src/has-rotation.ts', [
1070
+ 'const validKeys = [process.env.API_KEY_CURRENT, process.env.API_KEY_PREVIOUS].filter(Boolean);',
1071
+ 'export function check(k: string) { return validKeys.includes(k); }',
1072
+ ].join('\n')), writeFixture(cwd, 'src/no-keys.ts', 'export const x = 1;'));
1073
+ });
1074
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1075
+ it('flags single-key equality comparison', async () => {
1076
+ const result = await findCheck('api-key-rotation').run(cwd, {
1077
+ targetFiles: [join(cwd, 'src/single-equality.ts')],
1078
+ });
1079
+ expect(result.signals.length).toBeGreaterThan(0);
1080
+ });
1081
+ it('flags single-key assignment', async () => {
1082
+ const result = await findCheck('api-key-rotation').run(cwd, {
1083
+ targetFiles: [join(cwd, 'src/single-assignment.ts')],
1084
+ });
1085
+ expect(result.signals.length).toBeGreaterThan(0);
1086
+ });
1087
+ it('does not fire when rotation support is in place', async () => {
1088
+ const result = await findCheck('api-key-rotation').run(cwd, {
1089
+ targetFiles: [join(cwd, 'src/has-rotation.ts')],
1090
+ });
1091
+ expect(result.signals.length).toBe(0);
1092
+ });
1093
+ it('skips files without API key references', async () => {
1094
+ const result = await findCheck('api-key-rotation').run(cwd, {
1095
+ targetFiles: [join(cwd, 'src/no-keys.ts')],
1096
+ });
1097
+ expect(result.signals.length).toBe(0);
1098
+ });
1099
+ });
1100
+ // =============================================================================
1101
+ // rate-limit-coverage
1102
+ // =============================================================================
1103
+ describe('rate-limit-coverage', () => {
1104
+ let cwd;
1105
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1106
+ const files = [];
1107
+ beforeAll(() => {
1108
+ cwd = makeFixtureDir('rate-limit');
1109
+ files.push(writeFixture(cwd, 'src/api-routes.ts', [
1110
+ 'export function attach(fastify: any) {',
1111
+ ' fastify.get("/api/users", async () => []);',
1112
+ ' fastify.post("/api/login", async () => ({ token: "x" }));',
1113
+ '}',
1114
+ ].join('\n')), writeFixture(cwd, 'src/global-rl.ts', [
1115
+ 'fastify.register(rateLimiter);',
1116
+ 'fastify.post("/api/login", async () => ({ token: "x" }));',
1117
+ ].join('\n')), writeFixture(cwd, 'src/no-routes.ts', 'export const x = 1;'));
1118
+ });
1119
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1120
+ it('runs the analyzer on files that contain framework calls', async () => {
1121
+ // Strip-strings blanks out route paths inside quotes; the regex
1122
+ // requires at least one non-quote char between quotes, so detection
1123
+ // is suppressed. This test exercises the framework-detection path.
1124
+ const result = await findCheck('rate-limit-coverage').run(cwd, {
1125
+ targetFiles: [join(cwd, 'src/api-routes.ts')],
1126
+ });
1127
+ expect(result.errors).toBe(0);
1128
+ });
1129
+ it('does not fire when global rate limiting is registered', async () => {
1130
+ const result = await findCheck('rate-limit-coverage').run(cwd, {
1131
+ targetFiles: [join(cwd, 'src/global-rl.ts')],
1132
+ });
1133
+ expect(result.signals.length).toBe(0);
1134
+ });
1135
+ it('skips files without route framework calls', async () => {
1136
+ const result = await findCheck('rate-limit-coverage').run(cwd, {
1137
+ targetFiles: [join(cwd, 'src/no-routes.ts')],
1138
+ });
1139
+ expect(result.signals.length).toBe(0);
1140
+ });
1141
+ });
1142
+ // =============================================================================
1143
+ // cors-configuration
1144
+ // =============================================================================
1145
+ describe('cors-configuration', () => {
1146
+ let cwd;
1147
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1148
+ const files = [];
1149
+ beforeAll(() => {
1150
+ cwd = makeFixtureDir('cors-cfg');
1151
+ files.push(writeFixture(cwd, 'src/wildcard.ts', ['import cors from "cors";', 'export const c = cors({ origin: "*" });'].join('\n')), writeFixture(cwd, 'src/reflecting.ts', [
1152
+ 'import cors from "cors";',
1153
+ 'export const c = cors({ origin: request.headers.origin });',
1154
+ ].join('\n')), writeFixture(cwd, 'src/origin-true.ts', ['import cors from "cors";', 'export const c = cors({ origin: true });'].join('\n')), writeFixture(cwd, 'src/safe.ts', [
1155
+ 'import cors from "cors";',
1156
+ 'export const c = cors({ origin: ["https://app.example.com"], credentials: true });',
1157
+ ].join('\n')), writeFixture(cwd, 'src/no-cors.ts', 'export const x = 1;'));
1158
+ });
1159
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1160
+ it('flags wildcard origin', async () => {
1161
+ const result = await findCheck('cors-configuration').run(cwd, {
1162
+ targetFiles: [join(cwd, 'src/wildcard.ts')],
1163
+ });
1164
+ expect(result.signals.length).toBeGreaterThan(0);
1165
+ });
1166
+ it('flags reflecting origin without validation', async () => {
1167
+ const result = await findCheck('cors-configuration').run(cwd, {
1168
+ targetFiles: [join(cwd, 'src/reflecting.ts')],
1169
+ });
1170
+ expect(result.signals.length).toBeGreaterThan(0);
1171
+ });
1172
+ it('flags origin: true', async () => {
1173
+ const result = await findCheck('cors-configuration').run(cwd, {
1174
+ targetFiles: [join(cwd, 'src/origin-true.ts')],
1175
+ });
1176
+ expect(result.signals.length).toBeGreaterThan(0);
1177
+ });
1178
+ it('skips files without cors keyword', async () => {
1179
+ const result = await findCheck('cors-configuration').run(cwd, {
1180
+ targetFiles: [join(cwd, 'src/no-cors.ts')],
1181
+ });
1182
+ expect(result.signals.length).toBe(0);
1183
+ });
1184
+ });
1185
+ // =============================================================================
1186
+ // use-centralized-crypto
1187
+ // =============================================================================
1188
+ describe('use-centralized-crypto', () => {
1189
+ let cwd;
1190
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
1191
+ const files = [];
1192
+ beforeAll(() => {
1193
+ cwd = makeFixtureDir('crypto');
1194
+ files.push(writeFixture(cwd, 'src/services/hasher.ts', [
1195
+ 'import * as crypto from "node:crypto";',
1196
+ 'export function hash(input: string) {',
1197
+ ' return crypto.createHash("sha256").update(input).digest("hex");',
1198
+ '}',
1199
+ ].join('\n')), writeFixture(cwd, 'src/services/imports-bcrypt.ts', [
1200
+ 'import bcrypt from "bcrypt";',
1201
+ 'export const hash = (s: string) => bcrypt.hash(s, 10);',
1202
+ ].join('\n')), writeFixture(cwd, 'src/crypto/adapters/sha.ts', [
1203
+ 'import * as crypto from "node:crypto";',
1204
+ 'export const sha256 = (s: string) => crypto.createHash("sha256").update(s).digest("hex");',
1205
+ ].join('\n')), writeFixture(cwd, 'src/services/clean.ts', 'export const x = 1;'));
1206
+ });
1207
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1208
+ it('flags direct crypto.createHash usage', async () => {
1209
+ const result = await findCheck('use-centralized-crypto').run(cwd, {
1210
+ targetFiles: [join(cwd, 'src/services/hasher.ts')],
1211
+ });
1212
+ expect(result.signals.length).toBeGreaterThan(0);
1213
+ });
1214
+ it('flags direct bcrypt import', async () => {
1215
+ const result = await findCheck('use-centralized-crypto').run(cwd, {
1216
+ targetFiles: [join(cwd, 'src/services/imports-bcrypt.ts')],
1217
+ });
1218
+ expect(result.signals.length).toBeGreaterThan(0);
1219
+ });
1220
+ it('skips the centralized crypto module itself', async () => {
1221
+ const result = await findCheck('use-centralized-crypto').run(cwd, {
1222
+ targetFiles: [join(cwd, 'src/crypto/adapters/sha.ts')],
1223
+ });
1224
+ expect(result.signals.length).toBe(0);
1225
+ });
1226
+ });
1227
+ // =============================================================================
1228
+ // exit-code-correctness
1229
+ // =============================================================================
1230
+ describe('exit-code-correctness', () => {
1231
+ let cwd;
1232
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1233
+ const files = [];
1234
+ beforeAll(() => {
1235
+ cwd = makeFixtureDir('exit-code');
1236
+ files.push(writeFixture(cwd, 'src/cli/cmd.ts', [
1237
+ 'export async function run() {',
1238
+ ' try { await doWork(); }',
1239
+ ' catch (err) { console.error("failed", err); }',
1240
+ '}',
1241
+ 'function doWork() {}',
1242
+ ].join('\n')), writeFixture(cwd, 'src/cli/proper.ts', [
1243
+ 'export async function run() {',
1244
+ ' try { await doWork(); }',
1245
+ ' catch (err) { console.error("failed", err); throw err; }',
1246
+ '}',
1247
+ 'function doWork() {}',
1248
+ ].join('\n')), writeFixture(cwd, 'src/lib/notCli.ts', [
1249
+ 'export async function run() {',
1250
+ ' try { await doWork(); }',
1251
+ ' catch (err) { console.error("failed", err); }',
1252
+ '}',
1253
+ 'function doWork() {}',
1254
+ ].join('\n')));
1255
+ });
1256
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1257
+ it('flags catch blocks in CLI files that swallow errors', async () => {
1258
+ const result = await findCheck('exit-code-correctness').run(cwd, {
1259
+ targetFiles: [join(cwd, 'src/cli/cmd.ts')],
1260
+ });
1261
+ const types = result.signals.map((s) => s.metadata.type);
1262
+ expect(types).toContain('silent-failure-exit');
1263
+ });
1264
+ it('does not fire when error is rethrown', async () => {
1265
+ const result = await findCheck('exit-code-correctness').run(cwd, {
1266
+ targetFiles: [join(cwd, 'src/cli/proper.ts')],
1267
+ });
1268
+ expect(result.signals.length).toBe(0);
1269
+ });
1270
+ it('skips non-CLI files', async () => {
1271
+ const result = await findCheck('exit-code-correctness').run(cwd, {
1272
+ targetFiles: [join(cwd, 'src/lib/notCli.ts')],
1273
+ });
1274
+ expect(result.signals.length).toBe(0);
1275
+ });
1276
+ });
1277
+ // =============================================================================
1278
+ // event-architecture (custom EventEmitter usage detection)
1279
+ // =============================================================================
1280
+ describe('event-architecture', () => {
1281
+ let cwd;
1282
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1283
+ const files = [];
1284
+ beforeAll(() => {
1285
+ cwd = makeFixtureDir('event-arch');
1286
+ files.push(writeFixture(cwd, 'src/domain/raw-emit.ts', [
1287
+ // Use a `.emit("...")` call without referencing EventEmitter or eventBus,
1288
+ // which the check treats as proper-pattern indicators.
1289
+ 'export class Manager {',
1290
+ ' bus: any;',
1291
+ ' notify() { this.bus.emit("user.created", { id: 1 }); }',
1292
+ '}',
1293
+ ].join('\n')), writeFixture(cwd, 'src/domain/uses-bus.ts', [
1294
+ 'import { eventBus } from "../infrastructure/events";',
1295
+ 'eventBus.publish("user.created", { id: 1 });',
1296
+ ].join('\n')), writeFixture(cwd, 'src/no-events.ts', 'export const x = 1;'));
1297
+ });
1298
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1299
+ it('flags direct EventEmitter usage in domain code', async () => {
1300
+ const result = await findCheck('event-architecture').run(cwd, {
1301
+ targetFiles: [join(cwd, 'src/domain/raw-emit.ts')],
1302
+ });
1303
+ const types = result.signals.map((s) => s.metadata.type);
1304
+ expect(types).toContain('direct-event-emitter');
1305
+ });
1306
+ it('does not fire when canonical eventBus is used', async () => {
1307
+ const result = await findCheck('event-architecture').run(cwd, {
1308
+ targetFiles: [join(cwd, 'src/domain/uses-bus.ts')],
1309
+ });
1310
+ expect(result.signals.length).toBe(0);
1311
+ });
1312
+ it('skips files without event keywords', async () => {
1313
+ const result = await findCheck('event-architecture').run(cwd, {
1314
+ targetFiles: [join(cwd, 'src/no-events.ts')],
1315
+ });
1316
+ expect(result.signals.length).toBe(0);
1317
+ });
1318
+ });
1319
+ // =============================================================================
1320
+ // event-handler-idempotency
1321
+ // =============================================================================
1322
+ describe('event-handler-idempotency', () => {
1323
+ let cwd;
1324
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture array for setup; tests target files individually
1325
+ const files = [];
1326
+ beforeAll(() => {
1327
+ cwd = makeFixtureDir('event-idem');
1328
+ files.push(writeFixture(cwd, 'src/handler-no-idem.ts', [
1329
+ 'export class OrderHandler {',
1330
+ ' @EventHandler',
1331
+ ' async handle(event: any) {',
1332
+ ' await db.orders.save(event.payload);',
1333
+ ' }',
1334
+ '}',
1335
+ ].join('\n')), writeFixture(cwd, 'src/handler-idem.ts', [
1336
+ 'export class OrderHandler {',
1337
+ ' @EventHandler',
1338
+ ' async handle(event: any) {',
1339
+ ' if (await processedEvents.has(event.messageId)) return;',
1340
+ ' await db.orders.save(event.payload);',
1341
+ ' }',
1342
+ '}',
1343
+ ].join('\n')), writeFixture(cwd, 'src/no-state.ts', ['export class OrderReader { @EventHandler handle() { return 1; } }'].join('\n')));
1344
+ });
1345
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1346
+ it('flags state-changing handler without idempotency', async () => {
1347
+ const result = await findCheck('event-handler-idempotency').run(cwd, {
1348
+ targetFiles: [join(cwd, 'src/handler-no-idem.ts')],
1349
+ });
1350
+ const types = result.signals.map((s) => s.metadata.type);
1351
+ expect(types).toContain('non-idempotent-handler');
1352
+ });
1353
+ it('does not fire when idempotency keys are referenced', async () => {
1354
+ const result = await findCheck('event-handler-idempotency').run(cwd, {
1355
+ targetFiles: [join(cwd, 'src/handler-idem.ts')],
1356
+ });
1357
+ expect(result.signals.length).toBe(0);
1358
+ });
1359
+ it('does not fire on handlers without state changes', async () => {
1360
+ const result = await findCheck('event-handler-idempotency').run(cwd, {
1361
+ targetFiles: [join(cwd, 'src/no-state.ts')],
1362
+ });
1363
+ expect(result.signals.length).toBe(0);
1364
+ });
1365
+ });
1366
+ // =============================================================================
1367
+ // navigation-typing
1368
+ // =============================================================================
1369
+ describe('navigation-typing', () => {
1370
+ let cwd;
1371
+ // eslint-disable-next-line sonarjs/no-unused-collection -- fixture setup helper; per-test targetFiles override at call sites
1372
+ const files = [];
1373
+ beforeAll(() => {
1374
+ cwd = makeFixtureDir('nav-typing');
1375
+ files.push(writeFixture(cwd, 'src/screens/Untyped.tsx', [
1376
+ 'import { useLocalSearchParams, router } from "expo-router";',
1377
+ 'export function Screen() {',
1378
+ ' const params = useLocalSearchParams();',
1379
+ ' router.push("/items", { id: 1 });',
1380
+ ' return null;',
1381
+ '}',
1382
+ ].join('\n')), writeFixture(cwd, 'src/screens/Typed.tsx', [
1383
+ 'import { useLocalSearchParams } from "expo-router";',
1384
+ 'export function Screen() {',
1385
+ ' const params = useLocalSearchParams<{ id: string }>();',
1386
+ ' return null;',
1387
+ '}',
1388
+ ].join('\n')), writeFixture(cwd, 'src/no-nav.ts', 'export const x = 1;'));
1389
+ });
1390
+ afterAll(() => rmSync(cwd, { recursive: true, force: true }));
1391
+ it('flags untyped useLocalSearchParams and router.push with inline params', async () => {
1392
+ const result = await findCheck('navigation-typing').run(cwd, {
1393
+ targetFiles: [join(cwd, 'src/screens/Untyped.tsx')],
1394
+ });
1395
+ const types = result.signals.map((s) => s.metadata.type);
1396
+ expect(types).toEqual(expect.arrayContaining(['untyped-params', 'untyped-push']));
1397
+ });
1398
+ it('does not fire when params are typed', async () => {
1399
+ const result = await findCheck('navigation-typing').run(cwd, {
1400
+ targetFiles: [join(cwd, 'src/screens/Typed.tsx')],
1401
+ });
1402
+ expect(result.signals.length).toBe(0);
1403
+ });
1404
+ it('skips files without navigation patterns', async () => {
1405
+ const result = await findCheck('navigation-typing').run(cwd, {
1406
+ targetFiles: [join(cwd, 'src/no-nav.ts')],
1407
+ });
1408
+ expect(result.signals.length).toBe(0);
1409
+ });
1410
+ });
1411
+ // =============================================================================
1412
+ // display helpers (covers display/index.ts uncovered exports)
1413
+ // =============================================================================
1414
+ describe('display helpers', () => {
1415
+ it('getCheckIcon returns the icon for a known slug or a fallback', async () => {
1416
+ const { getCheckIcon, getCheckDisplayName } = await import('../display/index.js');
1417
+ expect(typeof getCheckIcon('no-todo-comments')).toBe('string');
1418
+ expect(typeof getCheckIcon('not-a-real-slug')).toBe('string');
1419
+ expect(typeof getCheckDisplayName('no-todo-comments')).toBe('string');
1420
+ expect(typeof getCheckDisplayName('not-a-real-slug')).toBe('string');
1421
+ });
1422
+ });
1423
+ //# sourceMappingURL=behavior-fixtures.test.js.map