@opensip-cli/checks-typescript 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 (404) 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 +12 -0
  5. package/dist/__tests__/all-checks-execute.test.d.ts.map +1 -0
  6. package/dist/__tests__/all-checks-execute.test.js +846 -0
  7. package/dist/__tests__/all-checks-execute.test.js.map +1 -0
  8. package/dist/__tests__/behavior-fixtures-2.test.d.ts +9 -0
  9. package/dist/__tests__/behavior-fixtures-2.test.d.ts.map +1 -0
  10. package/dist/__tests__/behavior-fixtures-2.test.js +625 -0
  11. package/dist/__tests__/behavior-fixtures-2.test.js.map +1 -0
  12. package/dist/__tests__/behavior-fixtures-3.test.d.ts +7 -0
  13. package/dist/__tests__/behavior-fixtures-3.test.d.ts.map +1 -0
  14. package/dist/__tests__/behavior-fixtures-3.test.js +658 -0
  15. package/dist/__tests__/behavior-fixtures-3.test.js.map +1 -0
  16. package/dist/__tests__/behavior-fixtures-4.test.d.ts +8 -0
  17. package/dist/__tests__/behavior-fixtures-4.test.d.ts.map +1 -0
  18. package/dist/__tests__/behavior-fixtures-4.test.js +590 -0
  19. package/dist/__tests__/behavior-fixtures-4.test.js.map +1 -0
  20. package/dist/__tests__/behavior-fixtures-5.test.d.ts +7 -0
  21. package/dist/__tests__/behavior-fixtures-5.test.d.ts.map +1 -0
  22. package/dist/__tests__/behavior-fixtures-5.test.js +548 -0
  23. package/dist/__tests__/behavior-fixtures-5.test.js.map +1 -0
  24. package/dist/__tests__/behavior-fixtures-6.test.d.ts +18 -0
  25. package/dist/__tests__/behavior-fixtures-6.test.d.ts.map +1 -0
  26. package/dist/__tests__/behavior-fixtures-6.test.js +1700 -0
  27. package/dist/__tests__/behavior-fixtures-6.test.js.map +1 -0
  28. package/dist/__tests__/behavior-fixtures.test.d.ts +10 -0
  29. package/dist/__tests__/behavior-fixtures.test.d.ts.map +1 -0
  30. package/dist/__tests__/behavior-fixtures.test.js +812 -0
  31. package/dist/__tests__/behavior-fixtures.test.js.map +1 -0
  32. package/dist/__tests__/branch-fixtures-2.test.d.ts +6 -0
  33. package/dist/__tests__/branch-fixtures-2.test.d.ts.map +1 -0
  34. package/dist/__tests__/branch-fixtures-2.test.js +1369 -0
  35. package/dist/__tests__/branch-fixtures-2.test.js.map +1 -0
  36. package/dist/__tests__/branch-fixtures-3.test.d.ts +7 -0
  37. package/dist/__tests__/branch-fixtures-3.test.d.ts.map +1 -0
  38. package/dist/__tests__/branch-fixtures-3.test.js +877 -0
  39. package/dist/__tests__/branch-fixtures-3.test.js.map +1 -0
  40. package/dist/__tests__/branch-fixtures.test.d.ts +6 -0
  41. package/dist/__tests__/branch-fixtures.test.d.ts.map +1 -0
  42. package/dist/__tests__/branch-fixtures.test.js +1072 -0
  43. package/dist/__tests__/branch-fixtures.test.js.map +1 -0
  44. package/dist/__tests__/checks.test.d.ts +2 -0
  45. package/dist/__tests__/checks.test.d.ts.map +1 -0
  46. package/dist/__tests__/checks.test.js +39 -0
  47. package/dist/__tests__/checks.test.js.map +1 -0
  48. package/dist/__tests__/fixture-coverage.allowlist.d.ts +19 -0
  49. package/dist/__tests__/fixture-coverage.allowlist.d.ts.map +1 -0
  50. package/dist/__tests__/fixture-coverage.allowlist.js +27 -0
  51. package/dist/__tests__/fixture-coverage.allowlist.js.map +1 -0
  52. package/dist/__tests__/fixture-coverage.test.d.ts +13 -0
  53. package/dist/__tests__/fixture-coverage.test.d.ts.map +1 -0
  54. package/dist/__tests__/fixture-coverage.test.js +57 -0
  55. package/dist/__tests__/fixture-coverage.test.js.map +1 -0
  56. package/dist/__tests__/no-bootstrap-tool-import.test.d.ts +2 -0
  57. package/dist/__tests__/no-bootstrap-tool-import.test.d.ts.map +1 -0
  58. package/dist/__tests__/no-bootstrap-tool-import.test.js +75 -0
  59. package/dist/__tests__/no-bootstrap-tool-import.test.js.map +1 -0
  60. package/dist/__tests__/phantom-dependency-detection.test.d.ts +12 -0
  61. package/dist/__tests__/phantom-dependency-detection.test.d.ts.map +1 -0
  62. package/dist/__tests__/phantom-dependency-detection.test.js +112 -0
  63. package/dist/__tests__/phantom-dependency-detection.test.js.map +1 -0
  64. package/dist/__tests__/typescript-frontend.test.d.ts +8 -0
  65. package/dist/__tests__/typescript-frontend.test.d.ts.map +1 -0
  66. package/dist/__tests__/typescript-frontend.test.js +57 -0
  67. package/dist/__tests__/typescript-frontend.test.js.map +1 -0
  68. package/dist/checks/architecture/circular-import-detection.d.ts +14 -0
  69. package/dist/checks/architecture/circular-import-detection.d.ts.map +1 -0
  70. package/dist/checks/architecture/circular-import-detection.js +55 -0
  71. package/dist/checks/architecture/circular-import-detection.js.map +1 -0
  72. package/dist/checks/architecture/contracts-schema-consistency.d.ts +11 -0
  73. package/dist/checks/architecture/contracts-schema-consistency.d.ts.map +1 -0
  74. package/dist/checks/architecture/contracts-schema-consistency.js +75 -0
  75. package/dist/checks/architecture/contracts-schema-consistency.js.map +1 -0
  76. package/dist/checks/architecture/drizzle-orm-migration-guardrails.d.ts +12 -0
  77. package/dist/checks/architecture/drizzle-orm-migration-guardrails.d.ts.map +1 -0
  78. package/dist/checks/architecture/drizzle-orm-migration-guardrails.js +92 -0
  79. package/dist/checks/architecture/drizzle-orm-migration-guardrails.js.map +1 -0
  80. package/dist/checks/architecture/index.d.ts +10 -0
  81. package/dist/checks/architecture/index.d.ts.map +1 -0
  82. package/dist/checks/architecture/index.js +10 -0
  83. package/dist/checks/architecture/index.js.map +1 -0
  84. package/dist/checks/architecture/missing-type-exports.d.ts +13 -0
  85. package/dist/checks/architecture/missing-type-exports.d.ts.map +1 -0
  86. package/dist/checks/architecture/missing-type-exports.js +245 -0
  87. package/dist/checks/architecture/missing-type-exports.js.map +1 -0
  88. package/dist/checks/architecture/module-coupling-fan-out.d.ts +20 -0
  89. package/dist/checks/architecture/module-coupling-fan-out.d.ts.map +1 -0
  90. package/dist/checks/architecture/module-coupling-fan-out.js +120 -0
  91. package/dist/checks/architecture/module-coupling-fan-out.js.map +1 -0
  92. package/dist/checks/architecture/no-bootstrap-tool-import.d.ts +38 -0
  93. package/dist/checks/architecture/no-bootstrap-tool-import.d.ts.map +1 -0
  94. package/dist/checks/architecture/no-bootstrap-tool-import.js +95 -0
  95. package/dist/checks/architecture/no-bootstrap-tool-import.js.map +1 -0
  96. package/dist/checks/architecture/package-json-exports-field.d.ts +10 -0
  97. package/dist/checks/architecture/package-json-exports-field.d.ts.map +1 -0
  98. package/dist/checks/architecture/package-json-exports-field.js +56 -0
  99. package/dist/checks/architecture/package-json-exports-field.js.map +1 -0
  100. package/dist/checks/architecture/phantom-dependency-detection.d.ts +22 -0
  101. package/dist/checks/architecture/phantom-dependency-detection.d.ts.map +1 -0
  102. package/dist/checks/architecture/phantom-dependency-detection.js +330 -0
  103. package/dist/checks/architecture/phantom-dependency-detection.js.map +1 -0
  104. package/dist/checks/architecture/tsconfig-extends-validation.d.ts +10 -0
  105. package/dist/checks/architecture/tsconfig-extends-validation.d.ts.map +1 -0
  106. package/dist/checks/architecture/tsconfig-extends-validation.js +78 -0
  107. package/dist/checks/architecture/tsconfig-extends-validation.js.map +1 -0
  108. package/dist/checks/index.d.ts +6 -0
  109. package/dist/checks/index.d.ts.map +1 -0
  110. package/dist/checks/index.js +6 -0
  111. package/dist/checks/index.js.map +1 -0
  112. package/dist/checks/quality/api/api-contract-validation.d.ts +15 -0
  113. package/dist/checks/quality/api/api-contract-validation.d.ts.map +1 -0
  114. package/dist/checks/quality/api/api-contract-validation.js +316 -0
  115. package/dist/checks/quality/api/api-contract-validation.js.map +1 -0
  116. package/dist/checks/quality/api/api-response-validation.d.ts +14 -0
  117. package/dist/checks/quality/api/api-response-validation.d.ts.map +1 -0
  118. package/dist/checks/quality/api/api-response-validation.js +209 -0
  119. package/dist/checks/quality/api/api-response-validation.js.map +1 -0
  120. package/dist/checks/quality/api/fastify-route-validation.d.ts +14 -0
  121. package/dist/checks/quality/api/fastify-route-validation.d.ts.map +1 -0
  122. package/dist/checks/quality/api/fastify-route-validation.js +298 -0
  123. package/dist/checks/quality/api/fastify-route-validation.js.map +1 -0
  124. package/dist/checks/quality/api/fastify-schema-coverage.d.ts +11 -0
  125. package/dist/checks/quality/api/fastify-schema-coverage.d.ts.map +1 -0
  126. package/dist/checks/quality/api/fastify-schema-coverage.js +261 -0
  127. package/dist/checks/quality/api/fastify-schema-coverage.js.map +1 -0
  128. package/dist/checks/quality/api/index.d.ts +5 -0
  129. package/dist/checks/quality/api/index.d.ts.map +1 -0
  130. package/dist/checks/quality/api/index.js +5 -0
  131. package/dist/checks/quality/api/index.js.map +1 -0
  132. package/dist/checks/quality/code-structure/duplicate-utility-functions.d.ts +32 -0
  133. package/dist/checks/quality/code-structure/duplicate-utility-functions.d.ts.map +1 -0
  134. package/dist/checks/quality/code-structure/duplicate-utility-functions.js +451 -0
  135. package/dist/checks/quality/code-structure/duplicate-utility-functions.js.map +1 -0
  136. package/dist/checks/quality/code-structure/index.d.ts +3 -0
  137. package/dist/checks/quality/code-structure/index.d.ts.map +1 -0
  138. package/dist/checks/quality/code-structure/index.js +3 -0
  139. package/dist/checks/quality/code-structure/index.js.map +1 -0
  140. package/dist/checks/quality/code-structure/no-any-types.d.ts +13 -0
  141. package/dist/checks/quality/code-structure/no-any-types.d.ts.map +1 -0
  142. package/dist/checks/quality/code-structure/no-any-types.js +116 -0
  143. package/dist/checks/quality/code-structure/no-any-types.js.map +1 -0
  144. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.d.ts +15 -0
  145. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.d.ts.map +1 -0
  146. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.js +51 -0
  147. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.js.map +1 -0
  148. package/dist/checks/quality/data-integrity/array-validation.d.ts +16 -0
  149. package/dist/checks/quality/data-integrity/array-validation.d.ts.map +1 -0
  150. package/dist/checks/quality/data-integrity/array-validation.js +508 -0
  151. package/dist/checks/quality/data-integrity/array-validation.js.map +1 -0
  152. package/dist/checks/quality/data-integrity/database-index-coverage.d.ts +14 -0
  153. package/dist/checks/quality/data-integrity/database-index-coverage.d.ts.map +1 -0
  154. package/dist/checks/quality/data-integrity/database-index-coverage.js +235 -0
  155. package/dist/checks/quality/data-integrity/database-index-coverage.js.map +1 -0
  156. package/dist/checks/quality/data-integrity/database-schema-validation.d.ts +16 -0
  157. package/dist/checks/quality/data-integrity/database-schema-validation.d.ts.map +1 -0
  158. package/dist/checks/quality/data-integrity/database-schema-validation.js +328 -0
  159. package/dist/checks/quality/data-integrity/database-schema-validation.js.map +1 -0
  160. package/dist/checks/quality/data-integrity/in-memory-repository-detection.d.ts +14 -0
  161. package/dist/checks/quality/data-integrity/in-memory-repository-detection.d.ts.map +1 -0
  162. package/dist/checks/quality/data-integrity/in-memory-repository-detection.js +157 -0
  163. package/dist/checks/quality/data-integrity/in-memory-repository-detection.js.map +1 -0
  164. package/dist/checks/quality/data-integrity/index.d.ts +8 -0
  165. package/dist/checks/quality/data-integrity/index.d.ts.map +1 -0
  166. package/dist/checks/quality/data-integrity/index.js +8 -0
  167. package/dist/checks/quality/data-integrity/index.js.map +1 -0
  168. package/dist/checks/quality/data-integrity/missing-input-validation.d.ts +12 -0
  169. package/dist/checks/quality/data-integrity/missing-input-validation.d.ts.map +1 -0
  170. package/dist/checks/quality/data-integrity/missing-input-validation.js +180 -0
  171. package/dist/checks/quality/data-integrity/missing-input-validation.js.map +1 -0
  172. package/dist/checks/quality/data-integrity/null-safety.d.ts +33 -0
  173. package/dist/checks/quality/data-integrity/null-safety.d.ts.map +1 -0
  174. package/dist/checks/quality/data-integrity/null-safety.js +766 -0
  175. package/dist/checks/quality/data-integrity/null-safety.js.map +1 -0
  176. package/dist/checks/quality/data-integrity/numeric-validation.d.ts +12 -0
  177. package/dist/checks/quality/data-integrity/numeric-validation.d.ts.map +1 -0
  178. package/dist/checks/quality/data-integrity/numeric-validation.js +409 -0
  179. package/dist/checks/quality/data-integrity/numeric-validation.js.map +1 -0
  180. package/dist/checks/quality/frontend/a11y-form-labels.d.ts +14 -0
  181. package/dist/checks/quality/frontend/a11y-form-labels.d.ts.map +1 -0
  182. package/dist/checks/quality/frontend/a11y-form-labels.js +93 -0
  183. package/dist/checks/quality/frontend/a11y-form-labels.js.map +1 -0
  184. package/dist/checks/quality/frontend/a11y-semantic-html.d.ts +14 -0
  185. package/dist/checks/quality/frontend/a11y-semantic-html.d.ts.map +1 -0
  186. package/dist/checks/quality/frontend/a11y-semantic-html.js +88 -0
  187. package/dist/checks/quality/frontend/a11y-semantic-html.js.map +1 -0
  188. package/dist/checks/quality/frontend/index.d.ts +4 -0
  189. package/dist/checks/quality/frontend/index.d.ts.map +1 -0
  190. package/dist/checks/quality/frontend/index.js +4 -0
  191. package/dist/checks/quality/frontend/index.js.map +1 -0
  192. package/dist/checks/quality/frontend/test-only-frontend-modules.d.ts +13 -0
  193. package/dist/checks/quality/frontend/test-only-frontend-modules.d.ts.map +1 -0
  194. package/dist/checks/quality/frontend/test-only-frontend-modules.js +159 -0
  195. package/dist/checks/quality/frontend/test-only-frontend-modules.js.map +1 -0
  196. package/dist/checks/quality/incomplete-regex-escaping.d.ts +13 -0
  197. package/dist/checks/quality/incomplete-regex-escaping.d.ts.map +1 -0
  198. package/dist/checks/quality/incomplete-regex-escaping.js +207 -0
  199. package/dist/checks/quality/incomplete-regex-escaping.js.map +1 -0
  200. package/dist/checks/quality/index.d.ts +11 -0
  201. package/dist/checks/quality/index.d.ts.map +1 -0
  202. package/dist/checks/quality/index.js +11 -0
  203. package/dist/checks/quality/index.js.map +1 -0
  204. package/dist/checks/quality/linting/index.d.ts +2 -0
  205. package/dist/checks/quality/linting/index.d.ts.map +1 -0
  206. package/dist/checks/quality/linting/index.js +2 -0
  207. package/dist/checks/quality/linting/index.js.map +1 -0
  208. package/dist/checks/quality/linting/typescript-frontend.d.ts +25 -0
  209. package/dist/checks/quality/linting/typescript-frontend.d.ts.map +1 -0
  210. package/dist/checks/quality/linting/typescript-frontend.js +159 -0
  211. package/dist/checks/quality/linting/typescript-frontend.js.map +1 -0
  212. package/dist/checks/quality/observability/index.d.ts +5 -0
  213. package/dist/checks/quality/observability/index.d.ts.map +1 -0
  214. package/dist/checks/quality/observability/index.js +5 -0
  215. package/dist/checks/quality/observability/index.js.map +1 -0
  216. package/dist/checks/quality/observability/logger-event-name-format.d.ts +12 -0
  217. package/dist/checks/quality/observability/logger-event-name-format.d.ts.map +1 -0
  218. package/dist/checks/quality/observability/logger-event-name-format.js +124 -0
  219. package/dist/checks/quality/observability/logger-event-name-format.js.map +1 -0
  220. package/dist/checks/quality/observability/no-hardcoded-correlation-id.d.ts +5 -0
  221. package/dist/checks/quality/observability/no-hardcoded-correlation-id.d.ts.map +1 -0
  222. package/dist/checks/quality/observability/no-hardcoded-correlation-id.js +77 -0
  223. package/dist/checks/quality/observability/no-hardcoded-correlation-id.js.map +1 -0
  224. package/dist/checks/quality/observability/observability-coverage/__tests__/analyzer.test.d.ts +11 -0
  225. package/dist/checks/quality/observability/observability-coverage/__tests__/analyzer.test.d.ts.map +1 -0
  226. package/dist/checks/quality/observability/observability-coverage/__tests__/analyzer.test.js +107 -0
  227. package/dist/checks/quality/observability/observability-coverage/__tests__/analyzer.test.js.map +1 -0
  228. package/dist/checks/quality/observability/observability-coverage/__tests__/logger-detector.test.d.ts +12 -0
  229. package/dist/checks/quality/observability/observability-coverage/__tests__/logger-detector.test.d.ts.map +1 -0
  230. package/dist/checks/quality/observability/observability-coverage/__tests__/logger-detector.test.js +94 -0
  231. package/dist/checks/quality/observability/observability-coverage/__tests__/logger-detector.test.js.map +1 -0
  232. package/dist/checks/quality/observability/observability-coverage/analyzer.d.ts +13 -0
  233. package/dist/checks/quality/observability/observability-coverage/analyzer.d.ts.map +1 -0
  234. package/dist/checks/quality/observability/observability-coverage/analyzer.js +117 -0
  235. package/dist/checks/quality/observability/observability-coverage/analyzer.js.map +1 -0
  236. package/dist/checks/quality/observability/observability-coverage/index.d.ts +4 -0
  237. package/dist/checks/quality/observability/observability-coverage/index.d.ts.map +1 -0
  238. package/dist/checks/quality/observability/observability-coverage/index.js +4 -0
  239. package/dist/checks/quality/observability/observability-coverage/index.js.map +1 -0
  240. package/dist/checks/quality/observability/observability-coverage/logger-detector.d.ts +29 -0
  241. package/dist/checks/quality/observability/observability-coverage/logger-detector.d.ts.map +1 -0
  242. package/dist/checks/quality/observability/observability-coverage/logger-detector.js +111 -0
  243. package/dist/checks/quality/observability/observability-coverage/logger-detector.js.map +1 -0
  244. package/dist/checks/quality/observability/observability-coverage/types.d.ts +64 -0
  245. package/dist/checks/quality/observability/observability-coverage/types.d.ts.map +1 -0
  246. package/dist/checks/quality/observability/observability-coverage/types.js +6 -0
  247. package/dist/checks/quality/observability/observability-coverage/types.js.map +1 -0
  248. package/dist/checks/quality/observability/pii-exposure-in-logs.d.ts +22 -0
  249. package/dist/checks/quality/observability/pii-exposure-in-logs.d.ts.map +1 -0
  250. package/dist/checks/quality/observability/pii-exposure-in-logs.js +212 -0
  251. package/dist/checks/quality/observability/pii-exposure-in-logs.js.map +1 -0
  252. package/dist/checks/quality/observability/pii-exposure-in-logs.test.d.ts +11 -0
  253. package/dist/checks/quality/observability/pii-exposure-in-logs.test.d.ts.map +1 -0
  254. package/dist/checks/quality/observability/pii-exposure-in-logs.test.js +46 -0
  255. package/dist/checks/quality/observability/pii-exposure-in-logs.test.js.map +1 -0
  256. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.d.ts +14 -0
  257. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.d.ts.map +1 -0
  258. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.js +61 -0
  259. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.js.map +1 -0
  260. package/dist/checks/quality/patterns/async-waterfall-detection.d.ts +26 -0
  261. package/dist/checks/quality/patterns/async-waterfall-detection.d.ts.map +1 -0
  262. package/dist/checks/quality/patterns/async-waterfall-detection.js +410 -0
  263. package/dist/checks/quality/patterns/async-waterfall-detection.js.map +1 -0
  264. package/dist/checks/quality/patterns/dispose-pattern-completeness.d.ts +13 -0
  265. package/dist/checks/quality/patterns/dispose-pattern-completeness.d.ts.map +1 -0
  266. package/dist/checks/quality/patterns/dispose-pattern-completeness.js +220 -0
  267. package/dist/checks/quality/patterns/dispose-pattern-completeness.js.map +1 -0
  268. package/dist/checks/quality/patterns/error-handling-quality.d.ts +17 -0
  269. package/dist/checks/quality/patterns/error-handling-quality.d.ts.map +1 -0
  270. package/dist/checks/quality/patterns/error-handling-quality.js +335 -0
  271. package/dist/checks/quality/patterns/error-handling-quality.js.map +1 -0
  272. package/dist/checks/quality/patterns/index.d.ts +10 -0
  273. package/dist/checks/quality/patterns/index.d.ts.map +1 -0
  274. package/dist/checks/quality/patterns/index.js +10 -0
  275. package/dist/checks/quality/patterns/index.js.map +1 -0
  276. package/dist/checks/quality/patterns/lifecycle-cleanup-enforcement.d.ts +16 -0
  277. package/dist/checks/quality/patterns/lifecycle-cleanup-enforcement.d.ts.map +1 -0
  278. package/dist/checks/quality/patterns/lifecycle-cleanup-enforcement.js +205 -0
  279. package/dist/checks/quality/patterns/lifecycle-cleanup-enforcement.js.map +1 -0
  280. package/dist/checks/quality/patterns/result-pattern-consistency.d.ts +16 -0
  281. package/dist/checks/quality/patterns/result-pattern-consistency.d.ts.map +1 -0
  282. package/dist/checks/quality/patterns/result-pattern-consistency.js +328 -0
  283. package/dist/checks/quality/patterns/result-pattern-consistency.js.map +1 -0
  284. package/dist/checks/quality/patterns/silent-early-returns.d.ts +23 -0
  285. package/dist/checks/quality/patterns/silent-early-returns.d.ts.map +1 -0
  286. package/dist/checks/quality/patterns/silent-early-returns.js +266 -0
  287. package/dist/checks/quality/patterns/silent-early-returns.js.map +1 -0
  288. package/dist/checks/quality/patterns/stream-buffer-size-limits.d.ts +13 -0
  289. package/dist/checks/quality/patterns/stream-buffer-size-limits.d.ts.map +1 -0
  290. package/dist/checks/quality/patterns/stream-buffer-size-limits.js +163 -0
  291. package/dist/checks/quality/patterns/stream-buffer-size-limits.js.map +1 -0
  292. package/dist/checks/quality/patterns/throws-documentation.d.ts +23 -0
  293. package/dist/checks/quality/patterns/throws-documentation.d.ts.map +1 -0
  294. package/dist/checks/quality/patterns/throws-documentation.js +519 -0
  295. package/dist/checks/quality/patterns/throws-documentation.js.map +1 -0
  296. package/dist/checks/quality/patterns/toctou-race-condition.d.ts +48 -0
  297. package/dist/checks/quality/patterns/toctou-race-condition.d.ts.map +1 -0
  298. package/dist/checks/quality/patterns/toctou-race-condition.js +639 -0
  299. package/dist/checks/quality/patterns/toctou-race-condition.js.map +1 -0
  300. package/dist/checks/quality/stubbed-implementation-detection.d.ts +24 -0
  301. package/dist/checks/quality/stubbed-implementation-detection.d.ts.map +1 -0
  302. package/dist/checks/quality/stubbed-implementation-detection.js +355 -0
  303. package/dist/checks/quality/stubbed-implementation-detection.js.map +1 -0
  304. package/dist/checks/quality/unused-config-options.d.ts +12 -0
  305. package/dist/checks/quality/unused-config-options.d.ts.map +1 -0
  306. package/dist/checks/quality/unused-config-options.js +245 -0
  307. package/dist/checks/quality/unused-config-options.js.map +1 -0
  308. package/dist/checks/resilience/__tests__/callback-invocation-safe.test.d.ts +2 -0
  309. package/dist/checks/resilience/__tests__/callback-invocation-safe.test.d.ts.map +1 -0
  310. package/dist/checks/resilience/__tests__/callback-invocation-safe.test.js +79 -0
  311. package/dist/checks/resilience/__tests__/callback-invocation-safe.test.js.map +1 -0
  312. package/dist/checks/resilience/__tests__/context-leakage-fp.test.d.ts +12 -0
  313. package/dist/checks/resilience/__tests__/context-leakage-fp.test.d.ts.map +1 -0
  314. package/dist/checks/resilience/__tests__/context-leakage-fp.test.js +34 -0
  315. package/dist/checks/resilience/__tests__/context-leakage-fp.test.js.map +1 -0
  316. package/dist/checks/resilience/__tests__/context-mutation.test.d.ts +11 -0
  317. package/dist/checks/resilience/__tests__/context-mutation.test.d.ts.map +1 -0
  318. package/dist/checks/resilience/__tests__/context-mutation.test.js +54 -0
  319. package/dist/checks/resilience/__tests__/context-mutation.test.js.map +1 -0
  320. package/dist/checks/resilience/callback-invocation-safe.d.ts +34 -0
  321. package/dist/checks/resilience/callback-invocation-safe.d.ts.map +1 -0
  322. package/dist/checks/resilience/callback-invocation-safe.js +247 -0
  323. package/dist/checks/resilience/callback-invocation-safe.js.map +1 -0
  324. package/dist/checks/resilience/context-leakage.d.ts +25 -0
  325. package/dist/checks/resilience/context-leakage.d.ts.map +1 -0
  326. package/dist/checks/resilience/context-leakage.js +435 -0
  327. package/dist/checks/resilience/context-leakage.js.map +1 -0
  328. package/dist/checks/resilience/context-mutation.d.ts +21 -0
  329. package/dist/checks/resilience/context-mutation.d.ts.map +1 -0
  330. package/dist/checks/resilience/context-mutation.js +368 -0
  331. package/dist/checks/resilience/context-mutation.js.map +1 -0
  332. package/dist/checks/resilience/detached-promises.d.ts +40 -0
  333. package/dist/checks/resilience/detached-promises.d.ts.map +1 -0
  334. package/dist/checks/resilience/detached-promises.js +646 -0
  335. package/dist/checks/resilience/detached-promises.js.map +1 -0
  336. package/dist/checks/resilience/index.d.ts +7 -0
  337. package/dist/checks/resilience/index.d.ts.map +1 -0
  338. package/dist/checks/resilience/index.js +7 -0
  339. package/dist/checks/resilience/index.js.map +1 -0
  340. package/dist/checks/resilience/no-raw-fetch.d.ts +11 -0
  341. package/dist/checks/resilience/no-raw-fetch.d.ts.map +1 -0
  342. package/dist/checks/resilience/no-raw-fetch.js +110 -0
  343. package/dist/checks/resilience/no-raw-fetch.js.map +1 -0
  344. package/dist/checks/resilience/no-unbounded-concurrency.d.ts +11 -0
  345. package/dist/checks/resilience/no-unbounded-concurrency.d.ts.map +1 -0
  346. package/dist/checks/resilience/no-unbounded-concurrency.js +117 -0
  347. package/dist/checks/resilience/no-unbounded-concurrency.js.map +1 -0
  348. package/dist/checks/security/__tests__/sql-injection.test.d.ts +17 -0
  349. package/dist/checks/security/__tests__/sql-injection.test.d.ts.map +1 -0
  350. package/dist/checks/security/__tests__/sql-injection.test.js +97 -0
  351. package/dist/checks/security/__tests__/sql-injection.test.js.map +1 -0
  352. package/dist/checks/security/index.d.ts +4 -0
  353. package/dist/checks/security/index.d.ts.map +1 -0
  354. package/dist/checks/security/index.js +4 -0
  355. package/dist/checks/security/index.js.map +1 -0
  356. package/dist/checks/security/input-sanitization.d.ts +20 -0
  357. package/dist/checks/security/input-sanitization.d.ts.map +1 -0
  358. package/dist/checks/security/input-sanitization.js +255 -0
  359. package/dist/checks/security/input-sanitization.js.map +1 -0
  360. package/dist/checks/security/sql-injection.d.ts +24 -0
  361. package/dist/checks/security/sql-injection.d.ts.map +1 -0
  362. package/dist/checks/security/sql-injection.js +330 -0
  363. package/dist/checks/security/sql-injection.js.map +1 -0
  364. package/dist/checks/security/unsafe-secret-comparison.d.ts +17 -0
  365. package/dist/checks/security/unsafe-secret-comparison.d.ts.map +1 -0
  366. package/dist/checks/security/unsafe-secret-comparison.js +227 -0
  367. package/dist/checks/security/unsafe-secret-comparison.js.map +1 -0
  368. package/dist/checks/testing/index.d.ts +2 -0
  369. package/dist/checks/testing/index.d.ts.map +1 -0
  370. package/dist/checks/testing/index.js +2 -0
  371. package/dist/checks/testing/index.js.map +1 -0
  372. package/dist/checks/testing/mock-implementations-in-production.d.ts +12 -0
  373. package/dist/checks/testing/mock-implementations-in-production.d.ts.map +1 -0
  374. package/dist/checks/testing/mock-implementations-in-production.js +211 -0
  375. package/dist/checks/testing/mock-implementations-in-production.js.map +1 -0
  376. package/dist/display/architecture.d.ts +9 -0
  377. package/dist/display/architecture.d.ts.map +1 -0
  378. package/dist/display/architecture.js +18 -0
  379. package/dist/display/architecture.js.map +1 -0
  380. package/dist/display/index.d.ts +20 -0
  381. package/dist/display/index.d.ts.map +1 -0
  382. package/dist/display/index.js +30 -0
  383. package/dist/display/index.js.map +1 -0
  384. package/dist/display/quality.d.ts +7 -0
  385. package/dist/display/quality.d.ts.map +1 -0
  386. package/dist/display/quality.js +39 -0
  387. package/dist/display/quality.js.map +1 -0
  388. package/dist/display/resilience.d.ts +7 -0
  389. package/dist/display/resilience.d.ts.map +1 -0
  390. package/dist/display/resilience.js +13 -0
  391. package/dist/display/resilience.js.map +1 -0
  392. package/dist/display/security-testing.d.ts +9 -0
  393. package/dist/display/security-testing.d.ts.map +1 -0
  394. package/dist/display/security-testing.js +14 -0
  395. package/dist/display/security-testing.js.map +1 -0
  396. package/dist/display/types.d.ts +6 -0
  397. package/dist/display/types.d.ts.map +1 -0
  398. package/dist/display/types.js +6 -0
  399. package/dist/display/types.js.map +1 -0
  400. package/dist/index.d.ts +19 -0
  401. package/dist/index.d.ts.map +1 -0
  402. package/dist/index.js +21 -0
  403. package/dist/index.js.map +1 -0
  404. package/package.json +55 -0
