@nielspeter/eess-ts 0.2.1 → 0.4.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 (433) hide show
  1. package/README.md +61 -35
  2. package/dist/builders/call-rule-builder.d.ts +66 -26
  3. package/dist/builders/call-rule-builder.d.ts.map +1 -1
  4. package/dist/builders/call-rule-builder.js +73 -31
  5. package/dist/builders/call-rule-builder.js.map +1 -1
  6. package/dist/builders/class-rule-builder.d.ts +125 -26
  7. package/dist/builders/class-rule-builder.d.ts.map +1 -1
  8. package/dist/builders/class-rule-builder.js +134 -30
  9. package/dist/builders/class-rule-builder.js.map +1 -1
  10. package/dist/builders/correspondence-builder.d.ts +287 -0
  11. package/dist/builders/correspondence-builder.d.ts.map +1 -0
  12. package/dist/builders/correspondence-builder.js +393 -0
  13. package/dist/builders/correspondence-builder.js.map +1 -0
  14. package/dist/builders/correspondence-findings.d.ts +104 -0
  15. package/dist/builders/correspondence-findings.d.ts.map +1 -0
  16. package/dist/builders/correspondence-findings.js +301 -0
  17. package/dist/builders/correspondence-findings.js.map +1 -0
  18. package/dist/builders/cross-layer-builder.d.ts +99 -16
  19. package/dist/builders/cross-layer-builder.d.ts.map +1 -1
  20. package/dist/builders/cross-layer-builder.js +181 -35
  21. package/dist/builders/cross-layer-builder.js.map +1 -1
  22. package/dist/builders/function-rule-builder.d.ts +144 -25
  23. package/dist/builders/function-rule-builder.d.ts.map +1 -1
  24. package/dist/builders/function-rule-builder.js +173 -26
  25. package/dist/builders/function-rule-builder.js.map +1 -1
  26. package/dist/builders/jsx-rule-builder.d.ts +70 -14
  27. package/dist/builders/jsx-rule-builder.d.ts.map +1 -1
  28. package/dist/builders/jsx-rule-builder.js +73 -14
  29. package/dist/builders/jsx-rule-builder.js.map +1 -1
  30. package/dist/builders/module-rule-builder.d.ts +2 -2
  31. package/dist/builders/module-rule-builder.d.ts.map +1 -1
  32. package/dist/builders/module-rule-builder.js +12 -3
  33. package/dist/builders/module-rule-builder.js.map +1 -1
  34. package/dist/builders/scoped-function-rule-builder.d.ts +11 -0
  35. package/dist/builders/scoped-function-rule-builder.d.ts.map +1 -1
  36. package/dist/builders/scoped-function-rule-builder.js +12 -0
  37. package/dist/builders/scoped-function-rule-builder.js.map +1 -1
  38. package/dist/builders/slice-discovery-message.d.ts +36 -0
  39. package/dist/builders/slice-discovery-message.d.ts.map +1 -0
  40. package/dist/builders/slice-discovery-message.js +181 -0
  41. package/dist/builders/slice-discovery-message.js.map +1 -0
  42. package/dist/builders/slice-rule-builder.d.ts +151 -12
  43. package/dist/builders/slice-rule-builder.d.ts.map +1 -1
  44. package/dist/builders/slice-rule-builder.js +242 -35
  45. package/dist/builders/slice-rule-builder.js.map +1 -1
  46. package/dist/builders/type-rule-builder.d.ts +95 -24
  47. package/dist/builders/type-rule-builder.d.ts.map +1 -1
  48. package/dist/builders/type-rule-builder.js +105 -29
  49. package/dist/builders/type-rule-builder.js.map +1 -1
  50. package/dist/{helpers → builders}/within.d.ts +2 -2
  51. package/dist/builders/within.d.ts.map +1 -0
  52. package/dist/{helpers → builders}/within.js +1 -1
  53. package/dist/builders/within.js.map +1 -0
  54. package/dist/cli/commands/baseline.d.ts +3 -2
  55. package/dist/cli/commands/baseline.d.ts.map +1 -1
  56. package/dist/cli/commands/baseline.js +68 -6
  57. package/dist/cli/commands/baseline.js.map +1 -1
  58. package/dist/cli/commands/check.d.ts +7 -2
  59. package/dist/cli/commands/check.d.ts.map +1 -1
  60. package/dist/cli/commands/check.js +241 -45
  61. package/dist/cli/commands/check.js.map +1 -1
  62. package/dist/cli/commands/doctor.d.ts +29 -0
  63. package/dist/cli/commands/doctor.d.ts.map +1 -0
  64. package/dist/cli/commands/doctor.js +224 -0
  65. package/dist/cli/commands/doctor.js.map +1 -0
  66. package/dist/cli/commands/explain.d.ts +2 -1
  67. package/dist/cli/commands/explain.d.ts.map +1 -1
  68. package/dist/cli/commands/explain.js +19 -10
  69. package/dist/cli/commands/explain.js.map +1 -1
  70. package/dist/cli/commands/init.d.ts +9 -3
  71. package/dist/cli/commands/init.d.ts.map +1 -1
  72. package/dist/cli/commands/init.js +162 -137
  73. package/dist/cli/commands/init.js.map +1 -1
  74. package/dist/cli/import-rule-module.d.ts +29 -0
  75. package/dist/cli/import-rule-module.d.ts.map +1 -0
  76. package/dist/cli/import-rule-module.js +60 -0
  77. package/dist/cli/import-rule-module.js.map +1 -0
  78. package/dist/cli/index.d.ts +2 -2
  79. package/dist/cli/index.d.ts.map +1 -1
  80. package/dist/cli/index.js +98 -33
  81. package/dist/cli/index.js.map +1 -1
  82. package/dist/cli/load-rules.d.ts +6 -11
  83. package/dist/cli/load-rules.d.ts.map +1 -1
  84. package/dist/cli/load-rules.js +21 -29
  85. package/dist/cli/load-rules.js.map +1 -1
  86. package/dist/cli/resolve-config.d.ts.map +1 -1
  87. package/dist/cli/resolve-config.js +24 -24
  88. package/dist/cli/resolve-config.js.map +1 -1
  89. package/dist/cli/rule-file-findings.d.ts +178 -0
  90. package/dist/cli/rule-file-findings.d.ts.map +1 -0
  91. package/dist/cli/rule-file-findings.js +293 -0
  92. package/dist/cli/rule-file-findings.js.map +1 -0
  93. package/dist/cli/watch.d.ts +20 -7
  94. package/dist/cli/watch.d.ts.map +1 -1
  95. package/dist/cli/watch.js +39 -14
  96. package/dist/cli/watch.js.map +1 -1
  97. package/dist/conditions/body-analysis-function.d.ts.map +1 -1
  98. package/dist/conditions/body-analysis-function.js +16 -7
  99. package/dist/conditions/body-analysis-function.js.map +1 -1
  100. package/dist/conditions/body-analysis-module.d.ts.map +1 -1
  101. package/dist/conditions/body-analysis-module.js +14 -9
  102. package/dist/conditions/body-analysis-module.js.map +1 -1
  103. package/dist/conditions/body-analysis.d.ts.map +1 -1
  104. package/dist/conditions/body-analysis.js +16 -7
  105. package/dist/conditions/body-analysis.js.map +1 -1
  106. package/dist/conditions/call.d.ts.map +1 -1
  107. package/dist/conditions/call.js +38 -17
  108. package/dist/conditions/call.js.map +1 -1
  109. package/dist/conditions/catch-analysis.d.ts +2 -1
  110. package/dist/conditions/catch-analysis.d.ts.map +1 -1
  111. package/dist/conditions/catch-analysis.js.map +1 -1
  112. package/dist/conditions/cross-layer.d.ts +2 -10
  113. package/dist/conditions/cross-layer.d.ts.map +1 -1
  114. package/dist/conditions/cross-layer.js +227 -23
  115. package/dist/conditions/cross-layer.js.map +1 -1
  116. package/dist/conditions/dependency.d.ts +35 -6
  117. package/dist/conditions/dependency.d.ts.map +1 -1
  118. package/dist/conditions/dependency.js +299 -45
  119. package/dist/conditions/dependency.js.map +1 -1
  120. package/dist/conditions/exports.d.ts.map +1 -1
  121. package/dist/conditions/exports.js +10 -0
  122. package/dist/conditions/exports.js.map +1 -1
  123. package/dist/conditions/function.d.ts.map +1 -1
  124. package/dist/conditions/function.js +19 -5
  125. package/dist/conditions/function.js.map +1 -1
  126. package/dist/conditions/helpers.d.ts +2 -1
  127. package/dist/conditions/helpers.d.ts.map +1 -1
  128. package/dist/conditions/helpers.js +4 -1
  129. package/dist/conditions/helpers.js.map +1 -1
  130. package/dist/conditions/jsx.d.ts.map +1 -1
  131. package/dist/conditions/jsx.js +7 -2
  132. package/dist/conditions/jsx.js.map +1 -1
  133. package/dist/conditions/match-identity.d.ts +43 -0
  134. package/dist/conditions/match-identity.d.ts.map +1 -0
  135. package/dist/conditions/match-identity.js +43 -0
  136. package/dist/conditions/match-identity.js.map +1 -0
  137. package/dist/conditions/members.d.ts.map +1 -1
  138. package/dist/conditions/members.js +17 -4
  139. package/dist/conditions/members.js.map +1 -1
  140. package/dist/conditions/reverse-dependency.d.ts +18 -5
  141. package/dist/conditions/reverse-dependency.d.ts.map +1 -1
  142. package/dist/conditions/reverse-dependency.js +109 -92
  143. package/dist/conditions/reverse-dependency.js.map +1 -1
  144. package/dist/conditions/slice.d.ts +86 -2
  145. package/dist/conditions/slice.d.ts.map +1 -1
  146. package/dist/conditions/slice.js +234 -53
  147. package/dist/conditions/slice.js.map +1 -1
  148. package/dist/conditions/structural.d.ts.map +1 -1
  149. package/dist/conditions/structural.js +16 -4
  150. package/dist/conditions/structural.js.map +1 -1
  151. package/dist/core/check-all.d.ts +19 -0
  152. package/dist/core/check-all.d.ts.map +1 -0
  153. package/dist/core/check-all.js +96 -0
  154. package/dist/core/check-all.js.map +1 -0
  155. package/dist/core/combinators.d.ts +48 -0
  156. package/dist/core/combinators.d.ts.map +1 -0
  157. package/dist/core/combinators.js +56 -0
  158. package/dist/core/combinators.js.map +1 -0
  159. package/dist/core/descendant-cache.d.ts +36 -0
  160. package/dist/core/descendant-cache.d.ts.map +1 -0
  161. package/dist/core/descendant-cache.js +119 -0
  162. package/dist/core/descendant-cache.js.map +1 -0
  163. package/dist/core/diagnose.d.ts +234 -0
  164. package/dist/core/diagnose.d.ts.map +1 -0
  165. package/dist/core/diagnose.js +382 -0
  166. package/dist/core/diagnose.js.map +1 -0
  167. package/dist/core/disk-set.d.ts +48 -0
  168. package/dist/core/disk-set.d.ts.map +1 -0
  169. package/dist/core/disk-set.js +218 -0
  170. package/dist/core/disk-set.js.map +1 -0
  171. package/dist/core/element-cache.d.ts +75 -0
  172. package/dist/core/element-cache.d.ts.map +1 -0
  173. package/dist/core/element-cache.js +32 -0
  174. package/dist/core/element-cache.js.map +1 -0
  175. package/dist/core/empty-project-advice.d.ts +75 -0
  176. package/dist/core/empty-project-advice.d.ts.map +1 -0
  177. package/dist/core/empty-project-advice.js +61 -0
  178. package/dist/core/empty-project-advice.js.map +1 -0
  179. package/dist/core/exclusion-comments.d.ts +54 -0
  180. package/dist/core/exclusion-comments.d.ts.map +1 -0
  181. package/dist/core/exclusion-comments.js +310 -0
  182. package/dist/core/exclusion-comments.js.map +1 -0
  183. package/dist/core/execute-rule.d.ts +150 -0
  184. package/dist/core/execute-rule.d.ts.map +1 -0
  185. package/dist/core/execute-rule.js +580 -0
  186. package/dist/core/execute-rule.js.map +1 -0
  187. package/dist/core/glob-diagnosis.d.ts +78 -0
  188. package/dist/core/glob-diagnosis.d.ts.map +1 -0
  189. package/dist/core/glob-diagnosis.js +134 -0
  190. package/dist/core/glob-diagnosis.js.map +1 -0
  191. package/dist/core/glob-evaluator.d.ts +48 -0
  192. package/dist/core/glob-evaluator.d.ts.map +1 -0
  193. package/dist/core/glob-evaluator.js +89 -0
  194. package/dist/core/glob-evaluator.js.map +1 -0
  195. package/dist/core/import-candidates.d.ts +60 -0
  196. package/dist/core/import-candidates.d.ts.map +1 -0
  197. package/dist/core/import-candidates.js +93 -0
  198. package/dist/core/import-candidates.js.map +1 -0
  199. package/dist/core/import-options.d.ts +55 -2
  200. package/dist/core/import-options.d.ts.map +1 -1
  201. package/dist/core/import-options.js +64 -0
  202. package/dist/core/import-options.js.map +1 -1
  203. package/dist/core/line-index.d.ts +3 -0
  204. package/dist/core/line-index.d.ts.map +1 -0
  205. package/dist/core/line-index.js +229 -0
  206. package/dist/core/line-index.js.map +1 -0
  207. package/dist/core/metric-violation.d.ts +86 -0
  208. package/dist/core/metric-violation.d.ts.map +1 -0
  209. package/dist/core/metric-violation.js +132 -0
  210. package/dist/core/metric-violation.js.map +1 -0
  211. package/dist/core/module-edges.d.ts +361 -0
  212. package/dist/core/module-edges.d.ts.map +1 -0
  213. package/dist/core/module-edges.js +592 -0
  214. package/dist/core/module-edges.js.map +1 -0
  215. package/dist/core/object-literal-functions.d.ts +42 -0
  216. package/dist/core/object-literal-functions.d.ts.map +1 -0
  217. package/dist/core/object-literal-functions.js +75 -0
  218. package/dist/core/object-literal-functions.js.map +1 -0
  219. package/dist/core/orphan-exclusions.d.ts +44 -0
  220. package/dist/core/orphan-exclusions.d.ts.map +1 -0
  221. package/dist/core/orphan-exclusions.js +214 -0
  222. package/dist/core/orphan-exclusions.js.map +1 -0
  223. package/dist/core/pair-condition.d.ts +32 -3
  224. package/dist/core/pair-condition.d.ts.map +1 -1
  225. package/dist/core/path-universe.d.ts +54 -0
  226. package/dist/core/path-universe.d.ts.map +1 -0
  227. package/dist/core/path-universe.js +65 -0
  228. package/dist/core/path-universe.js.map +1 -0
  229. package/dist/core/per-root-compiler-options.d.ts +20 -0
  230. package/dist/core/per-root-compiler-options.d.ts.map +1 -0
  231. package/dist/core/per-root-compiler-options.js +57 -0
  232. package/dist/core/per-root-compiler-options.js.map +1 -0
  233. package/dist/core/project-relative.d.ts +98 -0
  234. package/dist/core/project-relative.d.ts.map +1 -0
  235. package/dist/core/project-relative.js +185 -0
  236. package/dist/core/project-relative.js.map +1 -0
  237. package/dist/core/project.d.ts +7 -1
  238. package/dist/core/project.d.ts.map +1 -1
  239. package/dist/core/project.js +24 -0
  240. package/dist/core/project.js.map +1 -1
  241. package/dist/core/rule-builder.d.ts +282 -0
  242. package/dist/core/rule-builder.d.ts.map +1 -0
  243. package/dist/core/rule-builder.js +445 -0
  244. package/dist/core/rule-builder.js.map +1 -0
  245. package/dist/core/rule-declaration.d.ts +32 -0
  246. package/dist/core/rule-declaration.d.ts.map +1 -0
  247. package/dist/core/rule-declaration.js +126 -0
  248. package/dist/core/rule-declaration.js.map +1 -0
  249. package/dist/core/terminal-builder.d.ts +625 -0
  250. package/dist/core/terminal-builder.d.ts.map +1 -0
  251. package/dist/core/terminal-builder.js +787 -0
  252. package/dist/core/terminal-builder.js.map +1 -0
  253. package/dist/core/terminal-execution.d.ts +23 -0
  254. package/dist/core/terminal-execution.d.ts.map +1 -0
  255. package/dist/core/terminal-execution.js +71 -0
  256. package/dist/core/terminal-execution.js.map +1 -0
  257. package/dist/core/vacuity-diagnosis.d.ts +103 -0
  258. package/dist/core/vacuity-diagnosis.d.ts.map +1 -0
  259. package/dist/core/vacuity-diagnosis.js +415 -0
  260. package/dist/core/vacuity-diagnosis.js.map +1 -0
  261. package/dist/core/violation.d.ts +16 -11
  262. package/dist/core/violation.d.ts.map +1 -1
  263. package/dist/core/violation.js +33 -13
  264. package/dist/core/violation.js.map +1 -1
  265. package/dist/graphql/index.js +1 -1
  266. package/dist/graphql/index.js.map +1 -1
  267. package/dist/graphql/resolver-rule-builder.d.ts +98 -23
  268. package/dist/graphql/resolver-rule-builder.d.ts.map +1 -1
  269. package/dist/graphql/resolver-rule-builder.js +151 -21
  270. package/dist/graphql/resolver-rule-builder.js.map +1 -1
  271. package/dist/graphql/schema-loader.d.ts +21 -0
  272. package/dist/graphql/schema-loader.d.ts.map +1 -1
  273. package/dist/graphql/schema-loader.js +81 -12
  274. package/dist/graphql/schema-loader.js.map +1 -1
  275. package/dist/graphql/schema-rule-builder.d.ts +57 -20
  276. package/dist/graphql/schema-rule-builder.d.ts.map +1 -1
  277. package/dist/graphql/schema-rule-builder.js +101 -25
  278. package/dist/graphql/schema-rule-builder.js.map +1 -1
  279. package/dist/helpers/baseline-diagnostics.d.ts +45 -0
  280. package/dist/helpers/baseline-diagnostics.d.ts.map +1 -0
  281. package/dist/helpers/baseline-diagnostics.js +168 -0
  282. package/dist/helpers/baseline-diagnostics.js.map +1 -0
  283. package/dist/helpers/baseline-generator.d.ts +19 -0
  284. package/dist/helpers/baseline-generator.d.ts.map +1 -0
  285. package/dist/helpers/baseline-generator.js +18 -0
  286. package/dist/helpers/baseline-generator.js.map +1 -0
  287. package/dist/helpers/baseline.d.ts +336 -0
  288. package/dist/helpers/baseline.d.ts.map +1 -0
  289. package/dist/helpers/baseline.js +664 -0
  290. package/dist/helpers/baseline.js.map +1 -0
  291. package/dist/helpers/body-traversal.d.ts +27 -2
  292. package/dist/helpers/body-traversal.d.ts.map +1 -1
  293. package/dist/helpers/body-traversal.js +122 -16
  294. package/dist/helpers/body-traversal.js.map +1 -1
  295. package/dist/helpers/callback-extractor.d.ts.map +1 -1
  296. package/dist/helpers/callback-extractor.js +48 -47
  297. package/dist/helpers/callback-extractor.js.map +1 -1
  298. package/dist/helpers/complexity.d.ts +13 -5
  299. package/dist/helpers/complexity.d.ts.map +1 -1
  300. package/dist/helpers/complexity.js +15 -6
  301. package/dist/helpers/complexity.js.map +1 -1
  302. package/dist/helpers/diff-aware.d.ts +44 -0
  303. package/dist/helpers/diff-aware.d.ts.map +1 -0
  304. package/dist/helpers/diff-aware.js +84 -0
  305. package/dist/helpers/diff-aware.js.map +1 -0
  306. package/dist/helpers/matchers.d.ts +68 -7
  307. package/dist/helpers/matchers.d.ts.map +1 -1
  308. package/dist/helpers/matchers.js +114 -20
  309. package/dist/helpers/matchers.js.map +1 -1
  310. package/dist/helpers/slice-graph.d.ts +100 -8
  311. package/dist/helpers/slice-graph.d.ts.map +1 -1
  312. package/dist/helpers/slice-graph.js +126 -34
  313. package/dist/helpers/slice-graph.js.map +1 -1
  314. package/dist/helpers/tarjan.d.ts +0 -14
  315. package/dist/helpers/tarjan.d.ts.map +1 -1
  316. package/dist/helpers/tarjan.js +29 -16
  317. package/dist/helpers/tarjan.js.map +1 -1
  318. package/dist/index.d.ts +56 -17
  319. package/dist/index.d.ts.map +1 -1
  320. package/dist/index.js +57 -13
  321. package/dist/index.js.map +1 -1
  322. package/dist/models/arch-call.d.ts +3 -2
  323. package/dist/models/arch-call.d.ts.map +1 -1
  324. package/dist/models/arch-call.js.map +1 -1
  325. package/dist/models/arch-function.d.ts +28 -6
  326. package/dist/models/arch-function.d.ts.map +1 -1
  327. package/dist/models/arch-function.js +106 -3
  328. package/dist/models/arch-function.js.map +1 -1
  329. package/dist/models/arch-jsx-element.js +2 -2
  330. package/dist/models/arch-jsx-element.js.map +1 -1
  331. package/dist/models/slice.d.ts +33 -9
  332. package/dist/models/slice.d.ts.map +1 -1
  333. package/dist/models/slice.js +129 -19
  334. package/dist/models/slice.js.map +1 -1
  335. package/dist/predicates/call.d.ts.map +1 -1
  336. package/dist/predicates/call.js +5 -0
  337. package/dist/predicates/call.js.map +1 -1
  338. package/dist/predicates/identity.d.ts +21 -0
  339. package/dist/predicates/identity.d.ts.map +1 -1
  340. package/dist/predicates/identity.js +75 -3
  341. package/dist/predicates/identity.js.map +1 -1
  342. package/dist/predicates/index.d.ts +1 -1
  343. package/dist/predicates/index.d.ts.map +1 -1
  344. package/dist/predicates/index.js +1 -1
  345. package/dist/predicates/index.js.map +1 -1
  346. package/dist/predicates/metrics.d.ts +4 -2
  347. package/dist/predicates/metrics.d.ts.map +1 -1
  348. package/dist/predicates/metrics.js +4 -2
  349. package/dist/predicates/metrics.js.map +1 -1
  350. package/dist/predicates/module.d.ts +4 -12
  351. package/dist/predicates/module.d.ts.map +1 -1
  352. package/dist/predicates/module.js +57 -37
  353. package/dist/predicates/module.js.map +1 -1
  354. package/dist/presets/agent-guardrails.d.ts +22 -15
  355. package/dist/presets/agent-guardrails.d.ts.map +1 -1
  356. package/dist/presets/agent-guardrails.js +124 -19
  357. package/dist/presets/agent-guardrails.js.map +1 -1
  358. package/dist/presets/boundaries.d.ts +45 -2
  359. package/dist/presets/boundaries.d.ts.map +1 -1
  360. package/dist/presets/boundaries.js +132 -23
  361. package/dist/presets/boundaries.js.map +1 -1
  362. package/dist/presets/data-layer.d.ts +22 -2
  363. package/dist/presets/data-layer.d.ts.map +1 -1
  364. package/dist/presets/data-layer.js +58 -16
  365. package/dist/presets/data-layer.js.map +1 -1
  366. package/dist/presets/index.d.ts +7 -5
  367. package/dist/presets/index.d.ts.map +1 -1
  368. package/dist/presets/index.js +9 -3
  369. package/dist/presets/index.js.map +1 -1
  370. package/dist/presets/layered.d.ts +46 -2
  371. package/dist/presets/layered.d.ts.map +1 -1
  372. package/dist/presets/layered.js +66 -29
  373. package/dist/presets/layered.js.map +1 -1
  374. package/dist/presets/recommended.d.ts +80 -9
  375. package/dist/presets/recommended.d.ts.map +1 -1
  376. package/dist/presets/recommended.js +25 -23
  377. package/dist/presets/recommended.js.map +1 -1
  378. package/dist/presets/shared.d.ts +270 -2
  379. package/dist/presets/shared.d.ts.map +1 -1
  380. package/dist/presets/shared.js +338 -4
  381. package/dist/presets/shared.js.map +1 -1
  382. package/dist/rules/code-quality.d.ts.map +1 -1
  383. package/dist/rules/code-quality.js +15 -6
  384. package/dist/rules/code-quality.js.map +1 -1
  385. package/dist/rules/hygiene.d.ts +1 -1
  386. package/dist/rules/hygiene.js +1 -1
  387. package/dist/rules/metrics-function.d.ts +2 -1
  388. package/dist/rules/metrics-function.d.ts.map +1 -1
  389. package/dist/rules/metrics-function.js +33 -6
  390. package/dist/rules/metrics-function.js.map +1 -1
  391. package/dist/rules/metrics.d.ts +2 -1
  392. package/dist/rules/metrics.d.ts.map +1 -1
  393. package/dist/rules/metrics.js +39 -9
  394. package/dist/rules/metrics.js.map +1 -1
  395. package/dist/smells/duplicate-bodies.d.ts +58 -5
  396. package/dist/smells/duplicate-bodies.d.ts.map +1 -1
  397. package/dist/smells/duplicate-bodies.js +137 -56
  398. package/dist/smells/duplicate-bodies.js.map +1 -1
  399. package/dist/smells/fingerprint.d.ts +8 -1
  400. package/dist/smells/fingerprint.d.ts.map +1 -1
  401. package/dist/smells/fingerprint.js +21 -3
  402. package/dist/smells/fingerprint.js.map +1 -1
  403. package/dist/smells/inconsistent-siblings.d.ts +128 -7
  404. package/dist/smells/inconsistent-siblings.d.ts.map +1 -1
  405. package/dist/smells/inconsistent-siblings.js +288 -86
  406. package/dist/smells/inconsistent-siblings.js.map +1 -1
  407. package/dist/smells/sibling-files.d.ts +47 -0
  408. package/dist/smells/sibling-files.d.ts.map +1 -0
  409. package/dist/smells/sibling-files.js +104 -0
  410. package/dist/smells/sibling-files.js.map +1 -0
  411. package/dist/smells/similar-pairs.d.ts +27 -0
  412. package/dist/smells/similar-pairs.d.ts.map +1 -0
  413. package/dist/smells/similar-pairs.js +52 -0
  414. package/dist/smells/similar-pairs.js.map +1 -0
  415. package/dist/smells/smell-builder.d.ts +57 -12
  416. package/dist/smells/smell-builder.d.ts.map +1 -1
  417. package/dist/smells/smell-builder.js +111 -14
  418. package/dist/smells/smell-builder.js.map +1 -1
  419. package/dist/tsconfig/index.d.ts +19 -0
  420. package/dist/tsconfig/index.d.ts.map +1 -0
  421. package/dist/tsconfig/index.js +20 -0
  422. package/dist/tsconfig/index.js.map +1 -0
  423. package/dist/tsconfig/tsconfig-builder.d.ts +42 -9
  424. package/dist/tsconfig/tsconfig-builder.d.ts.map +1 -1
  425. package/dist/tsconfig/tsconfig-builder.js +64 -16
  426. package/dist/tsconfig/tsconfig-builder.js.map +1 -1
  427. package/package.json +3 -2
  428. package/dist/core/module-dependencies.d.ts +0 -28
  429. package/dist/core/module-dependencies.d.ts.map +0 -1
  430. package/dist/core/module-dependencies.js +0 -35
  431. package/dist/core/module-dependencies.js.map +0 -1
  432. package/dist/helpers/within.d.ts.map +0 -1
  433. package/dist/helpers/within.js.map +0 -1
