@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.
- package/CHANGELOG.md +227 -0
- package/dist/core/attach.js +21 -0
- package/dist/core/descend.js +6 -3
- package/dist/core/generators/commit.js +55 -3
- package/dist/core/generators/dev.d.ts +1 -0
- package/dist/core/generators/dev.js +69 -0
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +104 -22
- package/dist/derivation/generators/release.js +74 -25
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.d.ts +0 -1
- package/dist/integrations/cli.js +69 -11
- package/dist/integrations/declared.d.ts +2 -0
- package/dist/integrations/declared.js +6 -1
- package/dist/integrations/pglite.js +13 -3
- package/dist/integrations/target.d.ts +3 -0
- package/dist/integrity/attach.js +1 -5
- package/dist/integrity/slopnators/sl_stale_path.js +10 -0
- 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 +237 -24
- package/dist/operations/generators/compile.js +150 -50
- 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 -1185
- 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 -506
- 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 -606
- package/dist/integrations/declared.ts +0 -34
- 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 -672
- 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 -233
- 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 -229
- 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,140 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from '../../integrations/disk.ts'
|
|
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
|
-
const NODE = /export\s+const\s+[A-Za-z_$][\w$]*\s*:\s*Threaded\b/
|
|
9
|
-
|
|
10
|
-
const SHAPES = [/declares:\s*\[/g, /export\s+const\s+declares\b[^\n]*?=\s*\[/g]
|
|
11
|
-
|
|
12
|
-
const ID = /\bid:\s*'([^']+)'/g
|
|
13
|
-
const CALLED = /(?:^|[\s,[(])[a-zA-Z_$][\w$]*\(\s*'([^']+)'/g
|
|
14
|
-
|
|
15
|
-
const QUOTED = /"(?:[^"\\\n]|\\.)*"|`(?:[^`\\]|\\.)*`/g
|
|
16
|
-
|
|
17
|
-
function closes(code: string, from: number): number {
|
|
18
|
-
let depth = 0
|
|
19
|
-
for (let at = from; at < code.length; at += 1) {
|
|
20
|
-
if (code[at] === '[') depth += 1
|
|
21
|
-
else if (code[at] === ']') {
|
|
22
|
-
depth -= 1
|
|
23
|
-
if (depth === 0) return at
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return -1
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function shippedIn(text: string): ReadonlySet<string> {
|
|
30
|
-
const code = text.replace(QUOTED, '""')
|
|
31
|
-
if (!NODE.test(code)) return new Set()
|
|
32
|
-
const found = new Set<string>()
|
|
33
|
-
for (const shape of SHAPES) {
|
|
34
|
-
for (const one of code.matchAll(shape)) {
|
|
35
|
-
const opens = (one.index ?? 0) + one[0].length - 1
|
|
36
|
-
const shut = closes(code, opens)
|
|
37
|
-
if (shut === -1) continue
|
|
38
|
-
const rows = code.slice(opens, shut)
|
|
39
|
-
for (const held of rows.matchAll(ID)) found.add(held[1] ?? '')
|
|
40
|
-
for (const held of rows.matchAll(CALLED)) found.add(held[1] ?? '')
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return found
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function heldIn(text: string): ReadonlySet<string> {
|
|
47
|
-
const code = text.replace(QUOTED, '""')
|
|
48
|
-
const found = new Set<string>()
|
|
49
|
-
for (const one of code.matchAll(ID)) found.add(one[1] ?? '')
|
|
50
|
-
for (const one of code.matchAll(CALLED)) found.add(one[1] ?? '')
|
|
51
|
-
return found
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function filesIn(dir: string): readonly string[] {
|
|
55
|
-
try {
|
|
56
|
-
return readdirSync(dir).filter((one) => one.endsWith('.ts')).map((one) => join(dir, one))
|
|
57
|
-
} catch {
|
|
58
|
-
return []
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function installed(root: string): readonly { readonly name: string; readonly src: string }[] {
|
|
63
|
-
const at = join(root, 'node_modules', ORGANISATION)
|
|
64
|
-
const found: { name: string; src: string }[] = []
|
|
65
|
-
for (const one of safely(at)) {
|
|
66
|
-
const name = `${ORGANISATION}/${one}`
|
|
67
|
-
if (!ours(name)) continue
|
|
68
|
-
const src = join(at, one, 'dist')
|
|
69
|
-
if (existsSync(src) && statSync(src).isDirectory()) found.push({ name, src })
|
|
70
|
-
}
|
|
71
|
-
return found
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function safely(dir: string): readonly string[] {
|
|
75
|
-
try {
|
|
76
|
-
return readdirSync(dir)
|
|
77
|
-
} catch {
|
|
78
|
-
return []
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function read(path: string): string {
|
|
83
|
-
try {
|
|
84
|
-
return readFileSync(path, 'utf8')
|
|
85
|
-
} catch {
|
|
86
|
-
return ''
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export const slopnator: Slopnator = {
|
|
91
|
-
checks: (root) => {
|
|
92
|
-
const tree = findTree(root)
|
|
93
|
-
|
|
94
|
-
const held = new Set<string>()
|
|
95
|
-
for (const path of filesIn(tree.dir)) for (const one of heldIn(read(path))) held.add(one)
|
|
96
|
-
|
|
97
|
-
const wired = new Set<string>()
|
|
98
|
-
for (const path of filesIn(tree.dir)) {
|
|
99
|
-
for (const one of read(path).matchAll(/node_modules\/(@[\w.-]+\/[\w.-]+)\//g)) wired.add(one[1] ?? '')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const found: string[] = []
|
|
103
|
-
for (const pkg of installed(root)) {
|
|
104
|
-
if (!wired.has(pkg.name)) continue
|
|
105
|
-
for (const path of filesIn(pkg.src)) {
|
|
106
|
-
const shipped = shippedIn(read(path))
|
|
107
|
-
if (shipped.size === 0) continue
|
|
108
|
-
for (const id of [...shipped].sort()) {
|
|
109
|
-
if (held.has(id)) continue
|
|
110
|
-
found.push(
|
|
111
|
-
`${pkg.name} ships \`${id}\` and this tree does not hold it — run \`thread attach ${pkg.name}\` to put it back`,
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return found
|
|
117
|
-
},
|
|
118
|
-
proves: {
|
|
119
|
-
wrong: [
|
|
120
|
-
{
|
|
121
|
-
files: [
|
|
122
|
-
{ at: '_threads/_probe_missing.ts', source: "import type { Threaded } from '../../core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'node_modules/@prestack/thread-probe/dist/foo.ts',\n },\n ],\n}\n" },
|
|
123
|
-
{ at: 'node_modules/@prestack/thread-probe/dist/foo.ts', source: "export const PKG: Threaded = {\n declares: [\n {\n id: 'missing-thing',\n },\n ],\n}\n" },
|
|
124
|
-
],
|
|
125
|
-
at: '_threads/_probe_missing.ts',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
right: [
|
|
129
|
-
{
|
|
130
|
-
files: [
|
|
131
|
-
{ 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" },
|
|
132
|
-
{ at: '_threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: '_threads/_probe_missing.ts',\n },\n ],\n}\n" },
|
|
133
|
-
{ at: '_threads/_probe_missing.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'colour',\n },\n {\n id: 'missing-thing',\n },\n {\n at: 'node_modules/@prestack/thread-probe/dist/foo.ts',\n },\n ],\n}\n" },
|
|
134
|
-
{ at: 'node_modules/@prestack/thread-probe/dist/foo.ts', source: "export const PKG: Threaded = {\n declares: [\n {\n id: 'missing-thing',\n },\n ],\n}\n" },
|
|
135
|
-
],
|
|
136
|
-
at: '_threads/_probe_missing.ts',
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { dirname, join, normalize } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
4
|
-
|
|
5
|
-
import { ROOT, descend, read, reached } from '../../core/descend.ts'
|
|
6
|
-
import { rowsIn } from './sl_unthreaded.ts'
|
|
7
|
-
|
|
8
|
-
const DOOR = 'programs'
|
|
9
|
-
|
|
10
|
-
const ROOTED = ROOT + '/'
|
|
11
|
-
|
|
12
|
-
const slash = (one: string): string => one.split('\\').join('/')
|
|
13
|
-
|
|
14
|
-
const bare = (one: string): string => one.replace(/\.[^./]+$/, '')
|
|
15
|
-
|
|
16
|
-
function doorway(): string | undefined {
|
|
17
|
-
descend()
|
|
18
|
-
|
|
19
|
-
for (const at of reached()) {
|
|
20
|
-
const text = read(at)
|
|
21
|
-
if (text === undefined) continue
|
|
22
|
-
for (const one of rowsIn(text)) {
|
|
23
|
-
if (one.id === DOOR && one.at !== undefined) return one.at
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return undefined
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface Node {
|
|
31
|
-
readonly type?: string
|
|
32
|
-
readonly source?: { readonly type?: string; readonly value?: unknown }
|
|
33
|
-
readonly callee?: { readonly type?: string; readonly name?: string }
|
|
34
|
-
readonly arguments?: readonly { readonly type?: string; readonly value?: unknown }[]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const named = (held: unknown): string | undefined => {
|
|
38
|
-
const one = held as { type?: string; value?: unknown } | undefined
|
|
39
|
-
return one?.type === 'Literal' && typeof one.value === 'string' ? one.value : undefined
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const slopnator: Slopnator = {
|
|
43
|
-
on: ['ImportDeclaration', 'ImportExpression', 'CallExpression'],
|
|
44
|
-
holds: (node, at) => {
|
|
45
|
-
const held = node as Node
|
|
46
|
-
const from =
|
|
47
|
-
held.type === 'ImportDeclaration' || held.type === 'ImportExpression'
|
|
48
|
-
? (named(held.source) ?? named(held.arguments?.[0]))
|
|
49
|
-
: held.callee?.name === 'require'
|
|
50
|
-
? named(held.arguments?.[0])
|
|
51
|
-
: undefined
|
|
52
|
-
|
|
53
|
-
if (from === undefined) return false
|
|
54
|
-
|
|
55
|
-
const door = doorway()
|
|
56
|
-
if (door === undefined) return false
|
|
57
|
-
|
|
58
|
-
const here = at.filename.startsWith(ROOTED) ? at.filename.slice(ROOTED.length) : at.filename
|
|
59
|
-
const aimed = from.startsWith('.') ? slash(normalize(join(dirname(here), from))) : from
|
|
60
|
-
if (bare(aimed) !== bare(door)) return false
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
'it imports ' + door + ', which is where every process this framework starts comes from. A check that can reach it can ' +
|
|
64
|
-
'start something, and whatever it starts can reach back into the enforcement that called it'
|
|
65
|
-
)
|
|
66
|
-
},
|
|
67
|
-
proves: {
|
|
68
|
-
wrong: [
|
|
69
|
-
{
|
|
70
|
-
files: [
|
|
71
|
-
{ 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" },
|
|
72
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_programs.ts',\n },\n ],\n}\n" },
|
|
73
|
-
{ at: 'threads/_probe_programs.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const INTEGRATIONS: Threaded = {\n id: 'integrations',\n declares: [\n {\n id: 'programs',\n at: 'thread/integrations/cli.ts',\n },\n ],\n}\n" },
|
|
74
|
-
{ at: 'thread/integrity/slopnators/sl_probe_spawn.ts', source: "import { starts } from '../../integrations/cli.ts'\nexport const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n" },
|
|
75
|
-
],
|
|
76
|
-
at: 'thread/integrity/slopnators/sl_probe_spawn.ts',
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
right: [
|
|
80
|
-
{
|
|
81
|
-
files: [
|
|
82
|
-
{
|
|
83
|
-
at: 'thread/integrity/slopnators/sl_probe_spawn.ts',
|
|
84
|
-
source: "import { dirname } from 'node:path'\nexport const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
at: 'thread/integrity/slopnators/sl_probe_spawn.ts',
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
files: [
|
|
91
|
-
{
|
|
92
|
-
at: 'thread/integrity/slopnators/sl_probe_spawn.ts',
|
|
93
|
-
source: "export const slopnator = { holds: () => false, proves: { wrong: ['a'], right: ['b'] } }\n",
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
at: 'thread/integrity/slopnators/sl_probe_spawn.ts',
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { dirname, join, normalize } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import { parser } from 'typescript-eslint'
|
|
4
|
-
|
|
5
|
-
import { descend } from '../../core/descend.ts'
|
|
6
|
-
import { PREFIXES } from '../../core/thread.ts'
|
|
7
|
-
|
|
8
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
9
|
-
import { here, index, parses, unreadable } from './sl_unthreaded.ts'
|
|
10
|
-
|
|
11
|
-
const RELATIVE = /^\.\.?\//
|
|
12
|
-
|
|
13
|
-
const UNDER = 'operations/'
|
|
14
|
-
|
|
15
|
-
const COMPILES = 'thread compile'
|
|
16
|
-
|
|
17
|
-
const INVOKER = 'dispatch'
|
|
18
|
-
|
|
19
|
-
const slash = (one: string): string => one.split('\\').join('/')
|
|
20
|
-
|
|
21
|
-
interface Held {
|
|
22
|
-
readonly type?: string
|
|
23
|
-
readonly name?: string
|
|
24
|
-
readonly value?: unknown
|
|
25
|
-
readonly callee?: Held
|
|
26
|
-
readonly source?: Held
|
|
27
|
-
readonly arguments?: readonly Held[]
|
|
28
|
-
readonly quasis?: readonly { readonly value?: { readonly cooked?: string; readonly raw?: string } }[]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const IMPORTED = 'ImportBinding'
|
|
32
|
-
|
|
33
|
-
const TYPE_ONLY = 'type'
|
|
34
|
-
|
|
35
|
-
const QUERIED = 'TSTypeQuery'
|
|
36
|
-
|
|
37
|
-
let bodies: ReadonlySet<string> | undefined
|
|
38
|
-
|
|
39
|
-
function operations(): ReadonlySet<string> {
|
|
40
|
-
if (bodies !== undefined) return bodies
|
|
41
|
-
|
|
42
|
-
const prefixed = Object.values(PREFIXES)
|
|
43
|
-
const found = new Set<string>()
|
|
44
|
-
for (const [at, threadings] of descend().threaded) {
|
|
45
|
-
for (const one of threadings) {
|
|
46
|
-
if (one.list !== 'derives') continue
|
|
47
|
-
if (one.row.by !== COMPILES) continue
|
|
48
|
-
if (!prefixed.some((each) => one.by.startsWith(each))) continue
|
|
49
|
-
found.add(at)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
bodies = found
|
|
53
|
-
return found
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isBody(path: string): boolean {
|
|
57
|
-
const known = index().reached
|
|
58
|
-
if (known.has(path)) return operations().has(path)
|
|
59
|
-
if (!path.startsWith(UNDER)) return false
|
|
60
|
-
const file = path.slice(UNDER.length)
|
|
61
|
-
return !file.includes('/') && Object.values(PREFIXES).some((one) => file.startsWith(one))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let endings: ReadonlyMap<string, string> | undefined
|
|
65
|
-
|
|
66
|
-
function spellings(): ReadonlyMap<string, string> {
|
|
67
|
-
if (endings !== undefined) return endings
|
|
68
|
-
const found = new Map<string, string>()
|
|
69
|
-
for (const one of index().reached) {
|
|
70
|
-
const dot = one.lastIndexOf('.')
|
|
71
|
-
if (dot > one.lastIndexOf('/')) found.set(one.slice(0, dot), one)
|
|
72
|
-
}
|
|
73
|
-
endings = found
|
|
74
|
-
return found
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function reaches(said: string, from: string): boolean {
|
|
78
|
-
if (said === '') return false
|
|
79
|
-
const at = slash(normalize(RELATIVE.test(said) ? join(from, said) : said))
|
|
80
|
-
if (isBody(at)) return true
|
|
81
|
-
const whole = spellings().get(at)
|
|
82
|
-
return whole !== undefined && isBody(whole)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function spelled(node: Held | undefined, said: string[]): void {
|
|
86
|
-
if (node === undefined || node === null || typeof node !== 'object') return
|
|
87
|
-
if (node.type === 'Literal' && typeof node.value === 'string') said.push(node.value)
|
|
88
|
-
for (const one of node.quasis ?? []) {
|
|
89
|
-
const held = one.value?.cooked ?? one.value?.raw
|
|
90
|
-
if (typeof held === 'string') said.push(held)
|
|
91
|
-
}
|
|
92
|
-
for (const value of Object.values(node as Record<string, unknown>)) {
|
|
93
|
-
if (Array.isArray(value)) for (const one of value) spelled(one as Held, said)
|
|
94
|
-
else if (value !== null && typeof value === 'object') spelled(value as Held, said)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface Bound {
|
|
99
|
-
readonly type?: string
|
|
100
|
-
readonly node?: { readonly importKind?: string }
|
|
101
|
-
readonly parent?: { readonly importKind?: string; readonly source?: { readonly value?: unknown } }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
interface Named {
|
|
105
|
-
readonly defs?: readonly Bound[]
|
|
106
|
-
readonly references?: readonly { readonly identifier?: unknown; readonly isValueReference?: boolean }[]
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
interface Scoped {
|
|
110
|
-
readonly scopes?: readonly { readonly variables?: readonly Named[] }[]
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
interface Parsed {
|
|
114
|
-
readonly ast: unknown
|
|
115
|
-
readonly scopeManager?: Scoped
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function handled(scopes: Scoped | undefined, from: string): ReadonlySet<unknown> {
|
|
119
|
-
const found = new Set<unknown>()
|
|
120
|
-
for (const scope of scopes?.scopes ?? []) {
|
|
121
|
-
for (const one of scope.variables ?? []) {
|
|
122
|
-
const made = (one.defs ?? [])[0]
|
|
123
|
-
if (made?.type !== IMPORTED) continue
|
|
124
|
-
if (made.node?.importKind === TYPE_ONLY || made.parent?.importKind === TYPE_ONLY) continue
|
|
125
|
-
const said = made.parent?.source?.value
|
|
126
|
-
if (typeof said !== 'string' || !reaches(said, from)) continue
|
|
127
|
-
for (const seen of one.references ?? []) {
|
|
128
|
-
if (seen.isValueReference !== false && seen.identifier !== undefined) found.add(seen.identifier)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return found
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function walked(
|
|
136
|
-
node: unknown,
|
|
137
|
-
up: Held | undefined,
|
|
138
|
-
seen: (one: Held, parent: Held | undefined) => string | undefined,
|
|
139
|
-
): string | undefined {
|
|
140
|
-
if (node === null || typeof node !== 'object') return undefined
|
|
141
|
-
if (Array.isArray(node)) {
|
|
142
|
-
for (const one of node) {
|
|
143
|
-
const said = walked(one, up, seen)
|
|
144
|
-
if (said !== undefined) return said
|
|
145
|
-
}
|
|
146
|
-
return undefined
|
|
147
|
-
}
|
|
148
|
-
const held = node as Held
|
|
149
|
-
const real = typeof held.type === 'string'
|
|
150
|
-
if (real) {
|
|
151
|
-
const said = seen(held, up)
|
|
152
|
-
if (said !== undefined) return said
|
|
153
|
-
}
|
|
154
|
-
for (const value of Object.values(node as Record<string, unknown>)) {
|
|
155
|
-
const said = walked(value, real ? held : up, seen)
|
|
156
|
-
if (said !== undefined) return said
|
|
157
|
-
}
|
|
158
|
-
return undefined
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export const slopnator: Slopnator = {
|
|
162
|
-
holds: (_node, at) => {
|
|
163
|
-
const path = here(at.filename)
|
|
164
|
-
if (unreadable(path, at.text) !== undefined) return false
|
|
165
|
-
if (!parses(path)) return false
|
|
166
|
-
|
|
167
|
-
const text = String(at.text)
|
|
168
|
-
if (!text.includes(UNDER)) return false
|
|
169
|
-
|
|
170
|
-
const from = dirname(path)
|
|
171
|
-
|
|
172
|
-
let parsed: Parsed
|
|
173
|
-
try {
|
|
174
|
-
parsed = (parser.parseForESLint as (t: string, o: unknown) => Parsed)(text, {
|
|
175
|
-
range: true,
|
|
176
|
-
ecmaVersion: 'latest',
|
|
177
|
-
sourceType: 'module',
|
|
178
|
-
ecmaFeatures: { jsx: true },
|
|
179
|
-
})
|
|
180
|
-
} catch {
|
|
181
|
-
return false
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const mine = handled(parsed.scopeManager, from)
|
|
185
|
-
|
|
186
|
-
return (
|
|
187
|
-
walked(parsed.ast, undefined, (held, parent) => {
|
|
188
|
-
const dynamic =
|
|
189
|
-
held.type === 'ImportExpression'
|
|
190
|
-
? held.source
|
|
191
|
-
: held.type === 'CallExpression' && held.callee?.type === 'Identifier' && held.callee.name === 'require'
|
|
192
|
-
? held.arguments?.[0]
|
|
193
|
-
: undefined
|
|
194
|
-
|
|
195
|
-
if (dynamic !== undefined) {
|
|
196
|
-
const said: string[] = []
|
|
197
|
-
spelled(dynamic, said)
|
|
198
|
-
const reaching = said.find((one) => one.includes(UNDER) || reaches(one, from))
|
|
199
|
-
if (reaching !== undefined) {
|
|
200
|
-
return (
|
|
201
|
-
`this reaches \`${reaching}\` at run time, which points at an operation. An operation is handed ` +
|
|
202
|
-
`to \`${INVOKER}\`, and a file that goes looking for one is looking for a second way in`
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (held.type !== 'Identifier' || typeof held.name !== 'string' || !mine.has(held)) return undefined
|
|
208
|
-
if (parent?.type === QUERIED) return undefined
|
|
209
|
-
if (
|
|
210
|
-
parent?.type === 'CallExpression' &&
|
|
211
|
-
parent.callee?.type === 'Identifier' &&
|
|
212
|
-
parent.callee.name === INVOKER &&
|
|
213
|
-
(parent.arguments ?? []).includes(held)
|
|
214
|
-
) {
|
|
215
|
-
return undefined
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return (
|
|
219
|
-
`\`${held.name}\` is an operation, and this does something with it other than hand it to ` +
|
|
220
|
-
`\`${INVOKER}\`. Every gate that decides whether it may run is in \`${INVOKER}\` — naming it, ` +
|
|
221
|
-
'aliasing it, taking it apart or passing it on all reach past every one of them'
|
|
222
|
-
)
|
|
223
|
-
}) ?? false
|
|
224
|
-
)
|
|
225
|
-
},
|
|
226
|
-
proves: {
|
|
227
|
-
wrong: [
|
|
228
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation.run(input)",
|
|
229
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst { run } = operation\n\nawait run(opened, held)",
|
|
230
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation(input)",
|
|
231
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait somewhere(operation, input)",
|
|
232
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst one = operation\n\nawait one.run(input)",
|
|
233
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst { requires } = operation",
|
|
234
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nlet one\none = operation\nawait one.run(input)",
|
|
235
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nconst [one] = [operation]\n\nawait one.run(input)",
|
|
236
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nawait (0, operation).run(input)",
|
|
237
|
-
"import { operation } from './operations/wf_send_invoice.ts'\n\nexport { operation }",
|
|
238
|
-
"const held = await import('./operations/wf_send_invoice.ts')",
|
|
239
|
-
"const held = require('../operations/wf_send_invoice.ts')",
|
|
240
|
-
"const held = await import('../operations/' + name + '.ts')",
|
|
241
|
-
'const held = await import(`../operations/${name}.ts`)',
|
|
242
|
-
],
|
|
243
|
-
right: [
|
|
244
|
-
{
|
|
245
|
-
files: [
|
|
246
|
-
{ 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" },
|
|
247
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
|
|
248
|
-
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
249
|
-
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nawait dispatch(operation, input, actor, opened)\n" },
|
|
250
|
-
],
|
|
251
|
-
at: 'probe.tsx',
|
|
252
|
-
},
|
|
253
|
-
"import type { Operation } from '../../operations/operations.ts'\n\nexport const held: Operation = one",
|
|
254
|
-
{
|
|
255
|
-
files: [
|
|
256
|
-
{ 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" },
|
|
257
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
|
|
258
|
-
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
259
|
-
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nawait dispatch(wf_send_invoice_body, input, actor, opened)\n" },
|
|
260
|
-
],
|
|
261
|
-
at: 'probe.tsx',
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
files: [
|
|
265
|
-
{ 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" },
|
|
266
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
|
|
267
|
-
{ at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n describes: 'sends an invoice',\n derives: [\n {\n id: 'wf_send_invoice_body',\n at: 'operations/wf_send_invoice.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
|
|
268
|
-
{ at: 'probe.tsx', source: "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nlet one: typeof operation\n\nawait dispatch(operation, input, actor, opened)\n" },
|
|
269
|
-
],
|
|
270
|
-
at: 'probe.tsx',
|
|
271
|
-
},
|
|
272
|
-
"import * as steps from './operations/wf_send_invoice/steps.ts'\n\nexport const held = steps.reads_the_invoice",
|
|
273
|
-
"import * as steps from './steps/wf_send_invoice.ts'\n\nexport const held = steps.reads_the_invoice",
|
|
274
|
-
],
|
|
275
|
-
},
|
|
276
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from '../../integrations/disk.ts'
|
|
2
|
-
import { dirname, resolve } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
5
|
-
import { findTree } from '../../core/tree.ts'
|
|
6
|
-
|
|
7
|
-
const ROOT = findTree().at
|
|
8
|
-
|
|
9
|
-
const IMPORTS = /(?:^|\n)\s*import\s+(?!type\s)[^'"]*['"]([^'"]+)['"]/g
|
|
10
|
-
|
|
11
|
-
type Node = {
|
|
12
|
-
source?: { value?: unknown }
|
|
13
|
-
importKind?: string
|
|
14
|
-
specifiers?: readonly { importKind?: string }[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const target = (from: string, spec: string): string | undefined =>
|
|
18
|
-
spec.startsWith('.') ? resolve(dirname(from), spec) : undefined
|
|
19
|
-
|
|
20
|
-
const erased = (node: Node): boolean =>
|
|
21
|
-
node.importKind === 'type' ||
|
|
22
|
-
((node.specifiers ?? []).length > 0 && (node.specifiers ?? []).every((one) => one.importKind === 'type'))
|
|
23
|
-
|
|
24
|
-
export const slopnator: Slopnator = {
|
|
25
|
-
on: ['ImportDeclaration'],
|
|
26
|
-
holds: (node, at) => {
|
|
27
|
-
if (erased(node as Node)) return false
|
|
28
|
-
const spec = (node as Node).source?.value
|
|
29
|
-
if (typeof spec !== 'string') return false
|
|
30
|
-
|
|
31
|
-
const here = resolve(ROOT, at.filename)
|
|
32
|
-
const there = target(here, spec)
|
|
33
|
-
if (there === undefined || !existsSync(there)) return false
|
|
34
|
-
|
|
35
|
-
for (const found of readFileSync(there, 'utf8').matchAll(IMPORTS)) {
|
|
36
|
-
const back = found[1] ?? ''
|
|
37
|
-
if (target(there, back) === here) return `${spec} imports \`${back}\` straight back, so the two initialise each other`
|
|
38
|
-
}
|
|
39
|
-
return false
|
|
40
|
-
},
|
|
41
|
-
proves: {
|
|
42
|
-
wrong: [
|
|
43
|
-
{
|
|
44
|
-
files: [
|
|
45
|
-
{ at: 'threads/_probe_one.ts', source: "import { two } from './_probe_two.ts'\nexport const one = two\n" },
|
|
46
|
-
{ at: 'threads/_probe_two.ts', source: "import { one } from './_probe_one.ts'\nexport const two = one\n" },
|
|
47
|
-
],
|
|
48
|
-
at: 'threads/_probe_one.ts',
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
right: [
|
|
52
|
-
"import type { Law } from '../../core/generators/meta.ts'",
|
|
53
|
-
"import { SLUG, type Shaped } from '../../core/types.ts'",
|
|
54
|
-
"import { join } from 'node:path'",
|
|
55
|
-
"import type { Rule } from 'eslint'",
|
|
56
|
-
"import { CONSTRAINTS } from '../constraint.ts'",
|
|
57
|
-
{
|
|
58
|
-
files: [
|
|
59
|
-
{ at: 'threads/_probe_three.ts', source: "import type { Four } from './_probe_four.ts'\nexport const three: Four = 1\n" },
|
|
60
|
-
{ at: 'threads/_probe_four.ts', source: "import { three } from './_probe_three.ts'\nexport type Four = number\nexport const four = three\n" },
|
|
61
|
-
],
|
|
62
|
-
at: 'threads/_probe_three.ts',
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
}
|