@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.
- package/CHANGELOG.md +193 -0
- package/dist/core/attach.js +21 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +18 -3
- package/dist/core/generators/commit.js +55 -3
- package/dist/core/generators/dev.d.ts +1 -0
- package/dist/core/generators/dev.js +68 -0
- package/dist/core/rows.d.ts +5 -0
- package/dist/core/rows.js +45 -0
- package/dist/core/tree.d.ts +1 -0
- package/dist/core/tree.js +7 -1
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +103 -22
- package/dist/derivation/generators/release.js +74 -26
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.d.ts +3 -9
- package/dist/integrations/cli.js +96 -36
- package/dist/integrations/declared.d.ts +8 -0
- package/dist/integrations/declared.js +22 -0
- package/dist/integrations/pglite.js +13 -3
- package/dist/integrations/target.d.ts +4 -1
- package/dist/integrations/target.js +20 -6
- package/dist/integrity/attach.js +1 -5
- package/dist/integrity/generators/publish.js +2 -1
- package/dist/integrity/slopnators/sl_stale_path.js +10 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
- package/dist/operations/access.js +3 -2
- package/dist/operations/capabilities.d.ts +2 -0
- package/dist/operations/capabilities.js +2 -0
- package/dist/operations/generators/capability.js +234 -24
- package/dist/operations/generators/compile.d.ts +21 -0
- package/dist/operations/generators/compile.js +151 -51
- package/dist/operations/generators/permits.d.ts +2 -1
- package/dist/operations/generators/permits.js +52 -47
- package/dist/operations/generators/seed.d.ts +3 -0
- package/dist/operations/generators/seed.js +25 -3
- package/dist/operations/nodes.d.ts +2 -2
- package/package.json +1 -1
- package/dist/core/actor.ts +0 -21
- package/dist/core/attach.ts +0 -613
- package/dist/core/chain.ts +0 -145
- package/dist/core/condition.ts +0 -227
- package/dist/core/descend.ts +0 -1173
- package/dist/core/errors/catalogue.ts +0 -314
- package/dist/core/errors/classes.ts +0 -51
- package/dist/core/errors/error.ts +0 -152
- package/dist/core/errors/namespaces.ts +0 -19
- package/dist/core/finding.ts +0 -37
- package/dist/core/generators/commit.ts +0 -157
- package/dist/core/init.ts +0 -222
- package/dist/core/invoked.ts +0 -52
- package/dist/core/round.ts +0 -74
- package/dist/core/rows.ts +0 -459
- package/dist/core/scope.ts +0 -20
- package/dist/core/thread.ts +0 -29
- package/dist/core/tree.ts +0 -90
- package/dist/core/types.ts +0 -351
- package/dist/derivation/attach.ts +0 -57
- package/dist/derivation/generators/build.ts +0 -371
- package/dist/derivation/generators/check.ts +0 -118
- package/dist/derivation/generators/determinism.ts +0 -145
- package/dist/derivation/generators/release.ts +0 -236
- package/dist/derivation/release.ts +0 -43
- package/dist/integrations/attach.ts +0 -187
- package/dist/integrations/cli.ts +0 -615
- package/dist/integrations/disk.ts +0 -466
- package/dist/integrations/generators/read.ts +0 -127
- package/dist/integrations/generators/vault.ts +0 -210
- package/dist/integrations/https.ts +0 -216
- package/dist/integrations/pglite.ts +0 -206
- package/dist/integrations/secret.ts +0 -54
- package/dist/integrations/target.ts +0 -656
- package/dist/integrations/vault.ts +0 -148
- package/dist/integrity/attach.ts +0 -150
- package/dist/integrity/check-threads.ts +0 -166
- package/dist/integrity/constraint.ts +0 -1246
- package/dist/integrity/generators/compiler.ts +0 -138
- package/dist/integrity/generators/gate.ts +0 -227
- package/dist/integrity/generators/issues.ts +0 -160
- package/dist/integrity/generators/publish.ts +0 -232
- package/dist/integrity/generators/trial.ts +0 -359
- package/dist/integrity/lint.ts +0 -214
- package/dist/integrity/lsp/bin.ts +0 -44
- package/dist/integrity/lsp/diagnostics.ts +0 -92
- package/dist/integrity/lsp/findings.ts +0 -63
- package/dist/integrity/lsp/lint.ts +0 -124
- package/dist/integrity/lsp/protocol.ts +0 -78
- package/dist/integrity/lsp/server.ts +0 -224
- package/dist/integrity/slopnator.ts +0 -324
- package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
- package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
- package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
- package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
- package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
- package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
- package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
- package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
- package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
- package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
- package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
- package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
- package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
- package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
- package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
- package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
- package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
- package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
- package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
- package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
- package/dist/integrity/sweep.ts +0 -395
- package/dist/knowledge/attach.ts +0 -327
- package/dist/knowledge/generators/docsgen.ts +0 -186
- package/dist/knowledge/generators/glossary.ts +0 -73
- package/dist/knowledge/glossary.ts +0 -6
- package/dist/operations/access.ts +0 -131
- package/dist/operations/attach.ts +0 -408
- package/dist/operations/capabilities.ts +0 -88
- package/dist/operations/dispatch.ts +0 -294
- package/dist/operations/error-results.ts +0 -31
- package/dist/operations/generators/capability.ts +0 -1054
- package/dist/operations/generators/compile.ts +0 -701
- package/dist/operations/generators/permits.ts +0 -224
- package/dist/operations/generators/seed.ts +0 -359
- package/dist/operations/generators/trial.ts +0 -666
- package/dist/operations/nodes.ts +0 -53
- package/dist/operations/operations.ts +0 -91
- 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
|
-
}
|
package/dist/integrity/sweep.ts
DELETED
|
@@ -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
|
-
}
|