@@ -0,0 +1,664 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { subjectOf } from '@nielspeter/eess';
5
+ import { discoverIdentityRoot, normalizeIdentityText, toPortablePath } from '@nielspeter/eess';
6
+ import { writeStderr } from '@nielspeter/eess';
7
+ import { descriptionChangeFinding, unmatchedBaselineFinding } from './baseline-diagnostics.js';
8
+ /**
9
+ * Identity-hash format version.
10
+ *
11
+ * 1 — sha256(rule::element::message) verbatim; absolute paths leak in.
12
+ * 2 — the repository root is replaced with a token first, so identity is
13
+ * portable across checkouts (bug 0010).
14
+ *
15
+ * Note that v2 is byte-identical to v1 for any violation whose fields contain
16
+ * no path, so most v1 baselines keep matching. The version alone is therefore
17
+ * NOT grounds to fail — see `unmatchedBaselineFinding`, which fires on the
18
+ * measurement instead.
19
+ * 4 — bug 0012. Metric findings gained a producer-set `identity`
20
+ * (`file::element::metric`) and an accepted `measured` value, so every entry
21
+ * for the ten size and complexity conditions hashes differently. The v3
22
+ * precedent applies exactly: `hashViolation`'s FORMULA is untouched and one
23
+ * of its inputs moved, which is the case the 0.23.0 withdrawal established
24
+ * is worth a bump only when the identity of existing entries actually
25
+ * changes. It does here, for that family and no other — a baseline with no
26
+ * metric entries is byte-identical and keeps matching.
27
+ *
28
+ */
29
+ // Stays 2 through 0.23.0, deliberately. Accumulate (bug 0020) lengthens
30
+ // `buildRuleDescription()` for a rule derived off a held rule and for a
31
+ // pre-`.should()` `satisfy()`, and the description is hashed — so those entries
32
+ // stop matching. That is a change in the hash's *input*, not in how it is
33
+ // computed: `hashViolation` below never reads this constant.
34
+ //
35
+ // 0.23.0 drafted a bump to 3 to signal it and two independent reviews measured
36
+ // that as a defect. It matches no entry differently, and the only thing it
37
+ // changes is which `cause` sentence `unmatchedBaselineFinding` picks — so every
38
+ // user holding a pre-0.23.0 baseline that matched nothing for an unrelated
39
+ // reason would be told the format was "the likely cause", which cannot be true,
40
+ // while the branch naming the cause that usually is (a differently-resolved
41
+ // root) became unreachable. Bump this only when `hashViolation` changes.
42
+ //
43
+ // The unmatched *entry* still cannot be diagnosed — see bug 0027; that is the
44
+ // gap the bump was reaching for and did not close.
45
+ //
46
+ // 3 — dependency findings carry a producer-set `identity`, so `hashViolation`
47
+ // computes a different string for all of them (bug 0028). This bump satisfies
48
+ // the rule above, where 0.23.0's attempt did not: the *formula* changed, not
49
+ // just one of its inputs.
50
+ //
51
+ // Why it had to: a dependency message carries the basename and the resolved
52
+ // target and nothing else, so two edges from one file to one module hashed
53
+ // identically. Measured on this repo's barrel after v0.28.0 made barrels
54
+ // dependency-bearing — 114 findings, 87 identities, 46.5% of findings sharing
55
+ // one with a sibling. `identity` adds the imported names, which discriminate
56
+ // and survive code moving where a line number would not.
57
+ //
58
+ // **It invalidates existing baselines for every dependency finding**, not only
59
+ // the colliding ones, because identity replaces `element::message` wholesale.
60
+ // No printed text changes, which is why bug 0028 first recorded this as needing
61
+ // no migration treatment — that conflated text-stability with baseline-stability
62
+ // and was wrong. The bump is what lets `unmatchedBaselineFinding` name the real
63
+ // cause instead of guessing at a re-resolved root.
64
+ //
65
+ // 5 — plan 0104. `beFreeOfCycles` gains a producer-set `identity` PER INTERNAL EDGE
66
+ // (`cycle-edge::${from}->${to}`), replacing the old per-component `cycle::${members}`
67
+ // scheme wholesale — so every existing cycle finding's identity changes, whether or
68
+ // not its underlying edges did. The v3/v4 precedent applies exactly: `hashViolation`'s
69
+ // FORMULA is untouched and one of its inputs moved, and the identity of existing
70
+ // entries actually changes, for that family and no other — a baseline with no cycle
71
+ // entries is byte-identical and keeps matching. Distinct prefix (`cycle-edge::` vs.
72
+ // `cycle::`) also means an old-format entry cannot accidentally still match a
73
+ // new-format finding by coincidence, independent of this version bump.
74
+ export const HASH_VERSION = 5;
75
+ /**
76
+ * Units whose meaning has never changed, so an entry that predates unit
77
+ * stamping is still safely comparable against one.
78
+ *
79
+ * `code-lines` is deliberately absent, and that absence is the entire fix for
80
+ * [bug 0171](../../../../work/bugs/0171-a-metric-unit-change-silently-loosens-every-baselined-ratchet.md).
81
+ * An unstamped `lines` entry was written when `linesOfCode` returned a SPAN, so
82
+ * its number is denominated in something the tool no longer produces —
83
+ * measured, roughly three times the current value on this repo's own source.
84
+ * Comparing across that does not report a regression; it silently raises the
85
+ * ceiling and keeps the build green while a class triples.
86
+ *
87
+ * A metric added here later is asserting "this counts what it always counted".
88
+ */
89
+ const MEANING_NEVER_CHANGED = new Set([
90
+ 'methods',
91
+ 'parameters',
92
+ 'properties',
93
+ 'named-exports',
94
+ 'complexity',
95
+ ]);
96
+ /**
97
+ * Whether a stored measurement may be compared against a current one.
98
+ *
99
+ * Fails CLOSED: anything other than a demonstrable match is incomparable, and
100
+ * an incomparable entry stops suppressing. The alternative — assume they agree —
101
+ * is how a ratchet loosens without anyone being told (ADR-010: a pass is
102
+ * constructed from evidence, and "probably the same unit" is not evidence).
103
+ */
104
+ function measurementComparable(stored, current) {
105
+ // Both stamped and equal, or neither stamped (a producer that predates units
106
+ // on both sides — the honest pre-0171 reading).
107
+ if (stored === current)
108
+ return true;
109
+ // Stored predates stamping. Comparable only where the meaning cannot have moved.
110
+ if (stored === undefined)
111
+ return current !== undefined && MEANING_NEVER_CHANGED.has(current);
112
+ // Stored says one thing and this run says another. Never comparable.
113
+ return false;
114
+ }
115
+ /**
116
+ * Compute a stable hash for a violation.
117
+ *
118
+ * Uses rule + element + message as identity. This survives:
119
+ * - Line number changes (code moved)
120
+ * - Unrelated code changes in the same file
121
+ * - **The checkout's absolute location** — see `root` below
122
+ *
123
+ * Does NOT survive:
124
+ * - Rule DESCRIPTION changes — the assembled `that … should …` text, i.e. editing
125
+ * the rule's predicates or conditions. **Not** `.because()`: that sets
126
+ * `because`, which is not hashed. The old wording said "(rewording
127
+ * .because())" and it was false — measured, two violations differing only in
128
+ * `because` hash identically, and so do two differing only in `suggestion`.
129
+ * The distinction is load-bearing: it is what makes correcting a preset's
130
+ * remedy text a patch rather than a change that invalidates every consumer's
131
+ * baseline (bug 0017, whose own defect was a remedy claiming something it
132
+ * could not do — sitting in the docstring a compat auditor would check).
133
+ * - Element renames (class renamed)
134
+ * - Message text changes (condition wording updated)
135
+ *
136
+ * This is intentional — if the rule or element changes,
137
+ * the violation should be re-evaluated.
138
+ *
139
+ * @param root - Repository/workspace root. Every occurrence of it inside the
140
+ * rule, element and message is replaced with a stable token before hashing.
141
+ * Producers interpolate absolute paths into those fields, so without a root
142
+ * the identity encodes the checkout directory and a baseline written on one
143
+ * machine matches nothing on another (bug 0010). Omitting it preserves the
144
+ * pre-0.19 hash and is only correct when no field contains a path.
145
+ */
146
+ // eess-exclude eess/no-unused-exports: consumed by the test suite; the build tsconfig this gate reads excludes tests, so `src` is the only usage it can see
147
+ export function hashViolation(violation, root) {
148
+ const scrub = (text) => root === undefined ? text : normalizeIdentityText(text, root);
149
+ // A producer that sets `identity` has declared its own canonical form, which
150
+ // supersedes both element and message — see ArchViolation.identity. Without
151
+ // one, the composed string is byte-identical to the pre-0.19 input, so a
152
+ // violation whose fields never contained a path keeps its old hash.
153
+ // ONE definition, shared with `disambiguateIdentities` — see `subjectOf`. It used to be
154
+ // spelled out here and copied there, guarded by a test asserting the copies agree; the copy
155
+ // is gone because `helpers/` may import from `core/` and a shared definition cannot diverge.
156
+ const content = `${scrub(violation.rule)}::${scrub(subjectOf(violation))}`;
157
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
158
+ }
159
+ /**
160
+ * The violation's **subject**: what was found, independent of which rule found it.
161
+ *
162
+ * `hashViolation` above is `rule::element::message`, so editing a rule's
163
+ * predicates or conditions — or accumulating them, which v0.23.0 made happen for
164
+ * a rule derived off a held rule — changes the identity of violations that did
165
+ * not change at all. Those entries stop matching and their already-accepted
166
+ * violations report as **new**, with nothing saying why (bug 0027).
167
+ *
168
+ * This is the differently-derived value that tells the two cases apart:
169
+ *
170
+ * | baseline entry did not match because… | subject present in the run? |
171
+ * | ------------------------------------- | --------------------------- |
172
+ * | the violation was fixed | no — stay silent, this is success |
173
+ * | the rule's description changed | yes — say so, and regenerate |
174
+ *
175
+ * Bug 0027's own suggested signal was "an entry whose `rule` string appears under
176
+ * a different hash", and it cannot work: the rule string is precisely what
177
+ * changed. Measured before this was built.
178
+ */
179
+ export function hashSubject(violation, root) {
180
+ const scrub = (text) => root === undefined ? text : normalizeIdentityText(text, root);
181
+ return createHash('sha256')
182
+ .update(scrub(subjectOf(violation)))
183
+ .digest('hex')
184
+ .slice(0, 16);
185
+ }
186
+ /** Forward slashes so the recorded root reads the same on Windows and CI. */
187
+ function toPosix(value) {
188
+ return value.replaceAll('\\', '/');
189
+ }
190
+ /**
191
+ * Load a baseline from a JSON file.
192
+ *
193
+ * @param baselinePath - Path to the baseline JSON file
194
+ * @param options - See \{@link BaselineOptions\}
195
+ * @returns A Baseline object for use with check(\{ baseline \})
196
+ */
197
+ export function withBaseline(baselinePath, options = {}) {
198
+ const resolved = path.resolve(baselinePath);
199
+ const baselineDir = path.dirname(resolved);
200
+ const root = options.root !== undefined ? path.resolve(options.root) : discoverIdentityRoot(baselineDir);
201
+ if (!fs.existsSync(resolved)) {
202
+ // No baseline file = no known violations = all violations are new
203
+ return new Baseline(new Set(), root, HASH_VERSION);
204
+ }
205
+ const raw = fs.readFileSync(resolved, 'utf-8');
206
+ const parsed = JSON.parse(raw);
207
+ if (!parsed ||
208
+ typeof parsed !== 'object' ||
209
+ !('violations' in parsed) ||
210
+ !Array.isArray(parsed.violations)) {
211
+ writeStderr(`[eess] Invalid baseline file format at ${resolved} — treating as empty`);
212
+ return new Baseline(new Set(), root, HASH_VERSION);
213
+ }
214
+ const hashVersion = 'hashVersion' in parsed && typeof parsed.hashVersion === 'number' ? parsed.hashVersion : 1;
215
+ // A recorded root wins over re-discovery: it is what the hashes in this file
216
+ // were actually built against. Re-deriving here is what lets a machine
217
+ // without `.git` disagree with the machine that wrote the file, and that
218
+ // disagreement produces no signal at all (bug 0010, review C2). An explicit
219
+ // `options.root` still wins over both — the caller is overriding on purpose.
220
+ const recordedRoot = 'root' in parsed && typeof parsed.root === 'string'
221
+ ? path.resolve(baselineDir, parsed.root)
222
+ : undefined;
223
+ const effectiveRoot = options.root !== undefined ? root : (recordedRoot ?? root);
224
+ const hashes = new Set();
225
+ // subject hash -> the rule description recorded for it. Only what the
226
+ // description-change diagnosis needs, so a large baseline does not carry a
227
+ // second copy of every entry. Entries written before 0.24.0 have no subject
228
+ // and simply do not appear here.
229
+ const subjects = new Map();
230
+ /** hash -> accepted measurement and its unit, for metric findings (bugs 0012, 0171). */
231
+ const accepted = new Map();
232
+ // Annotated as `readonly unknown[]`, not iterated directly: `parsed.violations`
233
+ // is `any[]` after the `Array.isArray` check, and ADR-005 bars both `any` and
234
+ // the `as` that would otherwise be needed to re-narrow it. Assigning to this
235
+ // type is allowed and hands each element back as `unknown`.
236
+ const rawEntries = parsed.violations;
237
+ for (const entry of rawEntries) {
238
+ if (entry === null || typeof entry !== 'object')
239
+ continue;
240
+ if ('hash' in entry && typeof entry.hash === 'string')
241
+ hashes.add(entry.hash);
242
+ if ('subject' in entry &&
243
+ typeof entry.subject === 'string' &&
244
+ 'rule' in entry &&
245
+ typeof entry.rule === 'string') {
246
+ subjects.set(entry.subject, entry.rule);
247
+ }
248
+ // Bug 0012: the measurement this entry accepted. Absent on every non-metric
249
+ // entry and on anything written before this shipped, which `isKnown` reads
250
+ // as "no ratchet recorded" rather than as zero.
251
+ if ('hash' in entry &&
252
+ typeof entry.hash === 'string' &&
253
+ 'measured' in entry &&
254
+ typeof entry.measured === 'number') {
255
+ accepted.set(entry.hash, {
256
+ value: entry.measured,
257
+ // Absent on any entry written before bug 0171 — deliberately left
258
+ // `undefined` rather than defaulted, because a guess here is exactly
259
+ // the silent re-denomination this field exists to prevent.
260
+ unit: 'measuredUnit' in entry && typeof entry.measuredUnit === 'string'
261
+ ? entry.measuredUnit
262
+ : undefined,
263
+ });
264
+ }
265
+ }
266
+ return new Baseline(hashes, effectiveRoot, hashVersion, resolved, subjects, accepted);
267
+ }
268
+ /**
269
+ * Generate a baseline file from a list of violations.
270
+ *
271
+ * Call this to create/update the baseline:
272
+ * ```typescript
273
+ * const violations = collectAllViolations(rules)
274
+ * generateBaseline(violations, 'arch-baseline.json')
275
+ * ```
276
+ */
277
+ export function generateBaseline(violations, outputPath, options = {}) {
278
+ const resolved = path.resolve(outputPath);
279
+ const baselineDir = path.dirname(resolved);
280
+ const root = options.root !== undefined ? path.resolve(options.root) : discoverIdentityRoot(baselineDir);
281
+ // Config-level meta-findings (empty selector/discovery) must never be
282
+ // baselined away — they carry bypassFilters and are re-kept by filterNew
283
+ // regardless, so writing them in only pollutes the file (plan 0067).
284
+ const entries = violations
285
+ .filter((v) => v.bypassFilters !== true)
286
+ .map((v) => ({
287
+ rule: v.rule,
288
+ // Root-relative, not baseline-relative: the stored path must read the
289
+ // same in every checkout, and `../../` chains encode the baseline file's
290
+ // depth. Forward slashes so a file written on Windows reads on CI.
291
+ file: toPortablePath(v.file, root),
292
+ line: v.line,
293
+ hash: hashViolation(v, root),
294
+ subject: hashSubject(v, root),
295
+ // Bug 0012. Written only for metric findings, so a baseline of ordinary
296
+ // findings is byte-identical to one from before this shipped.
297
+ ...(v.measured === undefined ? {} : { measured: v.measured }),
298
+ // Bug 0171: what that number counts, so a later change to the metric
299
+ // cannot silently re-denominate an accepted ceiling.
300
+ ...(v.measuredUnit === undefined ? {} : { measuredUnit: v.measuredUnit }),
301
+ }));
302
+ // Read before writing — this is the only moment both sets exist (plan 0071).
303
+ const prior = readPriorHashes(resolved);
304
+ const baseline = {
305
+ generatedAt: new Date().toISOString(),
306
+ hashVersion: HASH_VERSION,
307
+ root: toPosix(path.relative(baselineDir, root)) || '.',
308
+ count: entries.length,
309
+ violations: entries,
310
+ };
311
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
312
+ fs.writeFileSync(resolved, JSON.stringify(baseline, null, 2) + '\n');
313
+ const written = new Set(entries.map((e) => e.hash));
314
+ return {
315
+ before: prior?.count,
316
+ after: entries.length,
317
+ added: prior === undefined
318
+ ? entries.length
319
+ : [...written].filter((h) => !prior.hashes.has(h)).length,
320
+ removed: prior === undefined ? 0 : [...prior.hashes].filter((h) => !written.has(h)).length,
321
+ priorHashVersion: prior?.hashVersion,
322
+ priorUnreadable: prior?.unreadable ?? false,
323
+ };
324
+ }
325
+ /**
326
+ * The hashes of the baseline file about to be overwritten.
327
+ *
328
+ * `undefined` when there is no prior file — the first-run case, which must read
329
+ * as "41 new entries", never as "+41, −0 against an empty baseline".
330
+ *
331
+ * Deliberately tolerant in the same shape as {@link withBaseline}, and for the
332
+ * same reason: a prior file that cannot be parsed must not throw here, because
333
+ * the write is the user's whole purpose. But it is reported rather than treated
334
+ * as absent — silently calling a corrupt baseline "no prior baseline" would
335
+ * print `(+78, −0)` and hide that 41 accepted findings just stopped being
336
+ * accepted.
337
+ */
338
+ function readPriorHashes(resolved) {
339
+ if (!fs.existsSync(resolved))
340
+ return undefined;
341
+ let parsed;
342
+ try {
343
+ parsed = JSON.parse(fs.readFileSync(resolved, 'utf-8'));
344
+ }
345
+ catch (error) {
346
+ // `unreadable` carries the cause, so the finding can say whether the file is
347
+ // malformed or simply absent — two different remedies (regenerate vs create).
348
+ return {
349
+ hashes: new Set(),
350
+ count: 0,
351
+ hashVersion: undefined,
352
+ unreadable: true,
353
+ unreadableReason: error instanceof Error ? error.message : String(error),
354
+ };
355
+ }
356
+ if (!parsed ||
357
+ typeof parsed !== 'object' ||
358
+ !('violations' in parsed) ||
359
+ !Array.isArray(parsed.violations)) {
360
+ return { hashes: new Set(), count: 0, hashVersion: undefined, unreadable: true };
361
+ }
362
+ const hashVersion = 'hashVersion' in parsed && typeof parsed.hashVersion === 'number' ? parsed.hashVersion : 1;
363
+ // Same `readonly unknown[]` narrowing as `withBaseline`, for the ADR-005
364
+ // reason documented there.
365
+ const rawEntries = parsed.violations;
366
+ const hashes = new Set();
367
+ for (const entry of rawEntries) {
368
+ if (entry === null || typeof entry !== 'object')
369
+ continue;
370
+ if ('hash' in entry && typeof entry.hash === 'string')
371
+ hashes.add(entry.hash);
372
+ }
373
+ // `count` is the entry count, not `hashes.size`: two entries can share a hash
374
+ // (bug 0028, measured at 17% in this repo), and reporting the deduplicated
375
+ // number as "41 entries" would disagree with the file the reader can open.
376
+ return { hashes, count: rawEntries.length, hashVersion, unreadable: false };
377
+ }
378
+ /**
379
+ * The one-line delta, e.g. `41 → 78 entries (+37, −0)`.
380
+ *
381
+ * Kept beside {@link BaselineDelta} rather than in the CLI so the `baseline`
382
+ * command and any programmatic caller print the same sentence.
383
+ */
384
+ export function formatBaselineDelta(delta) {
385
+ const plural = (n) => (n === 1 ? 'entry' : 'entries');
386
+ if (delta.priorUnreadable) {
387
+ return (`Baseline replaced: ${String(delta.after)} ${plural(delta.after)} written. ` +
388
+ `The previous file could not be read as a baseline, so this is not a delta — ` +
389
+ `whatever it accepted is no longer accepted. Check it in git history if that matters.`);
390
+ }
391
+ if (delta.before === undefined) {
392
+ return `Baseline created: ${String(delta.after)} ${plural(delta.after)} accepted (no previous baseline).`;
393
+ }
394
+ const line = `Baseline updated: ${String(delta.before)} → ${String(delta.after)} ${plural(delta.after)} ` +
395
+ `(+${String(delta.added)}, −${String(delta.removed)}).`;
396
+ // A full replacement — nothing carried over — means something different from a
397
+ // delta of the same magnitude, and the reader's next action is not the same:
398
+ // "you accepted 78 new findings" versus "the identities all changed, so this
399
+ // says nothing about what you accepted".
400
+ //
401
+ // **Keyed on the measurement, not on the version.** A version mismatch looks
402
+ // like the obvious trigger and is not one: v2 is byte-identical to v1 for any
403
+ // violation whose fields contain no path (see HASH_VERSION), so a v1 baseline
404
+ // usually keeps matching entirely — and a message asserting "none of its
405
+ // identities could be compared" beside `(+0, −0)` would be plainly false. The
406
+ // overlap is the differently-derived value (ADR-009 rule 5); the version is
407
+ // offered as a likely cause only once the measurement has established there
408
+ // is something to explain.
409
+ if (delta.before > 0 && delta.added === delta.after && delta.removed === delta.before) {
410
+ const cause = delta.priorHashVersion !== undefined && delta.priorHashVersion !== HASH_VERSION
411
+ ? ` The identity format changed (v${String(delta.priorHashVersion)} → v${String(HASH_VERSION)}), which is the usual cause.`
412
+ : '';
413
+ return (`${line} No entry survived: every prior identity is gone and every entry written is new, ` +
414
+ `so this delta does not tell you what changed in your code.${cause}`);
415
+ }
416
+ if (delta.added > 0) {
417
+ return `${line} The +${String(delta.added)} are findings this file now accepts that it did not before.`;
418
+ }
419
+ return line;
420
+ }
421
+ /**
422
+ * A loaded baseline. Passed to check(\{ baseline \}) to filter known violations.
423
+ */
424
+ export class Baseline {
425
+ knownHashes;
426
+ root;
427
+ hashVersion;
428
+ sourcePath;
429
+ knownSubjects;
430
+ acceptedMeasurements;
431
+ // `Baseline` is an exported type and `new Baseline(hashes, root)` is a
432
+ // documented call, so folding these into an options object is a breaking
433
+ // change and not this plan's to make. Every parameter after the second is
434
+ // optional and defaulted, so the call sites that matter take two.
435
+ //
436
+ // The directive is the LAST line before the declaration on purpose: a
437
+ // single-line waiver covers exactly the next line, so prose placed under it
438
+ // consumes the waiver instead of the code (measured, right here).
439
+ // eess-exclude eess/max-parameters: published constructor; an options object would break it
440
+ constructor(knownHashes, root, hashVersion = HASH_VERSION, sourcePath,
441
+ /**
442
+ * Subject hash -> the rule description recorded against it. Empty for a
443
+ * baseline written before 0.24.0, which disables the description-change
444
+ * diagnosis rather than guessing at it (bug 0027).
445
+ */
446
+ knownSubjects = new Map(),
447
+ /**
448
+ * Hash -> the measurement accepted for it, for metric findings (bug 0012).
449
+ * Absent for every non-metric entry and for baselines written before this
450
+ * shipped, where equality of identity remains the right test.
451
+ */
452
+ acceptedMeasurements = new Map()) {
453
+ this.knownHashes = knownHashes;
454
+ this.root = root;
455
+ this.hashVersion = hashVersion;
456
+ this.sourcePath = sourcePath;
457
+ this.knownSubjects = knownSubjects;
458
+ this.acceptedMeasurements = acceptedMeasurements;
459
+ }
460
+ /**
461
+ * Check if a violation is known (exists in the baseline).
462
+ * Known violations are filtered out — they don't cause failures.
463
+ */
464
+ /**
465
+ * Whether the baseline holds an entry with this violation's identity.
466
+ *
467
+ * Distinct from {@link isKnown}, which also asks whether a metric finding is
468
+ * still within the measurement that entry accepted. A regressed metric has an
469
+ * entry and is not known.
470
+ */
471
+ hasEntry(violation) {
472
+ return this.knownHashes.has(hashViolation(violation, this.root));
473
+ }
474
+ /**
475
+ * Whether this violation is already accepted by the baseline.
476
+ *
477
+ * Identity answers "is this the same finding?"; for a metric finding that is
478
+ * not enough, so a known hash is additionally required to be **no worse** than
479
+ * the accepted measurement — improving a metric stays green, regressing past
480
+ * the accepted value fails (bug 0012).
481
+ */
482
+ isKnown(violation) {
483
+ const hash = hashViolation(violation, this.root);
484
+ if (!this.knownHashes.has(hash))
485
+ return false;
486
+ // Bug 0012: a metric finding is known only while it is **no worse** than
487
+ // what was accepted. Identity answers "is this the same finding?"; a metric
488
+ // needs "is it worse than what we accepted?", which is a comparison.
489
+ //
490
+ // Both sides must be present. A metric violation matched against an entry
491
+ // written before this shipped has no accepted value to compare with, and
492
+ // the honest reading of that is the pre-0012 one — the entry was accepted,
493
+ // so it stays accepted until the baseline is regenerated. Treating a
494
+ // missing value as 0 would fail every metric finding in an older baseline
495
+ // and call it a regression.
496
+ const accepted = this.acceptedMeasurements.get(hash);
497
+ if (violation.measured === undefined || accepted === undefined)
498
+ return true;
499
+ // Bug 0171: the entry's number and this run's number must count the same
500
+ // thing before `<=` means anything. When they do not, the finding is NOT
501
+ // known — reporting it is the only honest answer, because the accepted
502
+ // ceiling is denominated in a unit the tool no longer produces.
503
+ // `staleMeasurementFinding` supplies the cause and the remedy so this does
504
+ // not read as fresh rot in the code (ADR-009 rule 2).
505
+ if (!measurementComparable(accepted.unit, violation.measuredUnit))
506
+ return false;
507
+ return violation.measured <= accepted.value;
508
+ }
509
+ /**
510
+ * What the diagnoses in `baseline-diagnostics.ts` need, stated explicitly.
511
+ *
512
+ * An object rather than `this`, because the fields are `private readonly` and
513
+ * a diagnosis should depend on the facts it reads, not on the class that
514
+ * happens to hold them.
515
+ */
516
+ facts() {
517
+ return {
518
+ knownHashes: this.knownHashes,
519
+ knownSubjects: this.knownSubjects,
520
+ root: this.root,
521
+ sourcePath: this.sourcePath,
522
+ hashVersion: this.hashVersion,
523
+ isKnown: (violation) => this.isKnown(violation),
524
+ hasEntry: (violation) => this.hasEntry(violation),
525
+ };
526
+ }
527
+ /**
528
+ * Filter out known violations, returning only new ones.
529
+ *
530
+ * Config-level meta-findings (empty selector/discovery) are never baselined
531
+ * away — a regenerated baseline must not silence them (ADR-008; plan 0067).
532
+ *
533
+ * A baseline that matches **nothing** looks identical to "every accepted
534
+ * violation regressed at once", so that case is reported as what it is —
535
+ * see \{@link unmatchedBaselineFinding\}.
536
+ */
537
+ filterNew(violations) {
538
+ const kept = [];
539
+ let matched = 0;
540
+ let matchable = 0;
541
+ for (const violation of violations) {
542
+ if (violation.bypassFilters === true) {
543
+ kept.push(violation);
544
+ continue;
545
+ }
546
+ matchable += 1;
547
+ // `matched` counts entries the baseline RECOGNISED, not violations it
548
+ // suppressed, and the two differ for a metric finding that regressed past
549
+ // its accepted value (bug 0012). Counting suppression instead made a
550
+ // single-entry baseline whose one metric got worse report "no entry
551
+ // survived: every prior identity is gone" — a false cause, since the
552
+ // identity matched perfectly and only the number moved.
553
+ if (this.hasEntry(violation))
554
+ matched += 1;
555
+ if (this.isKnown(violation))
556
+ continue;
557
+ kept.push(violation);
558
+ }
559
+ // The specific diagnosis SUPERSEDES the generic one, and not merely for
560
+ // tidiness: it disproves it. `unmatchedBaselineFinding` fires on
561
+ // `matched === 0` and, in the same-version case, tells the reader the likely
562
+ // cause is a differently-resolved repository root. A detected description
563
+ // change means a stored SUBJECT matched — and subjects are scrubbed with the
564
+ // same root as hashes — so the root is demonstrably resolving consistently
565
+ // and the root explanation is false. Reporting both would put two
566
+ // contradictory causes in one run, which is the ADR-009 rule 2 defect the
567
+ // withdrawn HASH_VERSION bump already committed once in this area.
568
+ const facts = this.facts();
569
+ const descriptionChange = descriptionChangeFinding(facts, violations);
570
+ const finding = descriptionChange ?? unmatchedBaselineFinding(facts, matched, matchable);
571
+ // Additive, not exclusive: a stale unit and a renamed rule are independent
572
+ // causes that can both be true in one upgrade, and each carries its own
573
+ // remedy. The `??` above is a different situation — there, one diagnosis
574
+ // DISPROVES the other.
575
+ const stale = this.staleMeasurementFinding(violations);
576
+ const meta = [];
577
+ if (stale !== undefined)
578
+ meta.push(stale);
579
+ if (finding !== undefined)
580
+ meta.push(finding);
581
+ return meta.length === 0 ? kept : [...meta, ...kept];
582
+ }
583
+ /**
584
+ * The stale-unit measurements in this run, grouped by the unit pair that
585
+ * changed — so one upgrade is described once however many elements it
586
+ * touched, with the identities carried inside.
587
+ *
588
+ * Extracted from {@link staleMeasurementFinding} because the four
589
+ * disqualifying cases are most of that method's branching, the same reason
590
+ * {@link renamedRuleFor} sits beside {@link descriptionChangeFinding}.
591
+ */
592
+ staleMeasurementsByUnit(violations) {
593
+ const stale = new Map();
594
+ for (const violation of violations) {
595
+ const accepted = this.incomparableAcceptedFor(violation);
596
+ if (accepted === undefined)
597
+ continue;
598
+ const key = `${accepted.unit ?? '(unrecorded)'} -> ${violation.measuredUnit ?? '(none)'}`;
599
+ const named = stale.get(key) ?? [];
600
+ named.push(`${violation.element} (accepted ${String(accepted.value)}, now ${String(violation.measured ?? 0)})`);
601
+ stale.set(key, named);
602
+ }
603
+ return stale;
604
+ }
605
+ /**
606
+ * The accepted measurement this violation matches but CANNOT be compared
607
+ * against, or `undefined` when there is no such conflict — which covers the
608
+ * ordinary cases too: not a metric finding, no matching entry, or a unit that
609
+ * still means what it meant.
610
+ */
611
+ incomparableAcceptedFor(violation) {
612
+ if (violation.bypassFilters === true || violation.measured === undefined)
613
+ return undefined;
614
+ const accepted = this.acceptedMeasurements.get(hashViolation(violation, this.root));
615
+ if (accepted === undefined)
616
+ return undefined;
617
+ if (measurementComparable(accepted.unit, violation.measuredUnit))
618
+ return undefined;
619
+ return accepted;
620
+ }
621
+ /**
622
+ * A meta-finding for baselined metric entries whose accepted measurement is
623
+ * denominated in a unit this version no longer produces — bug 0171.
624
+ *
625
+ * Without it, the upgrade that changed a metric reports the affected classes
626
+ * as ordinary new violations. The author sees findings against code they did
627
+ * not touch, on a rule they had already baselined, with nothing connecting
628
+ * that to the release note — the "eess started reporting something new"
629
+ * failure ADR-009 rule 2 exists to prevent.
630
+ *
631
+ * This fires where `unmatchedBaselineFinding` structurally cannot: those
632
+ * entries MATCH. Identity is intact and only the unit moved, so `matched` is
633
+ * non-zero and that check stays silent by design.
634
+ */
635
+ staleMeasurementFinding(violations) {
636
+ const stale = this.staleMeasurementsByUnit(violations);
637
+ if (stale.size === 0)
638
+ return undefined;
639
+ const where = this.sourcePath ?? 'the baseline file';
640
+ const total = [...stale.values()].reduce((n, xs) => n + xs.length, 0);
641
+ // Identities, never a bare total (ADR-009 rule 4).
642
+ const detail = [...stale.entries()]
643
+ .map(([units, names]) => `\n ${units}:${names.map((n) => `\n ${n}`).join('')}`)
644
+ .join('');
645
+ return {
646
+ rule: 'eess-ts: baseline',
647
+ element: 'baseline',
648
+ file: '',
649
+ line: 0,
650
+ message: `Baseline at ${where} accepted ${String(total)} metric ${total === 1 ? 'finding' : 'findings'} ` +
651
+ `under a measurement this version no longer produces, so ${total === 1 ? 'it is' : 'they are'} ` +
652
+ `being reported rather than silently re-accepted. This is not new rot in your code — the ` +
653
+ `metric changed what it counts:${detail}`,
654
+ because: 'An accepted ceiling is a number in a unit. Comparing this run against a ceiling recorded in a different unit does not measure a regression — it moves the bar, and the build stays green while the code gets worse.',
655
+ suggestion: `Check that each element above is genuinely acceptable at its NEW number, then regenerate: \`npx eess-ts baseline <your-rule-files> --output ${where}\`. Re-accepting without reading it re-baselines whatever drift the old unit was hiding.`,
656
+ bypassFilters: true,
657
+ };
658
+ }
659
+ /** Number of known violations in the baseline */
660
+ get size() {
661
+ return this.knownHashes.size;
662
+ }
663
+ }
664
+ //# sourceMappingURL=baseline.js.map