@prestack/thread 0.1.21 → 0.1.22

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 (137) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.js +6 -3
  4. package/dist/core/generators/commit.js +55 -3
  5. package/dist/core/generators/dev.d.ts +1 -0
  6. package/dist/core/generators/dev.js +68 -0
  7. package/dist/core/tree.d.ts +1 -0
  8. package/dist/core/tree.js +7 -1
  9. package/dist/derivation/generators/build.d.ts +3 -1
  10. package/dist/derivation/generators/build.js +103 -22
  11. package/dist/derivation/generators/release.js +74 -26
  12. package/dist/derivation/release.js +3 -3
  13. package/dist/integrations/cli.js +69 -10
  14. package/dist/integrations/pglite.js +13 -3
  15. package/dist/integrations/target.d.ts +3 -0
  16. package/dist/integrity/attach.js +1 -5
  17. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  18. package/dist/operations/access.js +3 -2
  19. package/dist/operations/capabilities.d.ts +2 -0
  20. package/dist/operations/capabilities.js +2 -0
  21. package/dist/operations/generators/capability.js +234 -24
  22. package/dist/operations/generators/compile.js +150 -50
  23. package/dist/operations/generators/seed.d.ts +3 -0
  24. package/dist/operations/generators/seed.js +25 -3
  25. package/dist/operations/nodes.d.ts +2 -2
  26. package/package.json +1 -1
  27. package/dist/core/actor.ts +0 -21
  28. package/dist/core/attach.ts +0 -613
  29. package/dist/core/chain.ts +0 -145
  30. package/dist/core/condition.ts +0 -227
  31. package/dist/core/descend.ts +0 -1185
  32. package/dist/core/errors/catalogue.ts +0 -314
  33. package/dist/core/errors/classes.ts +0 -51
  34. package/dist/core/errors/error.ts +0 -152
  35. package/dist/core/errors/namespaces.ts +0 -19
  36. package/dist/core/finding.ts +0 -37
  37. package/dist/core/generators/commit.ts +0 -157
  38. package/dist/core/init.ts +0 -222
  39. package/dist/core/invoked.ts +0 -52
  40. package/dist/core/round.ts +0 -74
  41. package/dist/core/rows.ts +0 -506
  42. package/dist/core/scope.ts +0 -20
  43. package/dist/core/thread.ts +0 -29
  44. package/dist/core/tree.ts +0 -90
  45. package/dist/core/types.ts +0 -351
  46. package/dist/derivation/attach.ts +0 -57
  47. package/dist/derivation/generators/build.ts +0 -371
  48. package/dist/derivation/generators/check.ts +0 -118
  49. package/dist/derivation/generators/determinism.ts +0 -145
  50. package/dist/derivation/generators/release.ts +0 -236
  51. package/dist/derivation/release.ts +0 -43
  52. package/dist/integrations/attach.ts +0 -187
  53. package/dist/integrations/cli.ts +0 -606
  54. package/dist/integrations/declared.ts +0 -34
  55. package/dist/integrations/disk.ts +0 -466
  56. package/dist/integrations/generators/read.ts +0 -127
  57. package/dist/integrations/generators/vault.ts +0 -210
  58. package/dist/integrations/https.ts +0 -216
  59. package/dist/integrations/pglite.ts +0 -206
  60. package/dist/integrations/secret.ts +0 -54
  61. package/dist/integrations/target.ts +0 -672
  62. package/dist/integrations/vault.ts +0 -148
  63. package/dist/integrity/attach.ts +0 -150
  64. package/dist/integrity/check-threads.ts +0 -166
  65. package/dist/integrity/constraint.ts +0 -1246
  66. package/dist/integrity/generators/compiler.ts +0 -138
  67. package/dist/integrity/generators/gate.ts +0 -227
  68. package/dist/integrity/generators/issues.ts +0 -160
  69. package/dist/integrity/generators/publish.ts +0 -233
  70. package/dist/integrity/generators/trial.ts +0 -359
  71. package/dist/integrity/lint.ts +0 -214
  72. package/dist/integrity/lsp/bin.ts +0 -44
  73. package/dist/integrity/lsp/diagnostics.ts +0 -92
  74. package/dist/integrity/lsp/findings.ts +0 -63
  75. package/dist/integrity/lsp/lint.ts +0 -124
  76. package/dist/integrity/lsp/protocol.ts +0 -78
  77. package/dist/integrity/lsp/server.ts +0 -224
  78. package/dist/integrity/slopnator.ts +0 -324
  79. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  80. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  81. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  82. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  83. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  84. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  85. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  86. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  87. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  88. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  89. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  90. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  91. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  92. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  93. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  94. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  95. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  96. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  97. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  98. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  99. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  100. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  101. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  102. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  103. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  104. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  105. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  106. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  107. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  108. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  109. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  110. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  111. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  112. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  113. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  114. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  115. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  116. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  117. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  118. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  119. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  120. package/dist/integrity/sweep.ts +0 -395
  121. package/dist/knowledge/attach.ts +0 -327
  122. package/dist/knowledge/generators/docsgen.ts +0 -186
  123. package/dist/knowledge/generators/glossary.ts +0 -73
  124. package/dist/knowledge/glossary.ts +0 -6
  125. package/dist/operations/access.ts +0 -131
  126. package/dist/operations/attach.ts +0 -408
  127. package/dist/operations/capabilities.ts +0 -88
  128. package/dist/operations/dispatch.ts +0 -294
  129. package/dist/operations/error-results.ts +0 -31
  130. package/dist/operations/generators/capability.ts +0 -1054
  131. package/dist/operations/generators/compile.ts +0 -701
  132. package/dist/operations/generators/permits.ts +0 -229
  133. package/dist/operations/generators/seed.ts +0 -359
  134. package/dist/operations/generators/trial.ts +0 -666
  135. package/dist/operations/nodes.ts +0 -53
  136. package/dist/operations/operations.ts +0 -91
  137. package/dist/operations/result.ts +0 -28
