@prestack/thread 0.1.21 → 0.1.23

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 (138) hide show
  1. package/CHANGELOG.md +227 -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 +69 -0
  7. package/dist/derivation/generators/build.d.ts +3 -1
  8. package/dist/derivation/generators/build.js +104 -22
  9. package/dist/derivation/generators/release.js +74 -25
  10. package/dist/derivation/release.js +3 -3
  11. package/dist/integrations/cli.d.ts +0 -1
  12. package/dist/integrations/cli.js +69 -11
  13. package/dist/integrations/declared.d.ts +2 -0
  14. package/dist/integrations/declared.js +6 -1
  15. package/dist/integrations/pglite.js +13 -3
  16. package/dist/integrations/target.d.ts +3 -0
  17. package/dist/integrity/attach.js +1 -5
  18. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  19. package/dist/operations/access.js +3 -2
  20. package/dist/operations/capabilities.d.ts +2 -0
  21. package/dist/operations/capabilities.js +2 -0
  22. package/dist/operations/generators/capability.js +237 -24
  23. package/dist/operations/generators/compile.js +150 -50
  24. package/dist/operations/generators/seed.d.ts +3 -0
  25. package/dist/operations/generators/seed.js +25 -3
  26. package/dist/operations/nodes.d.ts +2 -2
  27. package/package.json +1 -1
  28. package/dist/core/actor.ts +0 -21
  29. package/dist/core/attach.ts +0 -613
  30. package/dist/core/chain.ts +0 -145
  31. package/dist/core/condition.ts +0 -227
  32. package/dist/core/descend.ts +0 -1185
  33. package/dist/core/errors/catalogue.ts +0 -314
  34. package/dist/core/errors/classes.ts +0 -51
  35. package/dist/core/errors/error.ts +0 -152
  36. package/dist/core/errors/namespaces.ts +0 -19
  37. package/dist/core/finding.ts +0 -37
  38. package/dist/core/generators/commit.ts +0 -157
  39. package/dist/core/init.ts +0 -222
  40. package/dist/core/invoked.ts +0 -52
  41. package/dist/core/round.ts +0 -74
  42. package/dist/core/rows.ts +0 -506
  43. package/dist/core/scope.ts +0 -20
  44. package/dist/core/thread.ts +0 -29
  45. package/dist/core/tree.ts +0 -90
  46. package/dist/core/types.ts +0 -351
  47. package/dist/derivation/attach.ts +0 -57
  48. package/dist/derivation/generators/build.ts +0 -371
  49. package/dist/derivation/generators/check.ts +0 -118
  50. package/dist/derivation/generators/determinism.ts +0 -145
  51. package/dist/derivation/generators/release.ts +0 -236
  52. package/dist/derivation/release.ts +0 -43
  53. package/dist/integrations/attach.ts +0 -187
  54. package/dist/integrations/cli.ts +0 -606
  55. package/dist/integrations/declared.ts +0 -34
  56. package/dist/integrations/disk.ts +0 -466
  57. package/dist/integrations/generators/read.ts +0 -127
  58. package/dist/integrations/generators/vault.ts +0 -210
  59. package/dist/integrations/https.ts +0 -216
  60. package/dist/integrations/pglite.ts +0 -206
  61. package/dist/integrations/secret.ts +0 -54
  62. package/dist/integrations/target.ts +0 -672
  63. package/dist/integrations/vault.ts +0 -148
  64. package/dist/integrity/attach.ts +0 -150
  65. package/dist/integrity/check-threads.ts +0 -166
  66. package/dist/integrity/constraint.ts +0 -1246
  67. package/dist/integrity/generators/compiler.ts +0 -138
  68. package/dist/integrity/generators/gate.ts +0 -227
  69. package/dist/integrity/generators/issues.ts +0 -160
  70. package/dist/integrity/generators/publish.ts +0 -233
  71. package/dist/integrity/generators/trial.ts +0 -359
  72. package/dist/integrity/lint.ts +0 -214
  73. package/dist/integrity/lsp/bin.ts +0 -44
  74. package/dist/integrity/lsp/diagnostics.ts +0 -92
  75. package/dist/integrity/lsp/findings.ts +0 -63
  76. package/dist/integrity/lsp/lint.ts +0 -124
  77. package/dist/integrity/lsp/protocol.ts +0 -78
  78. package/dist/integrity/lsp/server.ts +0 -224
  79. package/dist/integrity/slopnator.ts +0 -324
  80. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  81. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  82. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  83. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  84. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  85. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  86. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  87. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  88. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  89. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  90. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  91. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  92. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  93. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  94. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  95. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  96. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  97. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  98. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  99. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  100. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  101. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  102. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  103. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  104. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  105. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  106. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  107. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  108. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  109. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  110. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  111. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  112. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  113. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  114. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  115. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  116. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  117. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  118. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  119. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  120. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  121. package/dist/integrity/sweep.ts +0 -395
  122. package/dist/knowledge/attach.ts +0 -327
  123. package/dist/knowledge/generators/docsgen.ts +0 -186
  124. package/dist/knowledge/generators/glossary.ts +0 -73
  125. package/dist/knowledge/glossary.ts +0 -6
  126. package/dist/operations/access.ts +0 -131
  127. package/dist/operations/attach.ts +0 -408
  128. package/dist/operations/capabilities.ts +0 -88
  129. package/dist/operations/dispatch.ts +0 -294
  130. package/dist/operations/error-results.ts +0 -31
  131. package/dist/operations/generators/capability.ts +0 -1054
  132. package/dist/operations/generators/compile.ts +0 -701
  133. package/dist/operations/generators/permits.ts +0 -229
  134. package/dist/operations/generators/seed.ts +0 -359
  135. package/dist/operations/generators/trial.ts +0 -666
  136. package/dist/operations/nodes.ts +0 -53
  137. package/dist/operations/operations.ts +0 -91
  138. 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
- }