@prestack/thread 0.0.1 → 0.0.3
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/dist/core/actor.ts +20 -0
- package/dist/core/asking.d.ts +0 -1
- package/dist/core/asking.js +0 -1
- package/dist/core/asking.ts +1342 -0
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +585 -0
- package/dist/core/chain.js +8 -1
- package/dist/core/chain.ts +148 -0
- package/dist/core/cli.js +97 -18
- package/dist/core/cli.ts +2004 -0
- package/dist/core/condition.ts +227 -0
- package/dist/core/descend.d.ts +1 -0
- package/dist/core/descend.js +36 -0
- package/dist/core/descend.ts +1001 -0
- package/dist/core/finding.ts +38 -0
- package/dist/core/generators/meta.js +6 -0
- package/dist/core/generators/meta.ts +1006 -0
- package/dist/core/init.ts +170 -0
- package/dist/core/invoked.ts +47 -0
- package/dist/core/round.ts +57 -0
- package/dist/core/rows.ts +369 -0
- package/dist/core/scope.ts +18 -0
- package/dist/core/thread.ts +29 -0
- package/dist/core/tree.ts +67 -0
- package/dist/core/types.ts +350 -0
- package/dist/derivation/attach.js +1 -1
- package/dist/derivation/attach.ts +68 -0
- package/dist/derivation/generators/build.ts +232 -0
- package/dist/derivation/generators/check.ts +118 -0
- package/dist/derivation/generators/derivation.ts +347 -0
- package/dist/derivation/generators/determinism.ts +145 -0
- package/dist/derivation/generators/release.js +19 -9
- package/dist/derivation/generators/release.ts +213 -0
- package/dist/derivation/makers/release.ts +135 -0
- package/dist/derivation/release.ts +23 -0
- package/dist/integrations/attach.js +2 -2
- package/dist/integrations/attach.ts +207 -0
- package/dist/integrations/generators/read.ts +127 -0
- package/dist/integrations/generators/secret.ts +289 -0
- package/dist/integrations/generators/target.js +16 -6
- package/dist/integrations/generators/target.ts +944 -0
- package/dist/integrations/generators/vault.js +46 -14
- package/dist/integrations/generators/vault.ts +212 -0
- package/dist/integrations/{sdk.js → module.js} +4 -4
- package/dist/integrations/module.ts +189 -0
- package/dist/integrations/secret.ts +50 -0
- package/dist/integrations/target.d.ts +3 -1
- package/dist/integrations/target.js +14 -11
- package/dist/integrations/target.ts +340 -0
- package/dist/integrations/vault.d.ts +2 -0
- package/dist/integrations/vault.js +4 -2
- package/dist/integrations/vault.ts +136 -0
- package/dist/integrity/attach.ts +234 -0
- package/dist/integrity/check-threads.js +2 -26
- package/dist/integrity/check-threads.ts +166 -0
- package/dist/integrity/constraint.ts +1202 -0
- package/dist/integrity/generators/compiler.ts +161 -0
- package/dist/integrity/generators/gate.ts +201 -0
- package/dist/integrity/generators/issues.ts +160 -0
- package/dist/integrity/generators/publish.js +14 -5
- package/dist/integrity/generators/publish.ts +247 -0
- package/dist/integrity/generators/rule.ts +271 -0
- package/dist/integrity/generators/seal.ts +117 -0
- package/dist/integrity/generators/trial.ts +320 -0
- package/dist/integrity/lint.ts +227 -0
- package/dist/integrity/lsp/bin.ts +42 -0
- package/dist/integrity/lsp/diagnostics.ts +92 -0
- package/dist/integrity/lsp/findings.ts +63 -0
- package/dist/integrity/lsp/lint.ts +140 -0
- package/dist/integrity/lsp/protocol.ts +78 -0
- package/dist/integrity/lsp/server.ts +224 -0
- package/dist/integrity/sealed.js +2 -2
- package/dist/integrity/sealed.ts +56 -0
- package/dist/integrity/slopnator.ts +350 -0
- package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
- package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
- package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
- package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
- package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
- package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
- package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
- package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
- package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
- package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
- package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
- package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
- package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
- package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
- package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
- package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
- package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
- package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
- package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
- package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
- package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
- package/dist/integrity/sweep.ts +307 -0
- package/dist/knowledge/attach.ts +350 -0
- package/dist/knowledge/generators/docsgen.ts +174 -0
- package/dist/knowledge/generators/glossary.ts +73 -0
- package/dist/knowledge/generators/skills.ts +263 -0
- package/dist/knowledge/glossary.ts +6 -0
- package/dist/operations/access.ts +88 -0
- package/dist/operations/attach.ts +475 -0
- package/dist/operations/capabilities.ts +65 -0
- package/dist/operations/cost.ts +58 -0
- package/dist/operations/dispatch.ts +348 -0
- package/dist/operations/error-results.ts +31 -0
- package/dist/operations/errors/catalog.ts +168 -0
- package/dist/operations/errors/classes.ts +51 -0
- package/dist/operations/errors/errors.ts +314 -0
- package/dist/operations/generators/capability.js +29 -19
- package/dist/operations/generators/capability.ts +704 -0
- package/dist/operations/generators/compile.js +20 -1
- package/dist/operations/generators/compile.ts +643 -0
- package/dist/operations/generators/grant.ts +336 -0
- package/dist/operations/generators/operation.ts +972 -0
- package/dist/operations/generators/permits.ts +223 -0
- package/dist/operations/generators/seed.ts +347 -0
- package/dist/operations/generators/trial.js +4 -1
- package/dist/operations/generators/trial.ts +529 -0
- package/dist/operations/http.ts +164 -0
- package/dist/operations/makers/capability.ts +166 -0
- package/dist/operations/makers/trial.ts +520 -0
- package/dist/operations/memory.ts +470 -0
- package/dist/operations/metering.ts +328 -0
- package/dist/operations/nodes.ts +54 -0
- package/dist/operations/operations.ts +32 -0
- package/dist/operations/queue-policy.ts +10 -0
- package/dist/operations/result.ts +28 -0
- package/dist/operations/seams.d.ts +4 -4
- package/dist/operations/seams.ts +364 -0
- package/dist/operations/store.ts +29 -0
- package/package.json +8 -1
- package/dist/integrations/generators/sdk.d.ts +0 -1
- package/dist/integrations/generators/sdk.js +0 -148
- /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
+
|
|
3
|
+
interface Node {
|
|
4
|
+
readonly type?: string
|
|
5
|
+
readonly name?: string
|
|
6
|
+
readonly key?: { readonly name?: string; readonly value?: unknown }
|
|
7
|
+
readonly value?: unknown
|
|
8
|
+
readonly body?: unknown
|
|
9
|
+
readonly properties?: readonly Node[]
|
|
10
|
+
readonly elements?: readonly unknown[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const keyOf = (node: Node): string | undefined =>
|
|
14
|
+
node.key?.name ?? (typeof node.key?.value === 'string' ? node.key.value : undefined)
|
|
15
|
+
|
|
16
|
+
const answersUnwritten = (held: unknown): boolean => {
|
|
17
|
+
const fn = held as { type?: string; body?: { type?: string; name?: string } } | undefined
|
|
18
|
+
if (fn?.type !== 'ArrowFunctionExpression') return false
|
|
19
|
+
return fn.body?.type === 'Identifier' && fn.body.name === 'NOT_WRITTEN'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const statesNothing = (held: unknown): boolean => {
|
|
23
|
+
const proves = held as Node | undefined
|
|
24
|
+
if (proves?.type !== 'ObjectExpression') return false
|
|
25
|
+
return (proves.properties ?? []).some((one) => {
|
|
26
|
+
const key = keyOf(one)
|
|
27
|
+
if (key !== 'wrong' && key !== 'right') return false
|
|
28
|
+
const list = one.value as Node | undefined
|
|
29
|
+
return list?.type === 'ArrayExpression' && (list.elements ?? []).length === 0
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const slopnator: Slopnator = {
|
|
34
|
+
on: ['Property'],
|
|
35
|
+
holds: (node) => {
|
|
36
|
+
const held = node as Node
|
|
37
|
+
const key = keyOf(held)
|
|
38
|
+
|
|
39
|
+
if (key === 'holds' && answersUnwritten(held.value)) {
|
|
40
|
+
return 'this is still the scaffold — it answers `NOT_WRITTEN` to everything, so the rule that names it registers, loads, reports nothing, and reads from outside exactly like a rule being obeyed'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (key === 'on') {
|
|
44
|
+
const list = held.value as Node | undefined
|
|
45
|
+
if (list?.type === 'ArrayExpression' && (list.elements ?? []).length === 0) {
|
|
46
|
+
return 'it says it visits syntax and then names no node type, so it is handed nothing and governs nothing inside the files its rule governs'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (key === 'proves' && statesNothing(held.value)) {
|
|
51
|
+
return 'its `proves` states nothing — with no form it refuses and none it accepts, no trial is derived from it, so nothing anywhere shows the slopnator does what its row claims'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false
|
|
55
|
+
},
|
|
56
|
+
proves: {
|
|
57
|
+
wrong: [
|
|
58
|
+
{
|
|
59
|
+
files: [
|
|
60
|
+
{
|
|
61
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
62
|
+
source: 'export const slopnator = {\n on: [],\n holds: () => NOT_WRITTEN,\n proves: { wrong: [], right: [] },\n}\n',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
files: [
|
|
69
|
+
{
|
|
70
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
71
|
+
source: "export const slopnator = {\n holds: (_node, at) => at.text.includes('nope') && 'found',\n proves: { wrong: [], right: ['const held = 1'] },\n}\n",
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
right: [
|
|
78
|
+
{
|
|
79
|
+
files: [
|
|
80
|
+
{
|
|
81
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
82
|
+
source: "export const slopnator = {\n holds: (_node, at) => at.text.includes('nope') && 'found',\n proves: { wrong: ['nope'], right: ['const held = 1'] },\n}\n",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
files: [{ at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts', source: 'export const held = 1\n' }],
|
|
89
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_inert.ts',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
5
|
+
import { ORGANISATION, ours } from '../../core/scope.ts'
|
|
6
|
+
import { findTree } from '../../core/tree.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A row the package still ships and the tree no longer holds.
|
|
10
|
+
*
|
|
11
|
+
* Read as TEXT, deliberately. Eight laws were once added whose walks imported a workspace package,
|
|
12
|
+
* plain Node could not resolve it, `load()` swallowed the failure, and all eight read from every
|
|
13
|
+
* caller exactly like laws finding nothing. Nothing here imports anything but node builtins.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const ROOT = findTree().at
|
|
17
|
+
|
|
18
|
+
/** A node: a const annotated `Threaded`. */
|
|
19
|
+
const NODE = /export\s+const\s+[A-Za-z_$][\w$]*\s*:\s*Threaded\b/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Where a node's rows are written.
|
|
23
|
+
*
|
|
24
|
+
* A node may open the list inline, or name a `declares` array declared beside it and reference it by
|
|
25
|
+
* shorthand. Only the first was read, so `@thread/ui`'s tokens and signals nodes — 107 of its 178
|
|
26
|
+
* shipped rows — reported as shipping nothing, and every one of them could be deleted in silence.
|
|
27
|
+
*/
|
|
28
|
+
const SHAPES = [/declares:\s*\[/g, /export\s+const\s+declares\b[^\n]*?=\s*\[/g]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How a row states its id.
|
|
32
|
+
*
|
|
33
|
+
* A row may be an object literal, or a call to a factory declared beside the list that takes the id
|
|
34
|
+
* first. Only the literal was read, which is the other half of why tokens and signals were invisible.
|
|
35
|
+
*/
|
|
36
|
+
const ID = /\bid:\s*'([^']+)'/g
|
|
37
|
+
const CALLED = /(?:^|[\s,[(])[a-zA-Z_$][\w$]*\(\s*'([^']+)'/g
|
|
38
|
+
|
|
39
|
+
/** Strings out first, so a fixture that PRINTS a declaration is not read as being one. */
|
|
40
|
+
const QUOTED = /"(?:[^"\\\n]|\\.)*"|`(?:[^`\\]|\\.)*`/g
|
|
41
|
+
|
|
42
|
+
function closes(code: string, from: number): number {
|
|
43
|
+
let depth = 0
|
|
44
|
+
for (let at = from; at < code.length; at += 1) {
|
|
45
|
+
if (code[at] === '[') depth += 1
|
|
46
|
+
else if (code[at] === ']') {
|
|
47
|
+
depth -= 1
|
|
48
|
+
if (depth === 0) return at
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return -1
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* What a package SHIPS: only the ids inside a `declares` list.
|
|
56
|
+
*
|
|
57
|
+
* It was every id anywhere in a file that held one such list, which read an `Appending`'s own `id`
|
|
58
|
+
* as a shipped row — `@thread/operations` was reported as shipping `seed-generator`, the name of a
|
|
59
|
+
* row it asks an app to APPEND into another list, not one it declares.
|
|
60
|
+
*/
|
|
61
|
+
function shippedIn(text: string): ReadonlySet<string> {
|
|
62
|
+
const code = text.replace(QUOTED, '""')
|
|
63
|
+
if (!NODE.test(code)) return new Set()
|
|
64
|
+
const found = new Set<string>()
|
|
65
|
+
for (const shape of SHAPES) {
|
|
66
|
+
for (const one of code.matchAll(shape)) {
|
|
67
|
+
const opens = (one.index ?? 0) + one[0].length - 1
|
|
68
|
+
const shut = closes(code, opens)
|
|
69
|
+
if (shut === -1) continue
|
|
70
|
+
const rows = code.slice(opens, shut)
|
|
71
|
+
for (const held of rows.matchAll(ID)) found.add(held[1] ?? '')
|
|
72
|
+
for (const held of rows.matchAll(CALLED)) found.add(held[1] ?? '')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return found
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What a tree HOLDS: every id in it, whichever list it sits in.
|
|
80
|
+
*
|
|
81
|
+
* Gating this on `declares` made every row in `derivation.ts` invisible, because its node states no
|
|
82
|
+
* `declares` at all and its rows live in `GENERATORS`. A row plainly on disk read as missing, and
|
|
83
|
+
* running the command this law recommends would not have cleared the finding.
|
|
84
|
+
*/
|
|
85
|
+
function heldIn(text: string): ReadonlySet<string> {
|
|
86
|
+
const code = text.replace(QUOTED, '""')
|
|
87
|
+
const found = new Set<string>()
|
|
88
|
+
for (const one of code.matchAll(ID)) found.add(one[1] ?? '')
|
|
89
|
+
for (const one of code.matchAll(CALLED)) found.add(one[1] ?? '')
|
|
90
|
+
return found
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function filesIn(dir: string): readonly string[] {
|
|
94
|
+
try {
|
|
95
|
+
return readdirSync(dir).filter((one) => one.endsWith('.ts')).map((one) => join(dir, one))
|
|
96
|
+
} catch {
|
|
97
|
+
return []
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Every installed thread package that ships nodes of its own. */
|
|
102
|
+
function installed(): readonly { readonly name: string; readonly src: string }[] {
|
|
103
|
+
const at = join(ROOT, 'node_modules', ORGANISATION)
|
|
104
|
+
const found: { name: string; src: string }[] = []
|
|
105
|
+
for (const one of safely(at)) {
|
|
106
|
+
const name = `${ORGANISATION}/${one}`
|
|
107
|
+
if (!ours(name)) continue
|
|
108
|
+
const src = join(at, one, 'dist')
|
|
109
|
+
if (existsSync(src) && statSync(src).isDirectory()) found.push({ name, src })
|
|
110
|
+
}
|
|
111
|
+
return found
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function safely(dir: string): readonly string[] {
|
|
115
|
+
try {
|
|
116
|
+
return readdirSync(dir)
|
|
117
|
+
} catch {
|
|
118
|
+
return []
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function read(path: string): string {
|
|
123
|
+
try {
|
|
124
|
+
return readFileSync(path, 'utf8')
|
|
125
|
+
} catch {
|
|
126
|
+
return ''
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const slopnator: Slopnator = {
|
|
131
|
+
checks: () => {
|
|
132
|
+
const tree = findTree()
|
|
133
|
+
|
|
134
|
+
/* Every id the tree holds, across every node in it. */
|
|
135
|
+
const held = new Set<string>()
|
|
136
|
+
for (const path of filesIn(tree.dir)) for (const one of heldIn(read(path))) held.add(one)
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* Only packages this tree ATTACHED, not every one installed.
|
|
140
|
+
*
|
|
141
|
+
* Measured by running it: the first version named `@thread/knowledge`, which the audit app has
|
|
142
|
+
* in `node_modules` and never attached — so the law demanded rows for a package nobody had
|
|
143
|
+
* asked for. A package is attached when the tree names a file inside it; one nothing points at
|
|
144
|
+
* is a dependency, and a dependency ships nothing this tree promised to hold.
|
|
145
|
+
*/
|
|
146
|
+
const wired = new Set<string>()
|
|
147
|
+
for (const path of filesIn(tree.dir)) {
|
|
148
|
+
for (const one of read(path).matchAll(/node_modules\/(@[\w.-]+\/[\w.-]+)\//g)) wired.add(one[1] ?? '')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const found: string[] = []
|
|
152
|
+
for (const pkg of installed()) {
|
|
153
|
+
if (!wired.has(pkg.name)) continue
|
|
154
|
+
for (const path of filesIn(pkg.src)) {
|
|
155
|
+
const shipped = shippedIn(read(path))
|
|
156
|
+
if (shipped.size === 0) continue
|
|
157
|
+
for (const id of [...shipped].sort()) {
|
|
158
|
+
/* The node's own id names the list, not a row in it. */
|
|
159
|
+
if (held.has(id)) continue
|
|
160
|
+
found.push(
|
|
161
|
+
`${pkg.name} ships \`${id}\` and this tree does not hold it — run \`thread attach ${pkg.name}\` to put it back`,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return found
|
|
167
|
+
},
|
|
168
|
+
proves: {
|
|
169
|
+
wrong: [
|
|
170
|
+
{
|
|
171
|
+
files: [{ at: '_threads/_probe_missing.ts', source: "import type { Threaded } from '../../core/thread.ts'\n\nexport const M: Threaded = {\n declares: [],\n}\n" }],
|
|
172
|
+
at: '_threads/_probe_missing.ts',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
right: [
|
|
176
|
+
{
|
|
177
|
+
files: [{ at: '_threads/_probe_missing.ts', source: "import type { Threaded } from '../../core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'colour',\n },\n ],\n}\n" }],
|
|
178
|
+
at: '_threads/_probe_missing.ts',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
+
import { here } from './sl_unthreaded.ts'
|
|
3
|
+
|
|
4
|
+
const BARREL_NAME = /(^|\/)index\.(ts|tsx|mts|cts|js|mjs|cjs)$/
|
|
5
|
+
const CODE = /\.(ts|tsx|mts|cts|js|mjs|cjs)$/
|
|
6
|
+
|
|
7
|
+
const SAYS = "can't do barrels lazy ass"
|
|
8
|
+
|
|
9
|
+
function reExportsOnly(text: string): boolean {
|
|
10
|
+
const lines = text
|
|
11
|
+
.split('\n')
|
|
12
|
+
.map((one) => one.trim())
|
|
13
|
+
.filter((one) => one !== '')
|
|
14
|
+
|
|
15
|
+
if (lines.length === 0) return false
|
|
16
|
+
return lines.every((one) => one.startsWith('export *') || one.startsWith('export {') || one.startsWith('}') || one.startsWith('} from') || /^[A-Za-z_$][\w$]*,?$/.test(one) || /^type [A-Za-z_$][\w$]*,?$/.test(one))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const slopnator: Slopnator = {
|
|
20
|
+
holds: (_node, at) => {
|
|
21
|
+
const path = here(at.filename)
|
|
22
|
+
if (!CODE.test(path)) return false
|
|
23
|
+
|
|
24
|
+
if (BARREL_NAME.test(path)) {
|
|
25
|
+
return `${SAYS} — this file is named for the folder it sits in, so it exists to list what the folder holds, and the tree already says that`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (reExportsOnly(at.text)) {
|
|
29
|
+
return `${SAYS} — every line of this file is a re-export, so it holds no thing of its own and names nothing the tree has not already named`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false
|
|
33
|
+
},
|
|
34
|
+
proves: {
|
|
35
|
+
wrong: [
|
|
36
|
+
{
|
|
37
|
+
files: [{ at: 'packages/_probe_barrel/index.ts', source: "export const held = 1\n" }],
|
|
38
|
+
at: 'packages/_probe_barrel/index.ts',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
right: [
|
|
42
|
+
{
|
|
43
|
+
files: [{ at: 'packages/_probe_barrel/held.ts', source: "export const held = 1\n" }],
|
|
44
|
+
at: 'packages/_probe_barrel/held.ts',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
+
|
|
3
|
+
const STARTS_A_PROCESS = new Set(['node:child_process', 'child_process'])
|
|
4
|
+
|
|
5
|
+
interface Node {
|
|
6
|
+
readonly type?: string
|
|
7
|
+
readonly source?: { readonly type?: string; readonly value?: unknown }
|
|
8
|
+
readonly callee?: { readonly type?: string; readonly name?: string }
|
|
9
|
+
readonly arguments?: readonly { readonly type?: string; readonly value?: unknown }[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const named = (held: unknown): string | undefined => {
|
|
13
|
+
const one = held as { type?: string; value?: unknown } | undefined
|
|
14
|
+
return one?.type === 'Literal' && typeof one.value === 'string' ? one.value : undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const slopnator: Slopnator = {
|
|
18
|
+
on: ['ImportDeclaration', 'ImportExpression', 'CallExpression'],
|
|
19
|
+
holds: (node) => {
|
|
20
|
+
const held = node as Node
|
|
21
|
+
const from =
|
|
22
|
+
held.type === 'ImportDeclaration' || held.type === 'ImportExpression'
|
|
23
|
+
? (named(held.source) ?? named(held.arguments?.[0]))
|
|
24
|
+
: held.callee?.name === 'require'
|
|
25
|
+
? named(held.arguments?.[0])
|
|
26
|
+
: undefined
|
|
27
|
+
|
|
28
|
+
if (from === undefined || !STARTS_A_PROCESS.has(from)) return false
|
|
29
|
+
|
|
30
|
+
return `it imports \`${from}\`, so this slopnator can start a process — and whatever it starts can reach back into the enforcement that called it`
|
|
31
|
+
},
|
|
32
|
+
proves: {
|
|
33
|
+
wrong: [
|
|
34
|
+
{
|
|
35
|
+
files: [
|
|
36
|
+
{
|
|
37
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
38
|
+
source: "import { spawnSync } from 'node:child_process'\nexport const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
files: [
|
|
45
|
+
{
|
|
46
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
47
|
+
source: "const held = await import('child_process')\nexport const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
right: [
|
|
54
|
+
{
|
|
55
|
+
files: [
|
|
56
|
+
{
|
|
57
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
58
|
+
source: "import { readFileSync } from 'node:fs'\nexport const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
files: [
|
|
65
|
+
{
|
|
66
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
67
|
+
source: "export const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
at: 'packages/core/src/integrity/slopnators/sl_probe_spawn.ts',
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Slopnator, Spoken } from '../slopnator.ts'
|
|
2
|
+
|
|
3
|
+
const shebang = (text: string): string | undefined => (text.startsWith('#!') ? text.slice(2, text.indexOf('\n')) : undefined)
|
|
4
|
+
|
|
5
|
+
const spoken = (text: string, all: readonly Spoken[]): readonly Spoken[] => {
|
|
6
|
+
const line = shebang(text)
|
|
7
|
+
return all.filter((one, index) => !(index === 0 && one.value === line))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const slopnator: Slopnator = {
|
|
11
|
+
on: ['Program'],
|
|
12
|
+
holds: (_node, at) => {
|
|
13
|
+
const all = spoken(at.text, at.comments())
|
|
14
|
+
if (all.length === 0) return false
|
|
15
|
+
const first = (all[0]?.value ?? '')
|
|
16
|
+
.split('\n')
|
|
17
|
+
.map((line) => line.trim().replace(/^\*+\s?/, ''))
|
|
18
|
+
.find((line) => line !== '') ?? ''
|
|
19
|
+
return `${String(all.length)} comment(s), the first saying "${first.slice(0, 60)}"`
|
|
20
|
+
},
|
|
21
|
+
fixes: (_node, at) => {
|
|
22
|
+
const all = spoken(at.text, at.comments())
|
|
23
|
+
if (all.length === 0) return undefined
|
|
24
|
+
let out = at.text
|
|
25
|
+
for (const one of [...all].sort((a, b) => b.range[0] - a.range[0])) {
|
|
26
|
+
out = out.slice(0, one.range[0]) + out.slice(one.range[1])
|
|
27
|
+
}
|
|
28
|
+
return out.replace(/[ \t]+$/gm, '').replace(/\n{3,}/g, '\n\n')
|
|
29
|
+
},
|
|
30
|
+
proves: {
|
|
31
|
+
wrong: [
|
|
32
|
+
'const a = 1 // a line comment',
|
|
33
|
+
'/* a block comment */\nconst a = 1',
|
|
34
|
+
'/** JSDoc on the export */\nexport const a = 1',
|
|
35
|
+
],
|
|
36
|
+
right: [
|
|
37
|
+
'const a = "// not a comment"',
|
|
38
|
+
'const url = "https://example.com/docs"',
|
|
39
|
+
'const a = 1',
|
|
40
|
+
'#!/usr/bin/env node\nconst a = 1',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
}
|