@shivaedev/quality 0.22.0 → 0.24.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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -0
  3. package/dist/baseline/compare.d.ts +2 -2
  4. package/dist/baseline/compare.d.ts.map +1 -1
  5. package/dist/baseline/compare.js +5 -3
  6. package/dist/baseline/compare.js.map +1 -1
  7. package/dist/baseline/format.d.ts +5 -5
  8. package/dist/baseline/format.d.ts.map +1 -1
  9. package/dist/baseline/format.js +23 -13
  10. package/dist/baseline/format.js.map +1 -1
  11. package/dist/baseline/legacy.d.ts +2 -2
  12. package/dist/baseline/legacy.d.ts.map +1 -1
  13. package/dist/baseline/legacy.js +4 -4
  14. package/dist/baseline/legacy.js.map +1 -1
  15. package/dist/baseline/rewrite.d.ts +1 -1
  16. package/dist/baseline/rewrite.d.ts.map +1 -1
  17. package/dist/baseline/rewrite.js +2 -2
  18. package/dist/baseline/rewrite.js.map +1 -1
  19. package/dist/baseline/update.d.ts +2 -2
  20. package/dist/baseline/update.d.ts.map +1 -1
  21. package/dist/baseline/update.js +24 -18
  22. package/dist/baseline/update.js.map +1 -1
  23. package/dist/cli/args.d.ts +1 -1
  24. package/dist/cli/args.d.ts.map +1 -1
  25. package/dist/cli/args.js +15 -9
  26. package/dist/cli/args.js.map +1 -1
  27. package/dist/cli/baseline.d.ts +1 -1
  28. package/dist/cli/baseline.d.ts.map +1 -1
  29. package/dist/cli/baseline.js +10 -6
  30. package/dist/cli/baseline.js.map +1 -1
  31. package/dist/cli/fix.d.ts +2 -2
  32. package/dist/cli/fix.d.ts.map +1 -1
  33. package/dist/cli/fix.js +34 -40
  34. package/dist/cli/fix.js.map +1 -1
  35. package/dist/cli/lint.d.ts +1 -1
  36. package/dist/cli/lint.d.ts.map +1 -1
  37. package/dist/cli/lint.js +1 -1
  38. package/dist/cli/lint.js.map +1 -1
  39. package/dist/cli/migrate.d.ts.map +1 -1
  40. package/dist/cli/migrate.js +1 -1
  41. package/dist/cli/migrate.js.map +1 -1
  42. package/dist/cli/program.d.ts +9 -0
  43. package/dist/cli/program.d.ts.map +1 -0
  44. package/dist/cli/program.js +44 -0
  45. package/dist/cli/program.js.map +1 -0
  46. package/dist/cli/run-main.d.ts +5 -2
  47. package/dist/cli/run-main.d.ts.map +1 -1
  48. package/dist/cli/run-main.js +16 -12
  49. package/dist/cli/run-main.js.map +1 -1
  50. package/dist/cli/shrink.d.ts +2 -2
  51. package/dist/cli/shrink.d.ts.map +1 -1
  52. package/dist/cli/shrink.js +9 -3
  53. package/dist/cli/shrink.js.map +1 -1
  54. package/dist/cli.js +2 -49
  55. package/dist/cli.js.map +1 -1
  56. package/dist/config/decode.d.ts +1 -1
  57. package/dist/config/decode.d.ts.map +1 -1
  58. package/dist/config/decode.js +13 -9
  59. package/dist/config/decode.js.map +1 -1
  60. package/dist/config/load.d.ts.map +1 -1
  61. package/dist/config/load.js +26 -20
  62. package/dist/config/load.js.map +1 -1
  63. package/dist/config/resolve.d.ts +22 -2
  64. package/dist/config/resolve.d.ts.map +1 -1
  65. package/dist/config/resolve.js +18 -10
  66. package/dist/config/resolve.js.map +1 -1
  67. package/dist/config.d.ts +1 -1
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +3 -1
  70. package/dist/config.js.map +1 -1
  71. package/dist/decoded.d.ts +2 -2
  72. package/dist/decoded.d.ts.map +1 -1
  73. package/dist/decoded.js +7 -5
  74. package/dist/decoded.js.map +1 -1
  75. package/dist/engine/baseline-file.d.ts +1 -1
  76. package/dist/engine/baseline-file.d.ts.map +1 -1
  77. package/dist/engine/baseline-file.js +4 -2
  78. package/dist/engine/baseline-file.js.map +1 -1
  79. package/dist/engine/evaluate.d.ts +2 -2
  80. package/dist/engine/evaluate.d.ts.map +1 -1
  81. package/dist/engine/evaluate.js +5 -3
  82. package/dist/engine/evaluate.js.map +1 -1
  83. package/dist/engine/run-rules.d.ts +1 -1
  84. package/dist/engine/run-rules.d.ts.map +1 -1
  85. package/dist/engine/run-rules.js +24 -14
  86. package/dist/engine/run-rules.js.map +1 -1
  87. package/dist/engine/session.d.ts.map +1 -1
  88. package/dist/engine/session.js +8 -4
  89. package/dist/engine/session.js.map +1 -1
  90. package/dist/engine/violation.d.ts +7 -7
  91. package/dist/engine/violation.d.ts.map +1 -1
  92. package/dist/engine/violation.js +22 -10
  93. package/dist/engine/violation.js.map +1 -1
  94. package/dist/exceptions/registry.d.ts +2 -2
  95. package/dist/exceptions/registry.d.ts.map +1 -1
  96. package/dist/exceptions/registry.js +10 -6
  97. package/dist/exceptions/registry.js.map +1 -1
  98. package/dist/git/base.d.ts +1 -2
  99. package/dist/git/base.d.ts.map +1 -1
  100. package/dist/git/base.js +22 -10
  101. package/dist/git/base.js.map +1 -1
  102. package/dist/git/command.d.ts +2 -2
  103. package/dist/git/command.d.ts.map +1 -1
  104. package/dist/git/command.js +13 -9
  105. package/dist/git/command.js.map +1 -1
  106. package/dist/git/history.d.ts +1 -1
  107. package/dist/git/history.d.ts.map +1 -1
  108. package/dist/git/history.js +5 -3
  109. package/dist/git/history.js.map +1 -1
  110. package/dist/inventory/collect.d.ts +1 -1
  111. package/dist/inventory/collect.d.ts.map +1 -1
  112. package/dist/inventory/collect.js +7 -5
  113. package/dist/inventory/collect.js.map +1 -1
  114. package/dist/inventory/filesystem.d.ts +1 -1
  115. package/dist/inventory/filesystem.d.ts.map +1 -1
  116. package/dist/inventory/filesystem.js +11 -7
  117. package/dist/inventory/filesystem.js.map +1 -1
  118. package/dist/inventory/ignore-scope.d.ts +4 -4
  119. package/dist/inventory/ignore-scope.d.ts.map +1 -1
  120. package/dist/inventory/ignore-scope.js +18 -9
  121. package/dist/inventory/ignore-scope.js.map +1 -1
  122. package/dist/manifests/sorted.d.ts +1 -1
  123. package/dist/manifests/sorted.d.ts.map +1 -1
  124. package/dist/manifests/sorted.js +14 -16
  125. package/dist/manifests/sorted.js.map +1 -1
  126. package/dist/report/plural.d.ts +1 -1
  127. package/dist/report/plural.d.ts.map +1 -1
  128. package/dist/report/plural.js +3 -1
  129. package/dist/report/plural.js.map +1 -1
  130. package/dist/report/render.d.ts +1 -1
  131. package/dist/report/render.d.ts.map +1 -1
  132. package/dist/report/render.js +29 -21
  133. package/dist/report/render.js.map +1 -1
  134. package/dist/report/stale.d.ts +1 -1
  135. package/dist/report/stale.d.ts.map +1 -1
  136. package/dist/report/stale.js +18 -8
  137. package/dist/report/stale.js.map +1 -1
  138. package/dist/rule.d.ts +13 -13
  139. package/dist/rule.d.ts.map +1 -1
  140. package/dist/rule.js +10 -8
  141. package/dist/rule.js.map +1 -1
  142. package/dist/rules/built-in.d.ts +2 -2
  143. package/dist/rules/built-in.d.ts.map +1 -1
  144. package/dist/rules/built-in.js +6 -0
  145. package/dist/rules/built-in.js.map +1 -1
  146. package/dist/rules/comments/kinds.d.ts +4 -4
  147. package/dist/rules/comments/kinds.d.ts.map +1 -1
  148. package/dist/rules/comments/kinds.js +16 -8
  149. package/dist/rules/comments/kinds.js.map +1 -1
  150. package/dist/rules/comments/max-per-file.d.ts.map +1 -1
  151. package/dist/rules/comments/max-per-file.js +13 -9
  152. package/dist/rules/comments/max-per-file.js.map +1 -1
  153. package/dist/rules/comments/pattern-rule.d.ts +1 -1
  154. package/dist/rules/comments/pattern-rule.d.ts.map +1 -1
  155. package/dist/rules/comments/pattern-rule.js +12 -10
  156. package/dist/rules/comments/pattern-rule.js.map +1 -1
  157. package/dist/rules/comments/scan.d.ts +3 -3
  158. package/dist/rules/comments/scan.d.ts.map +1 -1
  159. package/dist/rules/comments/scan.js +23 -19
  160. package/dist/rules/comments/scan.js.map +1 -1
  161. package/dist/rules/max-lines.d.ts.map +1 -1
  162. package/dist/rules/max-lines.js +12 -10
  163. package/dist/rules/max-lines.js.map +1 -1
  164. package/dist/rules/maxDepth.d.ts +2 -0
  165. package/dist/rules/maxDepth.d.ts.map +1 -0
  166. package/dist/rules/maxDepth.js +20 -0
  167. package/dist/rules/maxDepth.js.map +1 -0
  168. package/dist/rules/noBarrel.d.ts +2 -0
  169. package/dist/rules/noBarrel.d.ts.map +1 -0
  170. package/dist/rules/noBarrel.js +14 -0
  171. package/dist/rules/noBarrel.js.map +1 -0
  172. package/dist/rules/stories/origins.d.ts +10 -0
  173. package/dist/rules/stories/origins.d.ts.map +1 -0
  174. package/dist/rules/stories/origins.js +129 -0
  175. package/dist/rules/stories/origins.js.map +1 -0
  176. package/dist/rules/stories/prerequisites.d.ts +4 -0
  177. package/dist/rules/stories/prerequisites.d.ts.map +1 -0
  178. package/dist/rules/stories/prerequisites.js +32 -0
  179. package/dist/rules/stories/prerequisites.js.map +1 -0
  180. package/dist/rules/stories/program.d.ts +5 -0
  181. package/dist/rules/stories/program.d.ts.map +1 -0
  182. package/dist/rules/stories/program.js +22 -0
  183. package/dist/rules/stories/program.js.map +1 -0
  184. package/dist/rules/stories/specs.d.ts +2 -0
  185. package/dist/rules/stories/specs.d.ts.map +1 -0
  186. package/dist/rules/stories/specs.js +80 -0
  187. package/dist/rules/stories/specs.js.map +1 -0
  188. package/dist/rules/suppressions/biome/configs.d.ts +1 -1
  189. package/dist/rules/suppressions/biome/configs.d.ts.map +1 -1
  190. package/dist/rules/suppressions/biome/configs.js +23 -17
  191. package/dist/rules/suppressions/biome/configs.js.map +1 -1
  192. package/dist/rules/suppressions/biome/declarations.d.ts +4 -4
  193. package/dist/rules/suppressions/biome/declarations.d.ts.map +1 -1
  194. package/dist/rules/suppressions/biome/declarations.js +15 -7
  195. package/dist/rules/suppressions/biome/declarations.js.map +1 -1
  196. package/dist/rules/suppressions/biome/json.d.ts +6 -6
  197. package/dist/rules/suppressions/biome/json.d.ts.map +1 -1
  198. package/dist/rules/suppressions/biome/json.js +27 -13
  199. package/dist/rules/suppressions/biome/json.js.map +1 -1
  200. package/dist/rules/suppressions/biome/resolve.d.ts +1 -1
  201. package/dist/rules/suppressions/biome/resolve.d.ts.map +1 -1
  202. package/dist/rules/suppressions/biome/resolve.js +23 -17
  203. package/dist/rules/suppressions/biome/resolve.js.map +1 -1
  204. package/dist/rules/suppressions/biome/weakenings.d.ts +1 -1
  205. package/dist/rules/suppressions/biome/weakenings.d.ts.map +1 -1
  206. package/dist/rules/suppressions/biome/weakenings.js +78 -52
  207. package/dist/rules/suppressions/biome/weakenings.js.map +1 -1
  208. package/dist/rules/suppressions/biome-overrides.d.ts.map +1 -1
  209. package/dist/rules/suppressions/biome-overrides.js +45 -33
  210. package/dist/rules/suppressions/biome-overrides.js.map +1 -1
  211. package/dist/rules/suppressions/directives.d.ts +1 -1
  212. package/dist/rules/suppressions/directives.d.ts.map +1 -1
  213. package/dist/rules/suppressions/directives.js +5 -3
  214. package/dist/rules/suppressions/directives.js.map +1 -1
  215. package/dist/rules/suppressions/no-double-cast.d.ts.map +1 -1
  216. package/dist/rules/suppressions/no-double-cast.js +14 -10
  217. package/dist/rules/suppressions/no-double-cast.js.map +1 -1
  218. package/dist/rules/suppressions/no-inline.d.ts.map +1 -1
  219. package/dist/rules/suppressions/no-inline.js +8 -6
  220. package/dist/rules/suppressions/no-inline.js.map +1 -1
  221. package/dist/rules/suppressions/stylesheet-comments.d.ts +1 -1
  222. package/dist/rules/suppressions/stylesheet-comments.d.ts.map +1 -1
  223. package/dist/rules/suppressions/stylesheet-comments.js +11 -5
  224. package/dist/rules/suppressions/stylesheet-comments.js.map +1 -1
  225. package/dist/rules/syntax.d.ts +1 -1
  226. package/dist/rules/syntax.d.ts.map +1 -1
  227. package/dist/rules/syntax.js +2 -2
  228. package/dist/rules/syntax.js.map +1 -1
  229. package/dist/standard-schema.d.ts +6 -6
  230. package/dist/standard-schema.d.ts.map +1 -1
  231. package/docs/modules.md +19 -1
  232. package/docs/roadmap.md +1 -1
  233. package/docs/rules.md +9 -1
  234. package/package.json +2 -2
  235. package/src/baseline/compare.ts +5 -3
  236. package/src/baseline/format.ts +17 -10
  237. package/src/baseline/legacy.ts +4 -4
  238. package/src/baseline/rewrite.ts +2 -2
  239. package/src/baseline/update.ts +20 -16
  240. package/src/cli/args.ts +15 -10
  241. package/src/cli/baseline.ts +36 -35
  242. package/src/cli/fix.ts +39 -42
  243. package/src/cli/lint.ts +17 -19
  244. package/src/cli/migrate.ts +20 -21
  245. package/src/cli/program.ts +50 -0
  246. package/src/cli/run-main.ts +19 -13
  247. package/src/cli/shrink.ts +11 -8
  248. package/src/cli.ts +2 -51
  249. package/src/config/decode.ts +15 -10
  250. package/src/config/load.ts +44 -37
  251. package/src/config/resolve.ts +32 -27
  252. package/src/config.ts +3 -1
  253. package/src/decoded.ts +5 -4
  254. package/src/engine/baseline-file.ts +14 -12
  255. package/src/engine/evaluate.ts +6 -5
  256. package/src/engine/run-rules.ts +21 -13
  257. package/src/engine/session.ts +32 -25
  258. package/src/engine/violation.ts +22 -11
  259. package/src/exceptions/registry.ts +10 -8
  260. package/src/git/base.ts +45 -37
  261. package/src/git/command.ts +6 -4
  262. package/src/git/history.ts +5 -4
  263. package/src/inventory/collect.ts +39 -34
  264. package/src/inventory/filesystem.ts +38 -30
  265. package/src/inventory/ignore-scope.ts +20 -10
  266. package/src/manifests/sorted.ts +18 -18
  267. package/src/report/plural.ts +3 -1
  268. package/src/report/render.ts +23 -18
  269. package/src/report/stale.ts +33 -25
  270. package/src/rule.ts +25 -19
  271. package/src/rules/built-in.ts +6 -0
  272. package/src/rules/comments/kinds.ts +16 -9
  273. package/src/rules/comments/max-per-file.ts +15 -11
  274. package/src/rules/comments/pattern-rule.ts +5 -4
  275. package/src/rules/comments/scan.ts +18 -15
  276. package/src/rules/max-lines.ts +3 -2
  277. package/src/rules/maxDepth.ts +24 -0
  278. package/src/rules/noBarrel.ts +16 -0
  279. package/src/rules/stories/origins.ts +147 -0
  280. package/src/rules/stories/prerequisites.ts +37 -0
  281. package/src/rules/stories/program.ts +29 -0
  282. package/src/rules/stories/specs.ts +93 -0
  283. package/src/rules/suppressions/biome/configs.ts +23 -17
  284. package/src/rules/suppressions/biome/declarations.ts +15 -10
  285. package/src/rules/suppressions/biome/json.ts +25 -13
  286. package/src/rules/suppressions/biome/resolve.ts +23 -18
  287. package/src/rules/suppressions/biome/weakenings.ts +60 -41
  288. package/src/rules/suppressions/biome-overrides.ts +32 -22
  289. package/src/rules/suppressions/directives.ts +5 -4
  290. package/src/rules/suppressions/no-double-cast.ts +14 -10
  291. package/src/rules/suppressions/no-inline.ts +5 -4
  292. package/src/rules/suppressions/stylesheet-comments.ts +11 -6
  293. package/src/rules/syntax.ts +2 -2
  294. package/src/standard-schema.ts +4 -4