@@ -0,0 +1,410 @@
1
+ // @fitness-ignore-file file-length-limit -- Complex module with tightly coupled logic; splitting would fragment cohesive functionality
2
+ // @fitness-ignore-file null-safety -- null checks are intentional guards
3
+ /**
4
+ * @fileoverview Async Waterfall Detection Check
5
+ *
6
+ * Detects sequential await statements that could potentially be parallelized
7
+ * with Promise.all(). Uses AST-aware heuristics:
8
+ * - Looks for consecutive lines with await expressions
9
+ * - Flags when the second await doesn't reference the variable from the first
10
+ * - Skips awaits in different conditional branches (if/else, ternary, switch)
11
+ * - Recognizes dynamic import destructuring dependencies
12
+ * - Skips mutex/lock acquire-then-execute patterns
13
+ * - Skips sleep/delay in polling loops
14
+ */
15
+ import { defineCheck, isTestFile } from '@opensip-cli/fitness';
16
+ import { getSharedSourceFile, isAsync } from '@opensip-cli/lang-typescript';
17
+ import * as ts from 'typescript';
18
+ /**
19
+ * Minimum line gap to consider awaits as consecutive (0 = adjacent lines)
20
+ */
21
+ const MAX_LINE_GAP = 1;
22
+ /**
23
+ * Function names that indicate sleep/delay/timer patterns (inherently sequential)
24
+ */
25
+ const SLEEP_DELAY_NAMES = new Set(['sleep', 'delay', 'wait', 'setTimeout', 'pause']);
26
+ /**
27
+ * Function names that indicate mutex/lock acquire patterns (inherently sequential)
28
+ */
29
+ const LOCK_ACQUIRE_NAMES = new Set(['acquire', 'lock', 'runExclusive', 'withLock']);
30
+ /**
31
+ * Check if a node is an async function (function, method, or arrow function).
32
+ *
33
+ * Defers the modifier inspection to `lang-typescript`'s canonical `isAsync`
34
+ * (modern `canHaveModifiers` + `getModifiers` API); the function-like guard
35
+ * stays inline so callers downstream still get the narrowed-via-control-flow
36
+ * type for `node` at the call site.
37
+ */
38
+ function isAsyncFunction(node) {
39
+ if (ts.isFunctionDeclaration(node) ||
40
+ ts.isMethodDeclaration(node) ||
41
+ ts.isArrowFunction(node) ||
42
+ ts.isFunctionExpression(node)) {
43
+ return isAsync(node);
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Check if a node is an if/else branch and return the branch key.
49
+ */
50
+ function getIfElseBranchKey(current, sourceFile) {
51
+ const parentNode = current.parent;
52
+ if (!ts.isIfStatement(parentNode))
53
+ return null;
54
+ const ifLine = sourceFile.getLineAndCharacterOfPosition(parentNode.getStart()).line;
55
+ if (current === parentNode.thenStatement)
56
+ return `if@L${ifLine}`;
57
+ if (current === parentNode.elseStatement)
58
+ return `else@L${ifLine}`;
59
+ return null;
60
+ }
61
+ /**
62
+ * Check if a node is inside a ternary expression and return the branch key.
63
+ */
64
+ function getTernaryBranchKey(current, sourceFile) {
65
+ const parentNode = current.parent;
66
+ /* v8 ignore next -- defensive AST/type guard */
67
+ if (!ts.isConditionalExpression(parentNode))
68
+ return null;
69
+ const condLine = sourceFile.getLineAndCharacterOfPosition(parentNode.getStart()).line;
70
+ if (current === parentNode.whenTrue)
71
+ return `ternary-true@L${condLine}`;
72
+ if (current === parentNode.whenFalse)
73
+ return `ternary-false@L${condLine}`;
74
+ return null;
75
+ }
76
+ /**
77
+ * Check if a node is a switch case clause and return the branch key.
78
+ */
79
+ function getSwitchBranchKey(current, sourceFile) {
80
+ /* v8 ignore next -- defensive AST/type guard */
81
+ if (!ts.isCaseClause(current) && !ts.isDefaultClause(current))
82
+ return null;
83
+ // @fitness-ignore-next-line null-safety -- CaseClause/DefaultClause parent is CaseBlock, grandparent is SwitchStatement per TS AST spec
84
+ const switchStmt = current.parent.parent;
85
+ if (!ts.isSwitchStatement(switchStmt))
86
+ return null;
87
+ const switchLine = sourceFile.getLineAndCharacterOfPosition(switchStmt.getStart()).line;
88
+ if (ts.isCaseClause(current)) {
89
+ const caseText = current.expression.getText(sourceFile);
90
+ return `case-${caseText}@L${switchLine}`;
91
+ }
92
+ return `default@L${switchLine}`;
93
+ }
94
+ /**
95
+ * Walk up the AST from a node to find if it's inside a conditional branch.
96
+ * Returns a branch key like 'if@L42' or 'else@L42' to identify which branch,
97
+ * or null if not inside a conditional branch.
98
+ *
99
+ * Starts at the node itself (not node.parent) so that direct children of
100
+ * ternary expressions are correctly identified as branch members. For example,
101
+ * in `cond ? await X : await Y`, the AwaitExpression is a direct child of
102
+ * the ConditionalExpression. Starting at node.parent would skip past the
103
+ * AwaitExpression level and miss the ternary branch detection.
104
+ */
105
+ function getBranchKey(node, sourceFile, functionNode) {
106
+ let current = node;
107
+ while (current !== functionNode) {
108
+ const ifElseKey = getIfElseBranchKey(current, sourceFile);
109
+ if (ifElseKey)
110
+ return ifElseKey;
111
+ const ternaryKey = getTernaryBranchKey(current, sourceFile);
112
+ /* v8 ignore next -- defensive AST/type guard */
113
+ if (ternaryKey)
114
+ return ternaryKey;
115
+ const switchKey = getSwitchBranchKey(current, sourceFile);
116
+ /* v8 ignore next -- defensive AST/type guard */
117
+ if (switchKey)
118
+ return switchKey;
119
+ current = current.parent;
120
+ }
121
+ return null;
122
+ }
123
+ /**
124
+ * Extract individual binding names from a destructuring pattern.
125
+ * For `const { foo, bar } = ...` returns ['foo', 'bar'].
126
+ * For `const [a, b] = ...` returns ['a', 'b'].
127
+ */
128
+ function extractDestructuredBindings(pattern, sourceFile) {
129
+ const names = [];
130
+ // @lazy-ok -- iterating binding elements, not awaiting
131
+ for (const element of pattern.elements) {
132
+ if (ts.isBindingElement(element)) {
133
+ if (ts.isIdentifier(element.name)) {
134
+ names.push(element.name.getText(sourceFile));
135
+ }
136
+ else if (ts.isObjectBindingPattern(element.name) ||
137
+ ts.isArrayBindingPattern(element.name)) {
138
+ // Nested destructuring: recurse
139
+ names.push(...extractDestructuredBindings(element.name, sourceFile));
140
+ }
141
+ }
142
+ }
143
+ return names;
144
+ }
145
+ /**
146
+ * Collect all await expressions within an async function (non-recursive into nested async functions)
147
+ */
148
+ function collectAwaitExpressions(node, sourceFile) {
149
+ const awaitInfos = [];
150
+ const visit = (n) => {
151
+ // Don't recurse into nested async functions
152
+ if (n !== node && isAsyncFunction(n)) {
153
+ return;
154
+ }
155
+ if (ts.isAwaitExpression(n)) {
156
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(n.getStart());
157
+ const assignedVariable = getAssignedVariable(n, sourceFile);
158
+ const destructuredBindings = getDestructuredBindings(n, sourceFile);
159
+ const isDynamicImport = isDynamicImportExpression(n);
160
+ const branchKey = getBranchKey(n, sourceFile, node);
161
+ awaitInfos.push({
162
+ line: line + 1, // Convert to 1-indexed
163
+ column: character + 1,
164
+ assignedVariable,
165
+ destructuredBindings,
166
+ expressionText: n.getText(sourceFile),
167
+ isDynamicImport,
168
+ branchKey,
169
+ node: n,
170
+ });
171
+ }
172
+ ts.forEachChild(n, visit);
173
+ };
174
+ ts.forEachChild(node, visit);
175
+ return awaitInfos;
176
+ }
177
+ /**
178
+ * Get the variable name if this await is part of a variable declaration or assignment
179
+ */
180
+ function getAssignedVariable(awaitNode, sourceFile) {
181
+ // Check if parent is a variable declaration: const foo = await ...
182
+ const parent = awaitNode.parent;
183
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
184
+ return parent.name.getText(sourceFile);
185
+ }
186
+ // Check for destructuring: const { foo } = await ... or const [foo] = await ...
187
+ if (ts.isVariableDeclaration(parent) &&
188
+ (ts.isObjectBindingPattern(parent.name) || ts.isArrayBindingPattern(parent.name))) {
189
+ // Return a placeholder to indicate there's an assigned variable
190
+ return parent.name.getText(sourceFile);
191
+ }
192
+ // Check for assignment: foo = await ...
193
+ if (ts.isBinaryExpression(parent) &&
194
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
195
+ ts.isIdentifier(parent.left)) {
196
+ return parent.left.getText(sourceFile);
197
+ }
198
+ return null;
199
+ }
200
+ /**
201
+ * Get individual binding names from destructured await patterns
202
+ */
203
+ function getDestructuredBindings(awaitNode, sourceFile) {
204
+ const parent = awaitNode.parent;
205
+ if (ts.isVariableDeclaration(parent) &&
206
+ (ts.isObjectBindingPattern(parent.name) || ts.isArrayBindingPattern(parent.name))) {
207
+ return extractDestructuredBindings(parent.name, sourceFile);
208
+ }
209
+ return [];
210
+ }
211
+ /**
212
+ * Check if an await expression is a dynamic import: `await import('...')`
213
+ */
214
+ function isDynamicImportExpression(awaitNode) {
215
+ const expr = awaitNode.expression;
216
+ // import(...) appears as a CallExpression with an ImportKeyword
217
+ return ts.isCallExpression(expr) && expr.expression.kind === ts.SyntaxKind.ImportKeyword;
218
+ }
219
+ /**
220
+ * Check if an await expression calls a sleep/delay function
221
+ */
222
+ function isSleepOrDelay(expressionText) {
223
+ const afterAwait = expressionText.replace(/^await\s+/, '');
224
+ // Extract the function name from patterns like: sleep(100), this.sleep(100), delay(ms)
225
+ // eslint-disable-next-line sonarjs/slow-regex -- `\w+` bounded by `(`; optional `this.` prefix is fixed
226
+ const match = /(?:this\.)?(\w+)\s*\(/.exec(afterAwait);
227
+ if (match?.[1] !== undefined) {
228
+ return SLEEP_DELAY_NAMES.has(match[1]);
229
+ }
230
+ return false;
231
+ }
232
+ /**
233
+ * Check if an await expression calls a lock/acquire function
234
+ */
235
+ function isLockAcquire(expressionText) {
236
+ const afterAwait = expressionText.replace(/^await\s+/, '');
237
+ // Extract the function name from patterns like: this.acquire(), acquire(timeout)
238
+ // eslint-disable-next-line sonarjs/slow-regex -- `\w+` bounded by `(`; optional `this.` prefix is fixed
239
+ const match = /(?:this\.)?(\w+)\s*\(/.exec(afterAwait);
240
+ if (match?.[1] !== undefined) {
241
+ return LOCK_ACQUIRE_NAMES.has(match[1]);
242
+ }
243
+ return false;
244
+ }
245
+ /**
246
+ * Check if the next await references any of the destructured bindings from the current await
247
+ */
248
+ function nextUsesDestructuredBindings(current, next) {
249
+ if (current.destructuredBindings.length === 0) {
250
+ return false;
251
+ }
252
+ return current.destructuredBindings.some((binding) => next.expressionText.includes(binding));
253
+ }
254
+ /**
255
+ * Check whether a pair of consecutive await expressions should be skipped (not flagged).
256
+ * Returns true if the pair is NOT a parallelizable waterfall.
257
+ */
258
+ function shouldSkipAwaitPair(current, next) {
259
+ // Skip if awaits are not on consecutive or near-consecutive lines
260
+ /* v8 ignore next -- defensive AST/type guard */
261
+ if (next.line - current.line > MAX_LINE_GAP + 1)
262
+ return true;
263
+ // Skip if both awaits are in different branches of a conditional
264
+ if (current.branchKey !== null &&
265
+ next.branchKey !== null &&
266
+ // @fitness-ignore-next-line unsafe-secret-comparison -- Comparing AST branch identifiers, not cryptographic keys
267
+ current.branchKey !== next.branchKey) {
268
+ return true;
269
+ }
270
+ // Skip if either await is a sleep/delay call (inherently sequential in polling loops)
271
+ /* v8 ignore next -- defensive AST/type guard */
272
+ if (isSleepOrDelay(current.expressionText) || isSleepOrDelay(next.expressionText))
273
+ return true;
274
+ // Skip if the first await is a lock/acquire call (inherently sequential)
275
+ /* v8 ignore next -- defensive AST/type guard */
276
+ if (isLockAcquire(current.expressionText))
277
+ return true;
278
+ // If the first await has an assigned variable, check if the second await uses it
279
+ if (current.assignedVariable !== null && next.expressionText.includes(current.assignedVariable)) {
280
+ return true;
281
+ }
282
+ // Check if the first await has destructured bindings used by the second
283
+ /* v8 ignore next -- defensive AST/type guard */
284
+ if (nextUsesDestructuredBindings(current, next))
285
+ return true;
286
+ // Skip if the first await is a dynamic import (next line typically uses the import)
287
+ /* v8 ignore next -- defensive AST/type guard */
288
+ if (current.isDynamicImport)
289
+ return true;
290
+ // Skip if either await is not a function call (just awaiting a variable)
291
+ if (!isAwaitingFunctionCall(current.expressionText) ||
292
+ !isAwaitingFunctionCall(next.expressionText)) {
293
+ return true;
294
+ }
295
+ return false;
296
+ }
297
+ /**
298
+ * Detect waterfall patterns in a list of await expressions
299
+ */
300
+ function detectWaterfalls(awaitInfos) {
301
+ const violations = [];
302
+ // Sort by line number
303
+ const sorted = [...awaitInfos].sort((a, b) => a.line - b.line);
304
+ // @lazy-ok -- validation depends on preceding await result
305
+ for (let i = 0; i < sorted.length - 1; i++) {
306
+ const current = sorted[i];
307
+ const next = sorted[i + 1];
308
+ // Array access with bounds-checked index is safe here
309
+ if (current === undefined || next === undefined)
310
+ continue;
311
+ if (shouldSkipAwaitPair(current, next))
312
+ continue;
313
+ // This looks like a potential waterfall pattern
314
+ violations.push({
315
+ line: current.line,
316
+ column: current.column,
317
+ message: 'Sequential await statements may be parallelizable with Promise.all()',
318
+ severity: 'warning',
319
+ suggestion: 'Consider using Promise.all() to parallelize independent async operations. ' +
320
+ 'Example: const [result1, result2] = await Promise.all([asyncOp1(), asyncOp2()]);',
321
+ type: 'async-waterfall',
322
+ match: `${current.expressionText} followed by ${next.expressionText}`,
323
+ });
324
+ // Skip the next await since we already flagged this pair
325
+ i++;
326
+ }
327
+ return violations;
328
+ }
329
+ /**
330
+ * Check if an await expression is awaiting a function call (not just a variable or property)
331
+ */
332
+ function isAwaitingFunctionCall(expressionText) {
333
+ // Remove the "await " prefix
334
+ const afterAwait = expressionText.replace(/^await\s+/, '');
335
+ // Check if it ends with () or has a call pattern
336
+ // This catches: foo(), foo.bar(), this.foo(), obj.method(args)
337
+ // eslint-disable-next-line sonarjs/slow-regex -- [^)]* bounded by ')' delimiter; $ anchored
338
+ return /\([^)]*\)\s*$/.test(afterAwait);
339
+ }
340
+ /**
341
+ * Analyze a file for async waterfall patterns
342
+ */
343
+ function analyzeFile(absolutePath, content) {
344
+ const violations = [];
345
+ const sourceFile = getSharedSourceFile(absolutePath, content);
346
+ /* v8 ignore next -- defensive guard */
347
+ if (!sourceFile)
348
+ return [];
349
+ // Find all async functions and analyze their await expressions
350
+ const visit = (node) => {
351
+ if (isAsyncFunction(node)) {
352
+ const asyncNode = node;
353
+ const awaitInfos = collectAwaitExpressions(asyncNode, sourceFile);
354
+ const newViolations = detectWaterfalls(awaitInfos);
355
+ violations.push(...newViolations);
356
+ }
357
+ ts.forEachChild(node, visit);
358
+ };
359
+ visit(sourceFile);
360
+ return violations;
361
+ }
362
+ /**
363
+ * Check: quality/async-waterfall-detection
364
+ *
365
+ * Detects sequential await statements that could potentially be parallelized.
366
+ * Uses AST-aware heuristics including:
367
+ * - Consecutive await detection within a configurable line gap
368
+ * - Variable dependency tracking (simple names and destructured bindings)
369
+ * - Conditional branch awareness (if/else, ternary, switch/case)
370
+ * - Dynamic import recognition
371
+ * - Mutex/lock acquire pattern exclusion
372
+ * - Sleep/delay pattern exclusion
373
+ */
374
+ export const asyncWaterfallDetection = defineCheck({
375
+ id: 'cf169aa8-906c-4e74-bd48-8c9f59ae3eb7',
376
+ slug: 'async-waterfall-detection',
377
+ scope: { languages: ['typescript'], concerns: ['backend', 'server'] },
378
+ contentFilter: 'strip-strings',
379
+ confidence: 'high',
380
+ description: 'Detect sequential await statements that could be parallelized',
381
+ longDescription: `**Purpose:** Detects sequential await statements that could potentially be parallelized with \`Promise.all()\`.
382
+
383
+ **Detects:** Analyzes each file individually using TypeScript AST. Finds consecutive await expressions (within ${MAX_LINE_GAP + 1} lines) in async functions where the second await does not reference the variable assigned by the first, and both await function calls (matched by trailing parentheses).
384
+
385
+ **Excludes (not flagged):**
386
+ - Awaits in different conditional branches (if/else, ternary, switch/case)
387
+ - Dynamic \`await import()\` expressions (next statement almost always depends on the import)
388
+ - Destructured binding dependencies (e.g., \`const { x } = await import(...); await x()\`)
389
+ - Sleep/delay calls in polling loops (\`await sleep()\`, \`await delay()\`)
390
+ - Mutex/lock acquire patterns (\`await this.acquire()\`, \`await lock()\`)
391
+ - CLI entry point files (\`**/bin/**\`)
392
+
393
+ **Why it matters:** Sequential independent awaits double latency unnecessarily; parallelizing them with \`Promise.all()\` can significantly improve performance.
394
+
395
+ **Scope:** General best practice`,
396
+ tags: ['quality', 'performance', 'async', 'patterns'],
397
+ fileTypes: ['ts'],
398
+ analyze(content, filePath) {
399
+ // Skip test files — sequential awaits in tests are low-risk
400
+ if (isTestFile(filePath))
401
+ return [];
402
+ // @lazy-ok -- 'await' appears as a string literal, not an actual await expression
403
+ // Quick filter: skip files without async/await
404
+ if (!content.includes('await')) {
405
+ return [];
406
+ }
407
+ return analyzeFile(filePath, content);
408
+ },
409
+ });
410
+ //# sourceMappingURL=async-waterfall-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-waterfall-detection.js","sourceRoot":"","sources":["../../../../src/checks/quality/patterns/async-waterfall-detection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Dispose Pattern Completeness check
3
+ *
4
+ * Validates that classes implementing IDisposable properly clean up resources.
5
+ * Ensures all subscriptions, connections, and resources are disposed.
6
+ */
7
+ /**
8
+ * Check: quality/dispose-pattern-completeness
9
+ *
10
+ * Validates IDisposable implementations properly clean up resources.
11
+ */
12
+ export declare const disposePatternCompleteness: import("@opensip-cli/fitness").Check;
13
+ //# sourceMappingURL=dispose-pattern-completeness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispose-pattern-completeness.d.ts","sourceRoot":"","sources":["../../../../src/checks/quality/patterns/dispose-pattern-completeness.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsPH;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,sCAiCrC,CAAC"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @fileoverview Dispose Pattern Completeness check
3
+ *
4
+ * Validates that classes implementing IDisposable properly clean up resources.
5
+ * Ensures all subscriptions, connections, and resources are disposed.
6
+ */
7
+ import { defineCheck } from '@opensip-cli/fitness';
8
+ import { getSharedSourceFile } from '@opensip-cli/lang-typescript';
9
+ import * as ts from 'typescript';
10
+ /**
11
+ * Check if a class implements IDisposable or Disposable
12
+ * @param node - The class declaration node
13
+ * @returns True if class implements IDisposable
14
+ */
15
+ function classImplementsDisposable(node) {
16
+ if (!node.heritageClauses) {
17
+ return false;
18
+ }
19
+ return node.heritageClauses.some((clause) => {
20
+ // @fitness-ignore-next-line unsafe-secret-comparison -- Comparing TypeScript AST syntax kind token, not a cryptographic token
21
+ if (clause.token !== ts.SyntaxKind.ImplementsKeyword) {
22
+ return false;
23
+ }
24
+ return clause.types.some((type) => ts.isIdentifier(type.expression) &&
25
+ (type.expression.text === 'IDisposable' || type.expression.text === 'Disposable'));
26
+ });
27
+ }
28
+ /**
29
+ * Find the dispose method in a class
30
+ * @param node - The class declaration node
31
+ * @returns The dispose method or undefined
32
+ */
33
+ function findDisposeMethod(node) {
34
+ const member = node.members.find((m) => ts.isMethodDeclaration(m) && ts.isIdentifier(m.name) && m.name.text === 'dispose');
35
+ return member;
36
+ }
37
+ /**
38
+ * Create a violation for missing dispose method
39
+ * @param ctx - Violation context
40
+ * @param node - The class declaration node
41
+ * @param className - The class name
42
+ * @returns CheckViolation object
43
+ */
44
+ function createMissingDisposeViolation(ctx, node, className) {
45
+ const { line: lineIdx, character } = ctx.sourceFile.getLineAndCharacterOfPosition(node.getStart());
46
+ const line = lineIdx + 1;
47
+ return {
48
+ line,
49
+ column: character + 1,
50
+ message: `Class '${className}' implements IDisposable but has no dispose method`,
51
+ severity: 'error',
52
+ suggestion: `Add 'dispose(): void { /* cleanup subscriptions, connections, timers */ }' method to ${className}`,
53
+ type: 'missing-dispose',
54
+ match: className,
55
+ };
56
+ }
57
+ /**
58
+ * Create a violation for empty dispose method
59
+ * @param ctx - Violation context
60
+ * @param disposeMethod - The dispose method node
61
+ * @param className - The class name
62
+ * @returns CheckViolation object
63
+ */
64
+ function createEmptyDisposeViolation(ctx, disposeMethod, className) {
65
+ const { line: lineIdx, character } = ctx.sourceFile.getLineAndCharacterOfPosition(disposeMethod.getStart());
66
+ const line = lineIdx + 1;
67
+ return {
68
+ line,
69
+ column: character + 1,
70
+ message: `Class '${className}' has empty dispose method`,
71
+ severity: 'warning',
72
+ suggestion: `Add cleanup logic to dispose(): unsubscribe from subscriptions, close connections, clear timers`,
73
+ type: 'empty-dispose',
74
+ match: `${className}.dispose`,
75
+ };
76
+ }
77
+ /**
78
+ * Check if dispose method is empty
79
+ * @param disposeMethod - The dispose method node
80
+ * @returns True if the dispose method body is empty
81
+ */
82
+ function isDisposeMethodEmpty(disposeMethod) {
83
+ const methodBody = disposeMethod.body;
84
+ return Boolean(methodBody && ts.isBlock(methodBody) && methodBody.statements.length === 0);
85
+ }
86
+ /**
87
+ * Check if a property is a subscription or listener field
88
+ * @param member - The class member node
89
+ * @returns True if the property is a subscription field
90
+ */
91
+ function isSubscriptionField(member) {
92
+ if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name)) {
93
+ return false;
94
+ }
95
+ const nameLower = member.name.text.toLowerCase();
96
+ return nameLower.includes('subscription') || nameLower.includes('listener');
97
+ }
98
+ /**
99
+ * Create a violation for uncleaned subscription
100
+ * @param ctx - Violation context
101
+ * @param field - The property declaration node
102
+ * @param fieldName - The field name
103
+ * @returns CheckViolation object
104
+ */
105
+ function createUncleanedSubscriptionViolation(ctx, field, fieldName) {
106
+ const { line: lineIdx, character } = ctx.sourceFile.getLineAndCharacterOfPosition(field.getStart());
107
+ const line = lineIdx + 1;
108
+ return {
109
+ line,
110
+ column: character + 1,
111
+ message: `Subscription '${fieldName}' not cleaned up in dispose()`,
112
+ severity: 'warning',
113
+ suggestion: `Add 'this.${fieldName}?.unsubscribe();' or 'this.${fieldName} = null;' in the dispose() method`,
114
+ type: 'uncleaned-subscription',
115
+ match: fieldName,
116
+ };
117
+ }
118
+ /**
119
+ * Check subscription fields for proper cleanup in dispose method
120
+ * @param ctx - Violation context
121
+ * @param node - The class declaration node
122
+ * @param disposeMethod - The dispose method node
123
+ * @returns Array of violations
124
+ */
125
+ function checkSubscriptionFields(ctx, node, disposeMethod) {
126
+ const violations = [];
127
+ /* v8 ignore next -- defensive nullish fallback */
128
+ const disposeBody = disposeMethod.body?.getText(ctx.sourceFile) ?? '';
129
+ for (const member of node.members) {
130
+ if (!isSubscriptionField(member)) {
131
+ continue;
132
+ }
133
+ const fieldName = member.name.text;
134
+ if (!disposeBody.includes(fieldName)) {
135
+ violations.push(createUncleanedSubscriptionViolation(ctx, member, fieldName));
136
+ }
137
+ }
138
+ return violations;
139
+ }
140
+ /**
141
+ * Analyze a class declaration for dispose pattern issues
142
+ * @param ctx - Violation context
143
+ * @param node - The class declaration node
144
+ * @returns Array of violations
145
+ */
146
+ function analyzeClassDeclaration(ctx, node) {
147
+ const violations = [];
148
+ if (!node.name || !classImplementsDisposable(node)) {
149
+ return violations;
150
+ }
151
+ const className = node.name.text;
152
+ const disposeMethod = findDisposeMethod(node);
153
+ if (!disposeMethod) {
154
+ violations.push(createMissingDisposeViolation(ctx, node, className));
155
+ return violations;
156
+ }
157
+ if (isDisposeMethodEmpty(disposeMethod)) {
158
+ violations.push(createEmptyDisposeViolation(ctx, disposeMethod, className));
159
+ }
160
+ violations.push(...checkSubscriptionFields(ctx, node, disposeMethod));
161
+ return violations;
162
+ }
163
+ /**
164
+ * Analyze a file for dispose pattern completeness
165
+ * @param absolutePath - The file path
166
+ * @param content - The file content
167
+ * @returns Array of violations
168
+ */
169
+ function analyzeFile(absolutePath, content) {
170
+ const violations = [];
171
+ const sourceFile = getSharedSourceFile(absolutePath, content);
172
+ /* v8 ignore next -- defensive guard */
173
+ if (!sourceFile)
174
+ return [];
175
+ const ctx = { absolutePath, content, sourceFile };
176
+ const visit = (node) => {
177
+ if (ts.isClassDeclaration(node)) {
178
+ violations.push(...analyzeClassDeclaration(ctx, node));
179
+ }
180
+ ts.forEachChild(node, visit);
181
+ };
182
+ visit(sourceFile);
183
+ return violations;
184
+ }
185
+ /**
186
+ * Check: quality/dispose-pattern-completeness
187
+ *
188
+ * Validates IDisposable implementations properly clean up resources.
189
+ */
190
+ export const disposePatternCompleteness = defineCheck({
191
+ id: 'f4b1e176-e276-45d5-8d75-c132d5b893d4',
192
+ slug: 'dispose-pattern-completeness',
193
+ scope: { languages: ['typescript'], concerns: ['backend', 'server'] },
194
+ confidence: 'high',
195
+ description: 'Validate IDisposable implementations clean up all resources',
196
+ longDescription: `**Purpose:** Validates that classes implementing \`IDisposable\` or \`Disposable\` properly clean up all resources in their \`dispose()\` method.
197
+
198
+ **Detects:** Analyzes each file individually using TypeScript AST. Checks for:
199
+ - Classes implementing IDisposable/Disposable that have no \`dispose()\` method
200
+ - Classes with an empty \`dispose()\` method body
201
+ - Subscription/listener fields (properties containing "subscription" or "listener" in name) not referenced in the \`dispose()\` method body
202
+
203
+ **Why it matters:** Incomplete dispose implementations cause memory leaks, dangling subscriptions, and unclosed connections that degrade application stability over time.
204
+
205
+ **Scope:** General best practice`,
206
+ tags: ['quality', 'resources', 'memory', 'dispose', 'cleanup'],
207
+ fileTypes: ['ts'],
208
+ analyze(content, filePath) {
209
+ // Quick filter: skip files without dispose-related patterns
210
+ const hasDisposePatterns = content.includes('dispose') ||
211
+ content.includes('Disposable') ||
212
+ content.includes('subscription') ||
213
+ content.includes('unsubscribe');
214
+ if (!hasDisposePatterns) {
215
+ return [];
216
+ }
217
+ return analyzeFile(filePath, content);
218
+ },
219
+ });
220
+ //# sourceMappingURL=dispose-pattern-completeness.js.map