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