@@ -8,12 +8,15 @@ export interface Violation extends Finding {
8
8
  readonly rule: string;
9
9
  }
10
10
 
11
- export const keyOf = (rule: string, file: string): string => `${rule}\u0000${file}`;
11
+ export function keyOf(rule: string, file: string): string {
12
+ return `${rule}\u0000${file}`;
13
+ }
12
14
 
13
- export const byLocation = (left: Violation, right: Violation): number =>
14
- left.rule.localeCompare(right.rule) || left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0);
15
+ export function byLocation(left: Violation, right: Violation): number {
16
+ return left.rule.localeCompare(right.rule) || left.file.localeCompare(right.file) || (left.line ?? 0) - (right.line ?? 0);
17
+ }
15
18
 
16
- export const groupBy = <TItem>(items: readonly TItem[], key: (item: TItem) => string): ReadonlyMap<string, readonly TItem[]> => {
19
+ export function groupBy<TItem>(items: readonly TItem[], key: (item: TItem) => string): ReadonlyMap<string, readonly TItem[]> {
17
20
  const groups = new Map<string, TItem[]>();
18
21
  for (const item of items) {
19
22
  const group = groups.get(key(item));
@@ -24,7 +27,7 @@ export const groupBy = <TItem>(items: readonly TItem[], key: (item: TItem) => st
24
27
  }
25
28
  }
26
29
  return groups;
27
- };
30
+ }
28
31
 