@@ -1,52 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
- import { here, parses, rowsIn } from './sl_unthreaded.ts'
3
-
4
- export const slopnator: Slopnator = {
5
- holds: (_node, at) => {
6
- const path = here(at.filename)
7
-
8
- if (!parses(path)) return false
9
-
10
- let governing: readonly { readonly list?: string; readonly id?: string; readonly version?: number }[]
11
- try {
12
- governing = rowsIn(at.text)
13
- } catch {
14
- return 'this file cannot be parsed, so the declarations it governs with were never read and nothing about their versions was measured'
15
- }
16
-
17
- for (const node of governing) {
18
- if (node.list !== undefined || node.version !== undefined) continue
19
- return (
20
- `"${node.id ?? ''}" is annotated with the interface that makes other things conform to it and states no \`version\`, ` +
21
- 'so a change to what it governs is a diff nobody downstream can detect and nothing can be carried forward from the shape it used to have'
22
- )
23
- }
24
-
25
- return false
26
- },
27
- proves: {
28
- wrong: [
29
- {
30
- files: [
31
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
32
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_unversioned_probe.ts',\n },\n ],\n}\n" },
33
- { at: 'threads/_unversioned_probe.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const L: Threaded = {\n id: 'a-law',\n at: 'thread/integrity/constraint.ts',\n describes: 'A rule that must be enforced.',\n}\n" },
34
- { at: 'thread/integrity/constraint.ts', source: 'export const CONSTRAINTS = []\n' },
35
- ],
36
- at: 'threads/_unversioned_probe.ts',
37
- },
38
- ],
39
- right: [
40
- {
41
- files: [
42
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
43
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_unversioned_probe.ts',\n },\n ],\n}\n" },
44
- { at: 'threads/_unversioned_probe.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const L: Threaded = {\n id: 'a-law',\n at: 'thread/integrity/constraint.ts',\n describes: 'A rule that must be enforced.',\n version: 3,\n}\n" },
45
- { at: 'thread/integrity/constraint.ts', source: 'export const CONSTRAINTS = []\n' },
46
- ],
47
- at: 'threads/_unversioned_probe.ts',
48
- },
49
- "export const HELPER = {\n id: 'a-helper',\n at: 'threads/core.ts',\n describes: 'Nothing conforms to this.',\n}",
50
- ],
51
- },
52
- }
@@ -1,395 +0,0 @@
1
- import { starts } from '../integrations/cli.ts'
2
- import { attached, existsSync, readFileSync, readdirSync } from '../integrations/disk.ts'
3
- import { join } from 'node:path'
4
- import { argv, stdout } from 'node:process'
5
-
6
- import { runChecker, type Finding } from '../core/finding.ts'
7
- import { exit, invoked } from '../core/invoked.ts'
8
-
9
- import { parser } from 'typescript-eslint'
10
-
11
- import { CONSTRAINTS, type Constraint, type Proof } from './constraint.ts'
12
- import { NOT_WRITTEN, ROOT, derivedHere, governs, load, proofsOf, reads, rules, slopnatorName, subject, sweeps, visits, type Reading, type Slopnator, type Spoken, type Visiting } from './slopnator.ts'
13
- import { crossed, deriveTrials, runTrials } from './generators/trial.ts'
14
-
15
- const PARSEABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js|jsx)$/
16
-
17
- interface Node {
18
- readonly type?: string
19
- readonly comments?: readonly Spoken[]
20
- readonly [key: string]: unknown
21
- }
22
-
23
- function tree(text: string): Node | undefined {
24
- const read = (jsx: boolean): { ast: Node } =>
25
- (parser.parseForESLint as (t: string, o: unknown) => { ast: Node })(text, {
26
- range: true,
27
- loc: true,
28
- comment: true,
29
- ecmaVersion: 'latest',
30
- sourceType: 'module',
31
- ecmaFeatures: { jsx },
32
- })
33
- try {
34
- return read(true).ast
35
- } catch {
36
- try {
37
- return read(false).ast
38
- } catch {
39
- return undefined
40
- }
41
- }
42
- }
43
-
44
- export function visit(root: Node, on: ReadonlySet<string>, each: (node: Node, ancestors: readonly Node[]) => void): void {
45
- const stack: Node[] = []
46
- const step = (held: unknown): void => {
47
- if (held === null || typeof held !== 'object') return
48
- if (Array.isArray(held)) {
49
- for (const one of held) step(one)
50
- return
51
- }
52
- const node = held as Node
53
- const named = typeof node.type === 'string'
54
- if (named && on.has(node.type as string)) each(node, [...stack])
55
- if (named) stack.push(node)
56
- for (const key of Object.keys(node)) {
57
- if (key === 'parent') continue
58
- step((node as Record<string, unknown>)[key])
59
- }
60
- if (named) stack.pop()
61
- }
62
- step(root)
63
- }
64
-
65
- const blank = (said: string): string => said.replace(/[^\n]/g, ' ')
66
-
67
- const sourcesIn = (one: Proof): readonly string[] =>
68
- typeof one === 'string'
69
- ? [one]
70
- : one.files.flatMap((each) => (typeof each.source === 'string' ? [each.source] : []))
71
-
72
- function without(text: string, rule: Constraint, slopnators: ReadonlyMap<string, Slopnator>): string {
73
- let said = text
74
- for (const proof of proofsOf(rule, slopnators).wrong) {
75
- for (const source of sourcesIn(proof)) {
76
- for (const one of [source, source.trim()]) {
77
- if (one === '') continue
78
- said = said.replaceAll(one, blank(one))
79
- }
80
- }
81
- }
82
- return said
83
- }
84
-
85
- export interface Caught {
86
- readonly rule: string
87
- readonly file: string
88
- readonly found: string
89
- }
90
-
91
- const ELSEWHERE = new Set(['node_modules', '.git'])
92
-
93
- function ignoredIn(root: string): ReadonlySet<string> {
94
- const at = join(root, '.gitignore')
95
- if (!existsSync(at)) return new Set()
96
- return new Set(
97
- readFileSync(at, 'utf8')
98
- .split('\n')
99
- .map((line) => line.trim().replace(/^\//, '').replace(/\/$/, ''))
100
- .filter((line) => line !== '' && !line.startsWith('#') && !line.includes('*') && !line.startsWith('!')),
101
- )
102
- }
103
-
104
- export function tracked(root: string): readonly string[] {
105
- const derived = derivedHere()
106
- const mine = (one: string): boolean => derived?.has(one) !== true
107
-
108
- try {
109
-
110
- const listed = starts('git', ['ls-files', '--cached', '--others', '--exclude-standard'], { at: root })
111
- .out.split('\n')
112
- .filter(Boolean)
113
- if (!attached() && listed.length > 0) {
114
- return [...new Set(listed)].filter((one) => existsSync(join(root, one)) && mine(one)).sort()
115
- }
116
- } catch {
117
-
118
- }
119
-
120
- const found: string[] = []
121
- const ignored = ignoredIn(root)
122
-
123
- const walk = (at: string, under: string): void => {
124
- for (const one of readdirSync(at, { withFileTypes: true })) {
125
- if (one.name.startsWith('.')) continue
126
- if (ELSEWHERE.has(one.name) || ignored.has(one.name) || ignored.has(under === '' ? one.name : `${under}/${one.name}`)) continue
127
- const here = under === '' ? one.name : `${under}/${one.name}`
128
- if (one.isDirectory()) walk(join(at, one.name), here)
129
- else if (mine(here)) found.push(here)
130
- }
131
- }
132
-
133
- walk(root, '')
134
- return found.sort()
135
- }
136
-
137
- export function sweep(
138
- root: string,
139
- all: readonly Constraint[],
140
- slopnators: ReadonlyMap<string, Slopnator>,
141
- files: readonly string[],
142
- ): {
143
- readonly findings: readonly Caught[]
144
- readonly swept: number
145
- readonly tracked: number
146
- readonly rules: number
147
- readonly unrun: readonly string[]
148
- readonly unmet: readonly string[]
149
- readonly linted: readonly string[]
150
- readonly spent: ReadonlyMap<string, number>
151
- } {
152
- const readers = all
153
- .map((rule) => ({ rule, slopnator: slopnators.get(slopnatorName(rule.id)) }))
154
- .filter((one): one is { rule: Constraint; slopnator: Reading } => one.slopnator !== undefined && sweeps(one.slopnator))
155
-
156
- const walkers = all
157
- .map((rule) => ({ rule, slopnator: slopnators.get(slopnatorName(rule.id)) }))
158
- .filter((one): one is { rule: Constraint; slopnator: Visiting } => one.slopnator !== undefined && visits(one.slopnator))
159
-
160
- const findings: Caught[] = []
161
- let swept = 0
162
- const spent = new Map<string, number>()
163
- const timed = <T>(rule: string, work: () => T): T => {
164
- const began = Date.now()
165
- try {
166
- return work()
167
- } finally {
168
- spent.set(rule, (spent.get(rule) ?? 0) + Date.now() - began)
169
- }
170
- }
171
-
172
- const met = new Set<string>()
173
-
174
- for (const file of files) {
175
- const reading = readers.filter((one) => subject(one.rule) && governs(one.rule, file))
176
- const walking = walkers.filter((one) => subject(one.rule) && governs(one.rule, file))
177
- for (const one of reading) met.add(one.rule.id)
178
- for (const one of walking) met.add(one.rule.id)
179
- if (reading.length === 0 && walking.length === 0) continue
180
-
181
- let text: string
182
- try {
183
- text = readFileSync(join(root, file), 'utf8')
184
- } catch {
185
- continue
186
- }
187
- swept += 1
188
-
189
- for (const { rule, slopnator } of reading) {
190
- const held = timed(rule.id, () =>
191
- slopnator.holds(undefined, {
192
- filename: file,
193
- text: without(text, rule, slopnators),
194
- ancestors: () => [],
195
- comments: () => [],
196
- }),
197
- )
198
- if (held === false || held === NOT_WRITTEN) continue
199
- findings.push({ rule: rule.id, file, found: typeof held === 'string' ? held : slopnatorName(rule.id) })
200
- }
201
-
202
- if (walking.length === 0 || !PARSEABLE.test(file)) continue
203
-
204
- const ast = tree(text)
205
- if (ast === undefined) {
206
- for (const { rule } of walking) {
207
- findings.push({
208
- rule: rule.id,
209
- file,
210
- found:
211
- 'this file could not be parsed, so nothing about its structure was measured — ' +
212
- 'a green result here says nothing about this rule on this file',
213
- })
214
- }
215
- continue
216
- }
217
-
218
- const comments = ast.comments ?? []
219
- for (const { rule, slopnator } of walking) {
220
- const on = new Set(slopnator.on)
221
- if (on.size === 0) {
222
- findings.push({
223
- rule: rule.id,
224
- file,
225
- found: 'its slopnator visits no node type, so it measured nothing here rather than finding nothing',
226
- })
227
- continue
228
- }
229
- timed(rule.id, () => visit(ast, on, (node, ancestors) => {
230
- const held = slopnator.holds(node, {
231
- filename: file,
232
- text,
233
- ancestors: () => ancestors,
234
- comments: () => comments,
235
- })
236
- if (held === false || held === NOT_WRITTEN) return
237
- findings.push({ rule: rule.id, file, found: typeof held === 'string' ? held : slopnatorName(rule.id) })
238
- }))
239
- }
240
- }
241
-
242
- for (const rule of all) {
243
- const slopnator = slopnators.get(slopnatorName(rule.id))
244
- if (slopnator === undefined || reads(slopnator)) continue
245
- for (const found of timed(rule.id, () => slopnator.checks(root))) findings.push({ rule: rule.id, file: '', found })
246
- }
247
-
248
- const unrun = all.filter((rule) => slopnators.get(slopnatorName(rule.id)) === undefined).map((rule) => rule.id)
249
- const linted = walkers.map((one) => one.rule.id)
250
- const unmet = [...new Set([...readers, ...walkers].map((one) => one.rule.id))].filter((id) => !met.has(id)).sort()
251
-
252
- return {
253
- findings,
254
- swept,
255
- tracked: files.length,
256
- unrun,
257
- unmet,
258
- linted,
259
- spent,
260
- rules:
261
- readers.length +
262
- walkers.length +
263
- all.filter((rule) => {
264
- const slopnator = slopnators.get(slopnatorName(rule.id))
265
- return slopnator !== undefined && !reads(slopnator)
266
- }).length,
267
- }
268
- }
269
-
270
- if (invoked(import.meta.url)) {
271
- const began = Date.now()
272
- const declared = [...CONSTRAINTS, ...((await rules()) ?? [])]
273
- let only: string | undefined
274
- let flagged = false
275
- for (const one of argv) {
276
- if (flagged) {
277
- only = one
278
- break
279
- }
280
- if (one === '--only') flagged = true
281
- }
282
- let governing: readonly Constraint[] = []
283
- for (const one of declared) {
284
- const id = one['id']
285
- if (only === undefined || id === only || slopnatorName(id) === only) governing = [...governing, one]
286
- }
287
- if (governing[0] === undefined) {
288
- runChecker(
289
- 'sweep',
290
- [
291
- {
292
- file: '',
293
- detail: 'this tree declares no rule called ' + String(only),
294
- instead:
295
- 'name a rule by its id, or by the slopnator beside it — `thread rules` lists both. Running all of them while ' +
296
- 'saying one was asked for is a green tick over a sweep nobody asked for, so nothing was swept',
297
- },
298
- ],
299
- '',
300
- )
301
- exit(1)
302
- }
303
- const slopnators = await load(ROOT, governing)
304
- const named = new Map(governing.map((rule) => [rule.id, rule]))
305
- const loaded = Date.now()
306
- const run = sweep(ROOT, governing, slopnators, tracked(ROOT))
307
- const walked = Date.now()
308
-
309
- const { default: plugin } = await import('./lint.ts')
310
- const derived = deriveTrials(governing, slopnators).trials
311
- const proving = runTrials(derived, governing, plugin.rules, parser, slopnators)
312
- const proved = Date.now()
313
- const crossing = new Map<string, number>()
314
- const unproven = [
315
- ...proving.outcomes.filter((one) => !one.passed),
316
- ...crossed(derived, declared, plugin.rules, parser, await load(ROOT, declared), crossing),
317
- ]
318
- const done = Date.now()
319
-
320
- const failing = new Map<string, string[]>()
321
- for (const one of unproven) {
322
- const held = failing.get(one.rule) ?? []
323
- held.push(` ${one.id} — ${one.failures.join('; ')}`)
324
- failing.set(one.rule, held)
325
- }
326
-
327
- const warned = new Set<string>()
328
- const grouped = new Map<string, string[]>()
329
- const files = new Map<string, Set<string>>()
330
- for (const finding of run.findings) {
331
- if (named.get(finding.rule)?.severity === 'warning') warned.add(finding.rule)
332
- const held = grouped.get(finding.rule) ?? []
333
- held.push(finding.file === '' ? ` ${finding.found}` : ` ${finding.file} — ${finding.found}`)
334
- grouped.set(finding.rule, held)
335
- const where = files.get(finding.rule) ?? new Set<string>()
336
- where.add(finding.file)
337
- files.set(finding.rule, where)
338
- }
339
- const refused = new Set(run.findings.map((one) => one.file).filter((one) => one !== ''))
340
-
341
- const findings: Finding[] = [
342
- ...[...grouped.entries()].map(([id, where]): Finding => {
343
- const rule = named.get(id)
344
- const count = files.get(id)?.size ?? 0
345
- const said = where.length === count ? `${String(count)} file(s)` : `${String(where.length)} finding(s) in ${String(count)} file(s)`
346
- return {
347
- detail: `${id} — ${rule?.bans ?? 'refused'}. ${said}:\n${where.sort().join('\n')}`,
348
- instead: rule?.instead ?? `read what ${id} refuses and stop writing it`,
349
- ...(rule?.severity === 'warning' ? { severity: 'warning' as const } : {}),
350
- }
351
- }),
352
- ...[...failing.entries()].map(([id, why]): Finding => ({
353
- detail: `${id} — ${String(why.length)} of its own trial(s) did not pass:\n${[...new Set(why)].sort().join('\n')}`,
354
- instead:
355
- 'make the rule refuse the `wrong` it declares and accept the `right`, or correct the pair — a rule that has never fired on its own example is a rule nobody has seen work',
356
- })),
357
- ...run.unrun.map((rule): Finding => ({
358
- detail: `${rule} DID NOT RUN — no slopnator is on disk for it, so this sweep measured nothing for that rule and a green result here says nothing about it`,
359
- instead: 'write the walk the rule names, or delete the rule and its row together — half a rule reads as a whole one from every direction',
360
- })),
361
- ]
362
-
363
- runChecker(
364
- 'sweep',
365
- findings,
366
- `each naming one rule and the files it refuses — ${String(run.findings.length)} refusal(s) across ${String(refused.size)} file(s), ` +
367
- `over ${String(run.rules)} rule(s) run across ${String(run.swept)} of ${String(run.tracked)} file(s) in this tree, ` +
368
- `${String(run.linted.length)} of them reading a syntax tree, and ${String(proving.passed)} of ${String(proving.outcomes.length)} rule trial(s) passed, ${String(proving.measured)} measured` +
369
- `${run.unrun.length === 0 ? '' : `, ${String(run.unrun.length)} that DID NOT RUN AT ALL`}` +
370
- `${run.unmet.length === 0 ? '' : `, and ${String(run.unmet.length)} that governed no file here and so measured nothing: ${run.unmet.join(', ')}`}`,
371
- )
372
- const seconds = (ms: number): string => String(Math.round(ms / 1000)) + ' s'
373
- const slowest = (spent: ReadonlyMap<string, number>): string =>
374
- [...spent.entries()]
375
- .sort((one, two) => two[1] - one[1])
376
- .slice(0, 3)
377
- .map(([rule, ms]) => rule + ' ' + seconds(ms))
378
- .join(', ')
379
- const own = new Map(run.spent)
380
- for (const [rule, ms] of proving.spent) own.set(rule, (own.get(rule) ?? 0) + ms)
381
- stdout.write(
382
- 'sweep: it took ' + seconds(done - began) + '. Loading the rules took ' + seconds(loaded - began) +
383
- ', reading the files in this tree ' + seconds(walked - loaded) +
384
- ', running each rule on its own examples ' + seconds(proved - walked) +
385
- ', and holding what each rule recommends against every other rule ' + seconds(done - proved) +
386
- '. Slowest on their own files and examples: ' + slowest(own) +
387
- '. Slowest to hold against the rest: ' + slowest(crossing) + '.\n',
388
- )
389
- const forbidding = findings.length - run.findings.filter((one) => named.get(one.rule)?.severity === 'warning').length
390
- if (forbidding > 0) exit(1)
391
- if (warned.size > 0) {
392
- stdout.write(`sweep: every finding above is a warning — ${[...warned].sort().join(', ')} state \`severity: 'warning'\`, so the gate does not stop here.\n`)
393
- }
394
- exit(0)
395
- }