29
32
  export interface RuleIndex {
30
33
  readonly families: ReadonlySet<string>;
@@ -32,18 +35,26 @@ export interface RuleIndex {
32
35
  readonly unregistrable: ReadonlySet<string>;
33
36
  }
34
37
 
35
- export const covers = (name: string, rule: string): boolean => rule === name || rule.startsWith(`${name}/`);
38
+ export function covers(name: string, rule: string): boolean {
39
+ return rule === name || rule.startsWith(`${name}/`);
40
+ }
36
41
 
37
- const ownerOf = (index: RuleIndex, rule: string): string => [...index.families].find((family) => covers(family, rule)) ?? rule;
42
+ function ownerOf(index: RuleIndex, rule: string): string {
43
+ return [...index.families].find((family) => covers(family, rule)) ?? rule;
44
+ }
38
45
 
39
- export const levelOf = (index: RuleIndex, rule: string): Level | undefined => index.levels.get(ownerOf(index, rule));
46
+ export function levelOf(index: RuleIndex, rule: string): Level | undefined {
47
+ return index.levels.get(ownerOf(index, rule));
48
+ }
40
49
 
41
- export const registrable = (index: RuleIndex, rule: string): boolean => !index.unregistrable.has(ownerOf(index, rule));
50
+ export function registrable(index: RuleIndex, rule: string): boolean {
51
+ return !index.unregistrable.has(ownerOf(index, rule));
52
+ }
42
53
 
43
- export const unusedEntryProblem = (index: RuleIndex, rule: string, unused: string): string => {
54
+ export function unusedEntryProblem(index: RuleIndex, rule: string, unused: string): string {
44
55
  const level = levelOf(index, rule);
45
56
  if (level === undefined) {
46
57
  return "names no known rule";
47
58
  }
48
59
  return level === "off" ? "names a rule that is off" : unused;
49
- };
60
+ }
@@ -15,8 +15,9 @@ const standard = Schema.toStandardSchemaV1(Schema.fromJsonString(Schema.Array(Re
15
15
  parseOptions: { errors: "all", onExcessProperty: "error" },
16
16
  });
17
17
 
18
- export const decodeRegistry = (raw: string | undefined): Promise<Decoded<readonly RegistryEntry[]>> =>
19
- raw === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(standard, raw);
18
+ export function decodeRegistry(raw: string | undefined): Promise<Decoded<readonly RegistryEntry[]>> {
19
+ return raw === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(standard, raw);
20
+ }
20
21
 
21
22
  export interface StaleRegistryEntry {
22
23
  readonly entry: RegistryEntry;
@@ -29,17 +30,18 @@ export interface RegistryCheck {
29
30
  readonly stale: readonly StaleRegistryEntry[];
30
31
  }
31
32
 
32
- const covers = (entry: RegistryEntry, violation: Violation): boolean =>
33
- entry.rule === violation.rule && entry.file === violation.file && (entry.subject === undefined || entry.subject === violation.subject);
33
+ function covers(entry: RegistryEntry, violation: Violation): boolean {
34
+ return entry.rule === violation.rule && entry.file === violation.file && (entry.subject === undefined || entry.subject === violation.subject);
35
+ }
34
36
 
35
37
  const UNREGISTRABLE = "names a rule that takes no exceptions. Fix the code, or baseline the violations while the repository adopts the rule";
36
38
 
37
- const coveringEntry = (entries: readonly RegistryEntry[], violation: Violation): RegistryEntry | undefined => {
39
+ function coveringEntry(entries: readonly RegistryEntry[], violation: Violation): RegistryEntry | undefined {
38
40
  const matching = entries.filter((entry) => covers(entry, violation));
39
41
  return matching.find((entry) => entry.subject !== undefined) ?? matching[0];
40
- };
42
+ }
41
43
 
42
- export const applyRegistry = (violations: readonly Violation[], entries: readonly RegistryEntry[], rules: RuleIndex): RegistryCheck => {
44
+ export function applyRegistry(violations: readonly Violation[], entries: readonly RegistryEntry[], rules: RuleIndex): RegistryCheck {
43
45
  const usable = entries.filter((entry) => registrable(rules, entry.rule));
44
46
  const used = new Set<RegistryEntry>();
45
47
  const kept = violations.filter((violation) => {
@@ -56,4 +58,4 @@ export const applyRegistry = (violations: readonly Violation[], entries: readonl
56
58
  problem: registrable(rules, entry.rule) ? unusedEntryProblem(rules, entry.rule, "matches no violation") : UNREGISTRABLE,
57
59
  }));
58
60
  return { kept, registered: violations.length - kept.length, stale };
59
- };
61
+ }
package/src/git/base.ts CHANGED
@@ -9,54 +9,62 @@ export interface Base {
9
9
 
10
10
  const FALLBACK_BRANCHES = ["origin/main", "origin/master"];
11
11
 
12
- const fail = (message: string) => Effect.fail(new SetupFailure({ message }));
12
+ function fail(message: string) {
13
+ return Effect.fail(new SetupFailure({ message }));
14
+ }
13
15
 
14
- const isCommit = (root: string, ref: string): Effect.Effect<boolean, SetupFailure, Git> =>
15
- Effect.map(git(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]), (result) => result.code === 0);
16
+ function isCommit(root: string, ref: string): Effect.Effect<boolean, SetupFailure, Git> {
17
+ return Effect.map(git(root, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]), (result) => result.code === 0);
18
+ }
16
19
 
17
- const defaultBranch = (root: string): Effect.Effect<string, SetupFailure, Git> =>
18
- Effect.gen(function* () {
19
- const head = yield* git(root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
20
- if (head.code === 0) {
21
- return head.stdout.trim();
22
- }
23
- for (const candidate of FALLBACK_BRANCHES) {
24
- if (yield* isCommit(root, candidate)) {
25
- return candidate;
26
- }
20
+ const defaultBranch = Effect.fn("Git.defaultBranch")(function* (root: string): Effect.fn.Return<string, SetupFailure, Git> {
21
+ const head = yield* git(root, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
22
+ if (head.code === 0) {
23
+ return head.stdout.trim();
24
+ }
25
+ for (const candidate of FALLBACK_BRANCHES) {
26
+ if (yield* isCommit(root, candidate)) {
27
+ return candidate;
27
28
  }
28
- return yield* fail(
29
- "cannot find the default branch: origin/HEAD, origin/main and origin/master are missing. Pass --against <ref>, or run `git remote set-head origin --auto`.",
30
- );
31
- });
29
+ }
30
+ return yield* fail(
31
+ "cannot find the default branch: origin/HEAD, origin/main and origin/master are missing. Pass --against <ref>, or run `git remote set-head origin --auto`.",
32
+ );
33
+ });
32
34
 
33
- const isShallow = (root: string): Effect.Effect<boolean, SetupFailure, Git> =>
34
- Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
35
+ function isShallow(root: string): Effect.Effect<boolean, SetupFailure, Git> {
36
+ return Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
37
+ }
35
38
 
36
- const mergeBase = (root: string, ref: string): Effect.Effect<string | undefined, SetupFailure, Git> =>
37
- Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
39
+ function mergeBase(root: string, ref: string): Effect.Effect<string | undefined, SetupFailure, Git> {
40
+ return Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
41
+ }
38
42
 
39
- const missingMergeBase = (root: string, ref: string): Effect.Effect<never, SetupFailure, Git> =>
40
- Effect.flatMap(isShallow(root), (shallow) =>
43
+ function missingMergeBase(root: string, ref: string): Effect.Effect<never, SetupFailure, Git> {
44
+ return Effect.flatMap(isShallow(root), (shallow) =>
41
45
  fail(
42
46
  shallow
43
47
  ? `the clone is shallow and does not reach the merge base of HEAD and ${ref}. Fetch the history first: \`git fetch --unshallow\`.`
44
48
  : `HEAD and ${ref} share no history.`,
45
49
  ),
46
50
  );
51
+ }
47
52
 
48
- const reachMergeBase = (root: string, ref: string): Effect.Effect<string, SetupFailure, Git> =>
49
- Effect.flatMap(mergeBase(root, ref), (found) => (found === undefined ? missingMergeBase(root, ref) : Effect.succeed(found)));
53
+ function reachMergeBase(root: string, ref: string): Effect.Effect<string, SetupFailure, Git> {
54
+ return Effect.flatMap(mergeBase(root, ref), (found) => (found === undefined ? missingMergeBase(root, ref) : Effect.succeed(found)));
55
+ }
50
56
 
51
- export const resolveBase = (root: string, against: string | undefined): Effect.Effect<Base, SetupFailure, Git> =>
52
- Effect.gen(function* () {
53
- const inside = yield* git(root, ["rev-parse", "--is-inside-work-tree"]);
54
- if (inside.code !== 0 || inside.stdout.trim() !== "true") {
55
- return yield* fail(`${root} is not in a git work tree; moves are followed through git history.`);
56
- }
57
- const ref = against ?? (yield* defaultBranch(root));
58
- if (!(yield* isCommit(root, ref))) {
59
- return yield* fail(`${ref} names no commit in this clone. Fetch it first, or pass another --against <ref>.`);
60
- }
61
- return { commit: yield* reachMergeBase(root, ref), ref };
62
- });
57
+ export const resolveBase = Effect.fn("Git.resolveBase")(function* (
58
+ root: string,
59
+ against: string | undefined,
60
+ ): Effect.fn.Return<Base, SetupFailure, Git> {
61
+ const inside = yield* git(root, ["rev-parse", "--is-inside-work-tree"]);
62
+ if (inside.code !== 0 || inside.stdout.trim() !== "true") {
63
+ return yield* fail(`${root} is not in a git work tree; moves are followed through git history.`);
64
+ }
65
+ const ref = against ?? (yield* defaultBranch(root));
66
+ if (!(yield* isCommit(root, ref))) {
67
+ return yield* fail(`${ref} names no commit in this clone. Fetch it first, or pass another --against <ref>.`);
68
+ }
69
+ return { commit: yield* reachMergeBase(root, ref), ref };
70
+ });
@@ -10,8 +10,8 @@ export interface GitResult {
10
10
 
11
11
  export type Git = ChildProcessSpawner.ChildProcessSpawner;
12
12
 
13
- export const git = (cwd: string, args: readonly string[]): Effect.Effect<GitResult, SetupFailure, Git> =>
14
- Effect.scoped(
13
+ export function git(cwd: string, args: readonly string[]): Effect.Effect<GitResult, SetupFailure, Git> {
14
+ return Effect.scoped(
15
15
  Effect.gen(function* () {
16
16
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
17
17
  const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, stdin: "ignore" }));
@@ -22,10 +22,12 @@ export const git = (cwd: string, args: readonly string[]): Effect.Effect<GitResu
22
22
  return { code: Number(code), stderr, stdout };
23
23
  }),
24
24
  ).pipe(Effect.mapError((error) => new SetupFailure({ message: `cannot run git: ${error.message}` })));
25
+ }
25
26
 
26
- export const gitOrFail = (cwd: string, args: readonly string[]): Effect.Effect<string, SetupFailure, Git> =>
27
- Effect.flatMap(git(cwd, args), (result) =>
27
+ export function gitOrFail(cwd: string, args: readonly string[]): Effect.Effect<string, SetupFailure, Git> {
28
+ return Effect.flatMap(git(cwd, args), (result) =>
28
29
  result.code === 0
29
30
  ? Effect.succeed(result.stdout)
30
31
  : Effect.fail(new SetupFailure({ message: `git ${args[0]} failed: ${result.stderr.trim() || `exit code ${result.code}`}` })),
31
32
  );
33
+ }
@@ -7,7 +7,7 @@ export interface Changes {
7
7
  readonly moves: ReadonlyMap<string, string>;
8
8
  }
9
9
 
10
- const parse = (output: string): Changes => {
10
+ function parse(output: string): Changes {
11
11
  const fields = output.split("\0");
12
12
  const files = new Set<string>();
13
13
  const moves = new Map<string, string>();
@@ -24,11 +24,12 @@ const parse = (output: string): Changes => {
24
24
  index += paths.length + 1;
25
25
  }
26
26
  return { files, moves };
27
- };
27
+ }
28
28
 
29
29
  // Paths are relative to the root, and only tracked files count: a move is seen once both of its sides are staged.
30
- export const changesSince = (root: string, revision: readonly string[]): Effect.Effect<Changes, SetupFailure, Git> =>
31
- Effect.map(
30
+ export function changesSince(root: string, revision: readonly string[]): Effect.Effect<Changes, SetupFailure, Git> {
31
+ return Effect.map(
32
32
  gitOrFail(root, ["diff", "--no-color", "--no-ext-diff", "--find-renames", "--name-status", "-z", "--relative", ...revision, "--"]),
33
33
  parse,
34
34
  );
35
+ }
@@ -19,42 +19,47 @@ export interface Inventory {
19
19
 
20
20
  const READ_CONCURRENCY = 16;
21
21
 
22
- export const linesOf = (text: string): readonly string[] => {
22
+ export function linesOf(text: string): readonly string[] {
23
23
  const lines = text.split("\n");
24
24
  return lines.at(-1) === "" ? lines.slice(0, -1) : lines;
25
- };
25
+ }
26
26
 
27
- const zoneFiles = (root: string, zone: string): Effect.Effect<readonly string[], FilesystemFailure, FileSystem.FileSystem> =>
28
- Effect.gen(function* () {
29
- const fs = yield* FileSystem.FileSystem;
30
- const path = join(root, zone);
31
- if (posix(relative(root, path)).startsWith("..")) {
32
- return yield* new FilesystemFailure({ message: `source "${zone}" lies outside the repository at ${root}`, path });
33
- }
34
- const info = yield* orWhenAbsent<FileSystem.File.Info | undefined>(path, fs.stat(path), undefined);
35
- if (info === undefined) {
36
- return yield* new FilesystemFailure({ message: `source "${zone}" does not exist in ${root}`, path });
37
- }
38
- return info.type === "Directory" ? yield* walk(path, yield* scopeAbove(root, path)) : [path];
39
- });
27
+ const zoneFiles = Effect.fn("Inventory.zoneFiles")(function* (
28
+ root: string,
29
+ zone: string,
30
+ ): Effect.fn.Return<readonly string[], FilesystemFailure, FileSystem.FileSystem> {
31
+ const fs = yield* FileSystem.FileSystem;
32
+ const path = join(root, zone);
33
+ if (posix(relative(root, path)).startsWith("..")) {
34
+ return yield* new FilesystemFailure({ message: `source "${zone}" lies outside the repository at ${root}`, path });
35
+ }
36
+ const info = yield* orWhenAbsent<FileSystem.File.Info | undefined>(path, fs.stat(path), undefined);
37
+ if (info === undefined) {
38
+ return yield* new FilesystemFailure({ message: `source "${zone}" does not exist in ${root}`, path });
39
+ }
40
+ return info.type === "Directory" ? yield* walk(path, yield* scopeAbove(root, path)) : [path];
41
+ });
40
42
 
41
- const readSource = (root: string, path: string): Effect.Effect<SourceFile, FilesystemFailure, FileSystem.FileSystem> =>
42
- Effect.map(readRequiredText(join(root, path)), (text) => ({ lines: linesOf(text), path, text }));
43
+ function readSource(root: string, path: string): Effect.Effect<SourceFile, FilesystemFailure, FileSystem.FileSystem> {
44
+ return Effect.map(readRequiredText(join(root, path)), (text) => ({ lines: linesOf(text), path, text }));
45
+ }
43
46
 
44
- export const collectInventory = (root: string, scope: InventoryScope): Effect.Effect<Inventory, FilesystemFailure, FileSystem.FileSystem> =>
45
- Effect.gen(function* () {
46
- const excluded = ignore().add([...scope.exclude]);
47
- const zones = yield* Effect.forEach(scope.sources, (zone) => zoneFiles(root, zone), { concurrency: READ_CONCURRENCY });
48
- const relativePaths = zones.flat().map((absolute) => posix(relative(root, absolute)));
49
- const files = [...new Set(relativePaths)]
50
- .filter((path) => !excluded.ignores(path))
51
- .sort((left, right) => {
52
- if (left === right) {
53
- return 0;
54
- }
55
- return left < right ? -1 : 1;
56
- });
57
- const sourcePaths = files.filter((path) => scope.extensions.some((extension) => path.endsWith(extension)));
58
- const sources = yield* Effect.forEach(sourcePaths, (path) => readSource(root, path), { concurrency: READ_CONCURRENCY });
59
- return { files, sources };
60
- });
47
+ export const collectInventory = Effect.fn("Inventory.collectInventory")(function* (
48
+ root: string,
49
+ scope: InventoryScope,
50
+ ): Effect.fn.Return<Inventory, FilesystemFailure, FileSystem.FileSystem> {
51
+ const excluded = ignore().add([...scope.exclude]);
52
+ const zones = yield* Effect.forEach(scope.sources, (zone) => zoneFiles(root, zone), { concurrency: READ_CONCURRENCY });
53
+ const relativePaths = zones.flat().map((absolute) => posix(relative(root, absolute)));
54
+ const files = [...new Set(relativePaths)]
55
+ .filter((path) => !excluded.ignores(path))
56
+ .sort((left, right) => {
57
+ if (left === right) {
58
+ return 0;
59
+ }
60
+ return left < right ? -1 : 1;
61
+ });
62
+ const sourcePaths = files.filter((path) => scope.extensions.some((extension) => path.endsWith(extension)));
63
+ const sources = yield* Effect.forEach(sourcePaths, (path) => readSource(root, path), { concurrency: READ_CONCURRENCY });
64
+ return { files, sources };
65
+ });
@@ -10,38 +10,46 @@ export class FilesystemFailure extends Data.TaggedError("FilesystemFailure")<{
10
10
  }> {}
11
11
 
12
12
  // Effect maps ENOTDIR and EISDIR to BadResource; the original errno remains on the cause.
13
- const errnoOf = (error: PlatformError): string => {
13
+ function errnoOf(error: PlatformError): string {
14
14
  const cause: unknown = error.reason.cause;
15
15
  return typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : "";
16
- };
16
+ }
17
17
 
18
- const failure = (path: string, error: PlatformError): FilesystemFailure =>
19
- new FilesystemFailure({ message: `cannot read ${path}: ${error.message}`, path });
18
+ function failure(path: string, error: PlatformError): FilesystemFailure {
19
+ return new FilesystemFailure({ message: `cannot read ${path}: ${error.message}`, path });
20
+ }
20
21
 
21
- export const orWhenAbsent = <Value>(
22
+ export function orWhenAbsent<TValue>(
22
23
  path: string,
23
- action: Effect.Effect<Value, PlatformError, FileSystem.FileSystem>,
24
- whenAbsent: Value,
25
- ): Effect.Effect<Value, FilesystemFailure, FileSystem.FileSystem> =>
26
- Effect.catchTag(action, "PlatformError", (error) => (ABSENT.has(errnoOf(error)) ? Effect.succeed(whenAbsent) : Effect.fail(failure(path, error))));
27
-
28
- export const readOptionalText = (path: string): Effect.Effect<string | undefined, FilesystemFailure, FileSystem.FileSystem> =>
29
- Effect.gen(function* () {
30
- const fs = yield* FileSystem.FileSystem;
31
- return yield* orWhenAbsent<string | undefined>(path, fs.readFileString(path), undefined);
32
- });
33
-
34
- export const readRequiredText = (path: string): Effect.Effect<string, FilesystemFailure, FileSystem.FileSystem> =>
35
- Effect.gen(function* () {
36
- const fs = yield* FileSystem.FileSystem;
37
- return yield* Effect.mapError(fs.readFileString(path), (error) =>
38
- ABSENT.has(errnoOf(error)) ? new FilesystemFailure({ message: `required input is missing: ${path}`, path }) : failure(path, error),
39
- );
40
- });
41
-
42
- export const writeText = (path: string, text: string): Effect.Effect<void, FilesystemFailure, FileSystem.FileSystem> =>
43
- Effect.gen(function* () {
44
- const fs = yield* FileSystem.FileSystem;
45
- const written = Effect.andThen(fs.makeDirectory(dirname(path), { recursive: true }), fs.writeFileString(path, text));
46
- return yield* Effect.mapError(written, (error) => new FilesystemFailure({ message: `cannot write ${path}: ${error.message}`, path }));
47
- });
24
+ action: Effect.Effect<TValue, PlatformError, FileSystem.FileSystem>,
25
+ whenAbsent: TValue,
26
+ ): Effect.Effect<TValue, FilesystemFailure, FileSystem.FileSystem> {
27
+ return Effect.catchTag(action, "PlatformError", (error) =>
28
+ ABSENT.has(errnoOf(error)) ? Effect.succeed(whenAbsent) : Effect.fail(failure(path, error)),
29
+ );
30
+ }
31
+
32
+ export const readOptionalText = Effect.fn("Inventory.readOptionalText")(function* (
33
+ path: string,
34
+ ): Effect.fn.Return<string | undefined, FilesystemFailure, FileSystem.FileSystem> {
35
+ const fs = yield* FileSystem.FileSystem;
36
+ return yield* orWhenAbsent<string | undefined>(path, fs.readFileString(path), undefined);
37
+ });
38
+
39
+ export const readRequiredText = Effect.fn("Inventory.readRequiredText")(function* (
40
+ path: string,
41
+ ): Effect.fn.Return<string, FilesystemFailure, FileSystem.FileSystem> {
42
+ const fs = yield* FileSystem.FileSystem;
43
+ return yield* Effect.mapError(fs.readFileString(path), (error) =>
44
+ ABSENT.has(errnoOf(error)) ? new FilesystemFailure({ message: `required input is missing: ${path}`, path }) : failure(path, error),
45
+ );
46
+ });
47
+
48
+ export const writeText = Effect.fn("Inventory.writeText")(function* (
49
+ path: string,
50
+ text: string,
51
+ ): Effect.fn.Return<void, FilesystemFailure, FileSystem.FileSystem> {
52
+ const fs = yield* FileSystem.FileSystem;
53
+ const written = Effect.andThen(fs.makeDirectory(dirname(path), { recursive: true }), fs.writeFileString(path, text));
54
+ return yield* Effect.mapError(written, (error) => new FilesystemFailure({ message: `cannot write ${path}: ${error.message}`, path }));
55
+ });
@@ -15,17 +15,23 @@ export interface IgnoreScope {
15
15
 
16
16
  export const emptyScope: IgnoreScope = { boundary: undefined, matchers: [] };
17
17
 
18
- export const posix = (path: string): string => path.replaceAll("\\", "/");
18
+ export function posix(path: string): string {
19
+ return path.replaceAll("\\", "/");
20
+ }
19
21
 
20
- export const withIgnoreFile = (scope: IgnoreScope, base: string, contents: string): IgnoreScope => ({
21
- boundary: scope.boundary,
22
- matchers: [...scope.matchers, { base, rules: ignore().add(contents) }],
23
- });
22
+ export function withIgnoreFile(scope: IgnoreScope, base: string, contents: string): IgnoreScope {
23
+ return {
24
+ boundary: scope.boundary,
25
+ matchers: [...scope.matchers, { base, rules: ignore().add(contents) }],
26
+ };
27
+ }
24
28
 
25
29
  // Re-including a directory resets directory rules below that boundary; filename rules still apply.
26
- export const insideKept = (scope: IgnoreScope, directory: string): IgnoreScope => ({ boundary: directory, matchers: scope.matchers });
30
+ export function insideKept(scope: IgnoreScope, directory: string): IgnoreScope {
31
+ return { boundary: directory, matchers: scope.matchers };
32
+ }
27
33
 
28
- const matcherVerdict = (matcher: IgnoreMatcher, boundary: string | undefined, path: string, directory: boolean): Verdict => {
34
+ function matcherVerdict(matcher: IgnoreMatcher, boundary: string | undefined, path: string, directory: boolean): Verdict {
29
35
  const anchor = boundary !== undefined && boundary.length > matcher.base.length ? boundary : matcher.base;
30
36
  const rel = posix(relative(anchor, path));
31
37
  if (rel === "" || rel.startsWith("..")) {
@@ -37,7 +43,11 @@ const matcherVerdict = (matcher: IgnoreMatcher, boundary: string | undefined, pa
37
43
  return "ignored";
38
44
  }
39
45
  return result.unignored ? "kept" : "unknown";
40
- };
46
+ }
41
47
 
42
- export const verdictFor = (scope: IgnoreScope, path: string, directory: boolean): Verdict =>
43
- scope.matchers.map((matcher) => matcherVerdict(matcher, scope.boundary, path, directory)).findLast((verdict) => verdict !== "unknown") ?? "unknown";
48
+ export function verdictFor(scope: IgnoreScope, path: string, directory: boolean): Verdict {
49
+ return (
50
+ scope.matchers.map((matcher) => matcherVerdict(matcher, scope.boundary, path, directory)).findLast((verdict) => verdict !== "unknown")
51
+ ?? "unknown"
52
+ );
53
+ }
@@ -26,21 +26,21 @@ export function sortingOf(text: string): Sorting {
26
26
  return sorted === text ? { _tag: "Sorted" } : { _tag: "Unsorted", sorted };
27
27
  }
28
28
 
29
- export function sortManifests(root: string): Effect.Effect<number, FilesystemFailure, FileSystem.FileSystem> {
30
- return Effect.gen(function* () {
31
- const fs = yield* FileSystem.FileSystem;
32
- const manifests = yield* manifestsIn(root);
33
- const written = yield* Effect.forEach(manifests, (path) =>
34
- Effect.gen(function* () {
35
- const absolute = join(root, path);
36
- const sorting = sortingOf(yield* fs.readFileString(absolute));
37
- if (sorting._tag !== "Unsorted") {
38
- return false;
39
- }
40
- yield* fs.writeFileString(absolute, sorting.sorted);
41
- return true;
42
- }).pipe(Effect.mapError((error) => new FilesystemFailure({ message: `cannot sort ${path}: ${error.message}`, path }))),
43
- );
44
- return written.filter(Boolean).length;
45
- });
46
- }
29
+ export const sortManifests = Effect.fn("Manifests.sortManifests")(function* (
30
+ root: string,
31
+ ): Effect.fn.Return<number, FilesystemFailure, FileSystem.FileSystem> {
32
+ const fs = yield* FileSystem.FileSystem;
33
+ const manifests = yield* manifestsIn(root);
34
+ const written = yield* Effect.forEach(manifests, (path) =>
35
+ Effect.gen(function* () {
36
+ const absolute = join(root, path);
37
+ const sorting = sortingOf(yield* fs.readFileString(absolute));
38
+ if (sorting._tag !== "Unsorted") {
39
+ return false;
40
+ }
41
+ yield* fs.writeFileString(absolute, sorting.sorted);
42
+ return true;
43
+ }).pipe(Effect.mapError((error) => new FilesystemFailure({ message: `cannot sort ${path}: ${error.message}`, path }))),
44
+ );
45
+ return written.filter(Boolean).length;
46
+ });
@@ -1 +1,3 @@
1
- export const plural = (count: number, singular: string, many = `${singular}s`): string => `${count} ${count === 1 ? singular : many}`;
1
+ export function plural(count: number, singular: string, many = `${singular}s`): string {
2
+ return `${count} ${count === 1 ? singular : many}`;
3
+ }
@@ -16,18 +16,21 @@ export interface ReportContext {
16
16
 
17
17
  const LISTED_FILES = 5;
18
18
 
19
- const locate = (violation: Violation): string => (violation.line === undefined ? violation.file : `${violation.file}:${violation.line}`);
19
+ function locate(violation: Violation): string {
20
+ return violation.line === undefined ? violation.file : `${violation.file}:${violation.line}`;
21
+ }
20
22
 
21
- const regressionNote = (regression: Regression): string =>
22
- ` ${regression.entry.file} is over its baseline: ${regression.count} against ${regression.entry.count} baselined.`;
23
+ function regressionNote(regression: Regression): string {
24
+ return ` ${regression.entry.file} is over its baseline: ${regression.count} against ${regression.entry.count} baselined.`;
25
+ }
23
26
 
24
- const heading = (label: string, rule: string, violations: readonly Violation[], context: ReportContext): readonly string[] => {
27
+ function heading(label: string, rule: string, violations: readonly Violation[], context: ReportContext): readonly string[] {
25
28
  const description = context.descriptions.get(rule);
26
29
  return [`${label} ${rule} (${violations.length})`, ...(description === undefined ? [] : [` ${description}`])];
27
- };
30
+ }
28
31
 
29
- const detailed = (violations: readonly Violation[], regressions: ReadonlyMap<string, Regression>): readonly string[] =>
30
- [...groupBy(violations, (violation) => violation.file).values()].flatMap((inFile) => {
32
+ function detailed(violations: readonly Violation[], regressions: ReadonlyMap<string, Regression>): readonly string[] {
33
+ return [...groupBy(violations, (violation) => violation.file).values()].flatMap((inFile) => {
31
34
  const first = inFile[0];
32
35
  const regression = first === undefined ? undefined : regressions.get(keyOf(first.rule, first.file));
33
36
  return [
@@ -35,33 +38,35 @@ const detailed = (violations: readonly Violation[], regressions: ReadonlyMap<str
35
38
  ...(regression === undefined ? [] : [regressionNote(regression)]),
36
39
  ];
37
40
  });
41
+ }
38
42
 
39
- const summarized = (violations: readonly Violation[]): readonly string[] => {
43
+ function summarized(violations: readonly Violation[]): readonly string[] {
40
44
  const files = [...groupBy(violations, (violation) => violation.file).entries()].sort(([, left], [, right]) => right.length - left.length);
41
45
  const listed = files.slice(0, LISTED_FILES).map(([file, inFile]) => `${file} (${inFile.length})`);
42
46
  const rest = files.length - listed.length;
43
47
  return [` ${listed.join(", ")}${rest > 0 ? `, and ${plural(rest, "more file", "more files")}` : ""}. --warnings all lists each one.`];
44
- };
48
+ }
45
49
 
46
- const ruleSections = (
50
+ function ruleSections(
47
51
  label: string,
48
52
  violations: readonly Violation[],
49
53
  context: ReportContext,
50
54
  body: (inRule: readonly Violation[]) => readonly string[],
51
- ): readonly string[] =>
52
- [...groupBy(violations, (violation) => violation.rule).entries()].map(([rule, inRule]) =>
55
+ ): readonly string[] {
56
+ return [...groupBy(violations, (violation) => violation.rule).entries()].map(([rule, inRule]) =>
53
57
  [...heading(label, rule, inRule, context), ...body(inRule)].join("\n"),
54
58
  );
59
+ }
55
60
 
56
- const failures = (outcome: Outcome): string => {
61
+ function failures(outcome: Outcome): string {
57
62
  const stale = outcome.staleBaseline.length + outcome.staleRegistry.length;
58
63
  return [
59
64
  ...(outcome.errors.length > 0 ? [plural(outcome.errors.length, "error")] : []),
60
65
  ...(stale > 0 ? [plural(stale, "stale entry", "stale entries")] : []),
61
66
  ].join(" and ");
62
- };
67
+ }
63
68
 
64
- const summary = (outcome: Outcome, context: ReportContext): string => {
69
+ function summary(outcome: Outcome, context: ReportContext): string {
65
70
  const verdict = passes(outcome) ? "quality: passed" : `quality: failed with ${failures(outcome)}`;
66
71
  const warnings = outcome.warnings.length > 0 ? `; ${plural(outcome.warnings.length, "warning")} from rules still at warn` : "";
67
72
  const hidden = [
@@ -71,7 +76,7 @@ const summary = (outcome: Outcome, context: ReportContext): string => {
71
76
  const suppressed =
72
77
  hidden.length === 0 ? "" : `; ${hidden.join(" and ")} ${outcome.baselined + outcome.registered === 1 ? "violation" : "violations"} not shown`;
73
78
  return `${verdict}${warnings}. ${plural(context.checked, "source file")} checked${suppressed}.`;
74
- };
79
+ }
75
80
 
76
81
  function rerecord(outcome: Outcome): readonly string[] {
77
82
  const rules = [...new Set(outcome.errors.map((violation) => violation.rule))].sort((left, right) => left.localeCompare(right));
@@ -84,7 +89,7 @@ function rerecord(outcome: Outcome): readonly string[] {
84
89
  ];
85
90
  }
86
91
 
87
- export const render = (outcome: Outcome, context: ReportContext): string => {
92
+ export function render(outcome: Outcome, context: ReportContext): string {
88
93
  const regressions = new Map(outcome.regressions.map((regression) => [keyOf(regression.entry.rule, regression.entry.file), regression]));
89
94
  const warningBody = context.warnings === "all" ? (inRule: readonly Violation[]) => detailed(inRule, regressions) : summarized;
90
95
  return [
@@ -94,4 +99,4 @@ export const render = (outcome: Outcome, context: ReportContext): string => {
94
99
  summary(outcome, context),
95
100
  ...rerecord(outcome),
96
101
  ].join("\n\n");
97
- };
102
+ }