@prestack/thread 0.1.3 → 0.1.5
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 +563 -0
- package/dist/core/actor.d.ts +1 -1
- package/dist/core/actor.js +5 -4
- package/dist/core/actor.ts +5 -4
- package/dist/core/attach.d.ts +0 -7
- package/dist/core/attach.js +38 -67
- package/dist/core/attach.ts +34 -80
- package/dist/core/chain.js +13 -17
- package/dist/core/chain.ts +11 -15
- package/dist/core/condition.d.ts +4 -4
- package/dist/core/condition.js +2 -2
- package/dist/core/descend.d.ts +4 -2
- package/dist/core/descend.js +91 -23
- package/dist/core/descend.ts +90 -21
- package/dist/core/errors/catalogue.d.ts +52 -0
- package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
- package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
- package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
- package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
- package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
- package/dist/core/errors/namespaces.d.ts +3 -0
- package/dist/core/errors/namespaces.js +15 -0
- package/dist/core/errors/namespaces.ts +19 -0
- package/dist/core/finding.js +2 -2
- package/dist/core/finding.ts +1 -2
- package/dist/core/init.d.ts +1 -1
- package/dist/core/init.js +51 -5
- package/dist/core/init.ts +58 -5
- package/dist/core/invoked.d.ts +1 -1
- package/dist/core/invoked.js +3 -1
- package/dist/core/invoked.ts +5 -2
- package/dist/core/round.js +36 -22
- package/dist/core/round.ts +26 -9
- package/dist/core/rows.d.ts +4 -0
- package/dist/core/rows.js +31 -2
- package/dist/core/rows.ts +33 -1
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/scope.ts +2 -0
- package/dist/core/thread.d.ts +3 -3
- package/dist/core/tree.d.ts +4 -1
- package/dist/core/tree.js +26 -6
- package/dist/core/tree.ts +29 -6
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +3 -1
- package/dist/core/types.ts +2 -1
- package/dist/derivation/attach.js +3 -2
- package/dist/derivation/attach.ts +2 -1
- package/dist/derivation/generators/build.js +6 -6
- package/dist/derivation/generators/build.ts +1 -1
- package/dist/derivation/generators/check.js +1 -1
- package/dist/derivation/generators/determinism.js +1 -1
- package/dist/derivation/generators/release.js +60 -47
- package/dist/derivation/generators/release.ts +66 -43
- package/dist/derivation/release.d.ts +7 -3
- package/dist/derivation/release.js +14 -1
- package/dist/derivation/release.ts +22 -2
- package/dist/integrations/attach.d.ts +4 -0
- package/dist/integrations/attach.js +37 -56
- package/dist/integrations/attach.ts +54 -60
- package/dist/integrations/cli.d.ts +37 -0
- package/dist/integrations/cli.js +440 -0
- package/dist/integrations/cli.ts +543 -0
- package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
- package/dist/integrations/disk.js +435 -0
- package/dist/integrations/disk.ts +465 -0
- package/dist/integrations/generators/read.js +6 -6
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +10 -11
- package/dist/integrations/generators/vault.ts +4 -5
- package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
- package/dist/integrations/https.js +170 -0
- package/dist/integrations/https.ts +216 -0
- package/dist/integrations/pglite.d.ts +26 -0
- package/dist/integrations/pglite.js +143 -0
- package/dist/integrations/pglite.ts +206 -0
- package/dist/integrations/secret.d.ts +8 -2
- package/dist/integrations/secret.js +3 -5
- package/dist/integrations/secret.ts +10 -6
- package/dist/integrations/target.d.ts +155 -14
- package/dist/integrations/target.js +171 -50
- package/dist/integrations/target.ts +377 -61
- package/dist/integrations/vault.d.ts +1 -0
- package/dist/integrations/vault.js +20 -5
- package/dist/integrations/vault.ts +17 -5
- package/dist/integrity/attach.js +8 -77
- package/dist/integrity/attach.ts +7 -76
- package/dist/integrity/check-threads.js +5 -5
- package/dist/integrity/check-threads.ts +2 -2
- package/dist/integrity/constraint.d.ts +0 -1
- package/dist/integrity/constraint.js +267 -366
- package/dist/integrity/constraint.ts +267 -375
- package/dist/integrity/generators/compiler.js +14 -17
- package/dist/integrity/generators/compiler.ts +10 -15
- package/dist/integrity/generators/gate.js +15 -15
- package/dist/integrity/generators/gate.ts +10 -10
- package/dist/integrity/generators/issues.js +7 -7
- package/dist/integrity/generators/publish.js +39 -57
- package/dist/integrity/generators/publish.ts +44 -55
- package/dist/integrity/generators/trial.d.ts +3 -3
- package/dist/integrity/generators/trial.js +133 -165
- package/dist/integrity/generators/trial.ts +145 -169
- package/dist/integrity/lint.js +9 -13
- package/dist/integrity/lint.ts +7 -10
- package/dist/integrity/lsp/bin.js +5 -4
- package/dist/integrity/lsp/bin.ts +3 -1
- package/dist/integrity/lsp/diagnostics.js +3 -3
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +19 -30
- package/dist/integrity/lsp/lint.ts +16 -32
- package/dist/integrity/lsp/server.js +3 -3
- package/dist/integrity/slopnator.d.ts +3 -13
- package/dist/integrity/slopnator.js +59 -38
- package/dist/integrity/slopnator.ts +54 -38
- package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
- package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
- package/dist/integrity/slopnators/sl_inert_island.js +10 -8
- package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
- package/dist/integrity/slopnators/sl_missing_row.js +6 -51
- package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
- package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
- package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
- package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
- package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
- package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
- package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
- package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
- package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
- package/dist/integrity/slopnators/sl_stale_path.js +5 -22
- package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
- package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
- package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
- package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
- package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
- package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
- package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
- package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
- package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
- package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
- package/dist/integrity/slopnators/sl_underived_route.js +61 -0
- package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
- package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
- package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
- package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
- package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
- package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
- package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
- package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
- package/dist/integrity/slopnators/sl_unversioned.js +6 -4
- package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
- package/dist/integrity/sweep.d.ts +1 -0
- package/dist/integrity/sweep.js +105 -42
- package/dist/integrity/sweep.ts +115 -42
- package/dist/knowledge/attach.js +4 -3
- package/dist/knowledge/attach.ts +3 -2
- package/dist/knowledge/generators/docsgen.js +8 -6
- package/dist/knowledge/generators/docsgen.ts +4 -2
- package/dist/knowledge/generators/glossary.js +4 -4
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/access.d.ts +23 -0
- package/dist/operations/access.js +28 -1
- package/dist/operations/access.ts +43 -0
- package/dist/operations/attach.js +33 -113
- package/dist/operations/attach.ts +51 -117
- package/dist/operations/capabilities.d.ts +14 -5
- package/dist/operations/capabilities.js +13 -4
- package/dist/operations/capabilities.ts +27 -8
- package/dist/operations/dispatch.d.ts +8 -2
- package/dist/operations/dispatch.js +128 -185
- package/dist/operations/dispatch.ts +138 -191
- package/dist/operations/error-results.d.ts +1 -1
- package/dist/operations/error-results.js +2 -2
- package/dist/operations/error-results.ts +2 -2
- package/dist/operations/generators/capability.d.ts +11 -1
- package/dist/operations/generators/capability.js +481 -224
- package/dist/operations/generators/capability.ts +562 -233
- package/dist/operations/generators/compile.js +66 -39
- package/dist/operations/generators/compile.ts +78 -39
- package/dist/operations/generators/permits.js +11 -12
- package/dist/operations/generators/permits.ts +3 -4
- package/dist/operations/generators/seed.d.ts +1 -0
- package/dist/operations/generators/seed.js +19 -17
- package/dist/operations/generators/seed.ts +22 -14
- package/dist/operations/generators/trial.d.ts +6 -8
- package/dist/operations/generators/trial.js +200 -70
- package/dist/operations/generators/trial.ts +230 -93
- package/dist/operations/nodes.d.ts +15 -16
- package/dist/operations/nodes.ts +16 -17
- package/dist/operations/operations.d.ts +40 -4
- package/dist/operations/operations.js +1 -0
- package/dist/operations/operations.ts +61 -4
- package/dist/operations/result.d.ts +2 -2
- package/dist/operations/result.ts +2 -2
- package/package.json +1 -4
- package/dist/core/answers.d.ts +0 -171
- package/dist/core/answers.js +0 -789
- package/dist/core/answers.ts +0 -999
- package/dist/core/client.d.ts +0 -63
- package/dist/core/client.js +0 -249
- package/dist/core/client.ts +0 -289
- package/dist/core/files.js +0 -171
- package/dist/core/files.ts +0 -222
- package/dist/integrations/module.d.ts +0 -6
- package/dist/integrations/module.js +0 -153
- package/dist/integrations/module.ts +0 -191
- package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
- package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
- package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
- package/dist/operations/cost.d.ts +0 -14
- package/dist/operations/cost.js +0 -37
- package/dist/operations/cost.ts +0 -58
- package/dist/operations/errors/errors.d.ts +0 -52
- package/dist/operations/http.js +0 -124
- package/dist/operations/http.ts +0 -169
- package/dist/operations/memory.d.ts +0 -104
- package/dist/operations/memory.js +0 -365
- package/dist/operations/memory.ts +0 -470
- package/dist/operations/metering.d.ts +0 -55
- package/dist/operations/metering.js +0 -247
- package/dist/operations/metering.ts +0 -328
- package/dist/operations/queue-policy.d.ts +0 -3
- package/dist/operations/queue-policy.js +0 -8
- package/dist/operations/queue-policy.ts +0 -10
- package/dist/operations/seams.d.ts +0 -239
- package/dist/operations/seams.js +0 -45
- package/dist/operations/seams.ts +0 -364
- package/dist/operations/store.d.ts +0 -14
- package/dist/operations/store.js +0 -1
- package/dist/operations/store.ts +0 -29
- /package/dist/{operations → core}/errors/classes.d.ts +0 -0
- /package/dist/{operations → core}/errors/classes.js +0 -0
- /package/dist/{operations → core}/errors/classes.ts +0 -0
- /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
- /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path'
|
|
2
|
+
|
|
3
|
+
import ts from 'typescript'
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from '../../integrations/disk.ts'
|
|
6
|
+
|
|
7
|
+
import type { Slopnator, SlopnatorContext } from '../slopnator.ts'
|
|
8
|
+
|
|
9
|
+
const LIST = 2
|
|
10
|
+
|
|
11
|
+
interface Held {
|
|
12
|
+
readonly type?: string
|
|
13
|
+
readonly value?: unknown
|
|
14
|
+
readonly name?: string
|
|
15
|
+
readonly operator?: string
|
|
16
|
+
readonly elements?: readonly (Held | null)[]
|
|
17
|
+
readonly properties?: readonly (Held | null)[]
|
|
18
|
+
readonly arguments?: readonly Held[]
|
|
19
|
+
readonly init?: Held | null
|
|
20
|
+
readonly id?: { readonly type?: string; readonly name?: string }
|
|
21
|
+
readonly callee?: Held
|
|
22
|
+
readonly object?: Held
|
|
23
|
+
readonly property?: Held
|
|
24
|
+
readonly expression?: Held
|
|
25
|
+
readonly argument?: Held | null
|
|
26
|
+
readonly test?: Held | null
|
|
27
|
+
readonly consequent?: Held | null
|
|
28
|
+
readonly alternate?: Held | null
|
|
29
|
+
readonly body?: readonly (Held | null)[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const WIDENED = 'TSAsExpression'
|
|
33
|
+
|
|
34
|
+
function bare(held: Held | null | undefined): Held | undefined {
|
|
35
|
+
let one = held ?? undefined
|
|
36
|
+
while (one?.type === WIDENED) one = one.expression
|
|
37
|
+
return one
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const spoken = (held: Held | null | undefined): boolean => {
|
|
41
|
+
const one = bare(held)
|
|
42
|
+
return one?.type === 'Literal' && typeof one.value === 'string'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const strings = (held: Held | null | undefined): boolean => {
|
|
46
|
+
const one = bare(held)
|
|
47
|
+
if (one?.type !== 'ArrayExpression') return false
|
|
48
|
+
const all = one.elements ?? []
|
|
49
|
+
return all.length >= LIST && all.every((each) => spoken(each) || woven(each))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SPREAD = 'SpreadElement'
|
|
53
|
+
|
|
54
|
+
function woven(held: Held | null | undefined): boolean {
|
|
55
|
+
const one = bare(held)
|
|
56
|
+
if (one?.type !== SPREAD) return false
|
|
57
|
+
let words = 0
|
|
58
|
+
let other = false
|
|
59
|
+
const walk = (node: unknown, seen: Set<unknown>): void => {
|
|
60
|
+
if (node === null || typeof node !== 'object' || seen.has(node)) return
|
|
61
|
+
seen.add(node)
|
|
62
|
+
if (Array.isArray(node)) {
|
|
63
|
+
for (const each of node) walk(each, seen)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const held = node as Held
|
|
67
|
+
if (held.type === 'Literal') {
|
|
68
|
+
if (typeof held.value === 'string') words += 1
|
|
69
|
+
else other = true
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
for (const [key, each] of Object.entries(node as Record<string, unknown>)) {
|
|
73
|
+
if (key !== BACK) walk(each, seen)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
walk(one.argument, new Set())
|
|
77
|
+
return words > 0 && !other
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function valued(held: Held): readonly Held[] {
|
|
81
|
+
const found: Held[] = []
|
|
82
|
+
for (const one of held.properties ?? []) {
|
|
83
|
+
if (one?.type === 'Property') found.push(one.value as Held)
|
|
84
|
+
}
|
|
85
|
+
return found
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function tabled(held: Held | null | undefined, seen: Set<unknown>): boolean {
|
|
89
|
+
const one = bare(held)
|
|
90
|
+
if (one === undefined || one.type !== 'ObjectExpression' || seen.has(one)) return false
|
|
91
|
+
seen.add(one)
|
|
92
|
+
const all = valued(one)
|
|
93
|
+
if (all.filter((each) => spoken(each)).length >= LIST) return true
|
|
94
|
+
return all.some((each) => strings(each) || tabled(each, seen))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function drawn(held: Held): string | undefined {
|
|
98
|
+
if (held.type !== 'NewExpression' || held.callee?.type !== 'Identifier' || held.callee.name !== 'Set') return undefined
|
|
99
|
+
const only = held.arguments ?? []
|
|
100
|
+
if (only.length !== 1) return undefined
|
|
101
|
+
const call = bare(only[0])
|
|
102
|
+
const callee = call?.callee
|
|
103
|
+
if (call?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return undefined
|
|
104
|
+
if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return undefined
|
|
105
|
+
const asked = callee.property?.name
|
|
106
|
+
if (asked !== 'keys' && asked !== 'values') return undefined
|
|
107
|
+
const from = bare((call.arguments ?? [])[0])
|
|
108
|
+
return from?.type === 'Identifier' ? from.name : undefined
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function adapted(node: ts.Node): Held | undefined {
|
|
112
|
+
if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
|
|
113
|
+
const inner = adapted(node.expression)
|
|
114
|
+
return inner === undefined ? undefined : { type: WIDENED, expression: inner }
|
|
115
|
+
}
|
|
116
|
+
if (ts.isStringLiteral(node)) return { type: 'Literal', value: node.text }
|
|
117
|
+
if (ts.isSpreadElement(node)) return { type: SPREAD, argument: adapted(node.expression) ?? null }
|
|
118
|
+
if (ts.isCallExpression(node)) {
|
|
119
|
+
return { type: 'CallExpression', arguments: node.arguments.map((one) => adapted(one)).filter((one): one is Held => one !== undefined) }
|
|
120
|
+
}
|
|
121
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
122
|
+
return { type: 'ArrayExpression', elements: node.elements.map((one) => adapted(one) ?? null) }
|
|
123
|
+
}
|
|
124
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
125
|
+
const properties: Held[] = []
|
|
126
|
+
for (const one of node.properties) {
|
|
127
|
+
if (!ts.isPropertyAssignment(one)) continue
|
|
128
|
+
const value = adapted(one.initializer)
|
|
129
|
+
if (value !== undefined) properties.push({ type: 'Property', value })
|
|
130
|
+
}
|
|
131
|
+
return { type: 'ObjectExpression', properties }
|
|
132
|
+
}
|
|
133
|
+
return undefined
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parsed(at: string, text: string): ts.SourceFile {
|
|
137
|
+
return ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function brought(source: ts.SourceFile, name: string): string | undefined {
|
|
141
|
+
for (const statement of source.statements) {
|
|
142
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue
|
|
143
|
+
const bindings = statement.importClause?.namedBindings
|
|
144
|
+
if (bindings === undefined || !ts.isNamedImports(bindings)) continue
|
|
145
|
+
if (!bindings.elements.some((one) => one.name.text === name)) continue
|
|
146
|
+
return statement.moduleSpecifier.text
|
|
147
|
+
}
|
|
148
|
+
return undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function elsewhere(at: SlopnatorContext, name: string): Held | undefined {
|
|
152
|
+
const specifier = brought(parsed(at.filename, at.text), name)
|
|
153
|
+
if (specifier === undefined || !specifier.startsWith('.')) return undefined
|
|
154
|
+
const path = resolve(dirname(at.filename), specifier)
|
|
155
|
+
let text: string
|
|
156
|
+
try {
|
|
157
|
+
text = readFileSync(path, 'utf8')
|
|
158
|
+
} catch {
|
|
159
|
+
return undefined
|
|
160
|
+
}
|
|
161
|
+
for (const statement of parsed(path, text).statements) {
|
|
162
|
+
if (!ts.isVariableStatement(statement)) continue
|
|
163
|
+
for (const one of statement.declarationList.declarations) {
|
|
164
|
+
if (!ts.isIdentifier(one.name) || one.name.text !== name || one.initializer === undefined) continue
|
|
165
|
+
return adapted(one.initializer)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function initOf(held: unknown, name: string, seen: Set<unknown>): Held | undefined {
|
|
172
|
+
if (held === null || typeof held !== 'object' || seen.has(held)) return undefined
|
|
173
|
+
seen.add(held)
|
|
174
|
+
if (Array.isArray(held)) {
|
|
175
|
+
for (const one of held) {
|
|
176
|
+
const found = initOf(one, name, seen)
|
|
177
|
+
if (found !== undefined) return found
|
|
178
|
+
}
|
|
179
|
+
return undefined
|
|
180
|
+
}
|
|
181
|
+
const node = held as Held
|
|
182
|
+
const init = node.init ?? undefined
|
|
183
|
+
if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier' && node.id.name === name && init !== undefined) return init
|
|
184
|
+
for (const [key, one] of Object.entries(held as Record<string, unknown>)) {
|
|
185
|
+
if (key === BACK) continue
|
|
186
|
+
const found = initOf(one, name, seen)
|
|
187
|
+
if (found !== undefined) return found
|
|
188
|
+
}
|
|
189
|
+
return undefined
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function listed(init: Held | null | undefined, program: unknown, at: SlopnatorContext, through: ReadonlySet<string>): boolean {
|
|
193
|
+
const one = bare(init)
|
|
194
|
+
if (one === undefined) return false
|
|
195
|
+
if (strings(one)) return true
|
|
196
|
+
if (tabled(one, new Set())) return true
|
|
197
|
+
if (one.type === 'NewExpression' || one.type === 'CallExpression') {
|
|
198
|
+
const only = one.arguments ?? []
|
|
199
|
+
if (only.length === 1 && strings(only[0])) return true
|
|
200
|
+
}
|
|
201
|
+
const from = drawn(one)
|
|
202
|
+
if (from === undefined || through.has(from)) return false
|
|
203
|
+
const followed = new Set(through)
|
|
204
|
+
followed.add(from)
|
|
205
|
+
const here = initOf(program, from, new Set())
|
|
206
|
+
return listed(here ?? elsewhere(at, from), program, at, followed)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const asks = (held: Held | undefined, name: string): boolean => {
|
|
210
|
+
const callee = held?.callee
|
|
211
|
+
if (held?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
|
|
212
|
+
const on = bare(callee.object)
|
|
213
|
+
if (on?.type !== 'Identifier' || on.name !== name) return false
|
|
214
|
+
const said = callee.property?.name
|
|
215
|
+
return said === 'has' || said === 'includes'
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const BACK = 'parent'
|
|
219
|
+
|
|
220
|
+
function asking(held: unknown, name: string, seen: Set<unknown>): boolean {
|
|
221
|
+
if (held === null || typeof held !== 'object' || seen.has(held)) return false
|
|
222
|
+
seen.add(held)
|
|
223
|
+
if (Array.isArray(held)) return held.some((one) => asking(one, name, seen))
|
|
224
|
+
if (asks(held as Held, name)) return true
|
|
225
|
+
return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && asking(one, name, seen))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function consults(node: Held, name: string): boolean {
|
|
229
|
+
if (node.type === 'MemberExpression') {
|
|
230
|
+
const on = bare(node.object)
|
|
231
|
+
if (on?.type === 'Identifier' && on.name === name) return true
|
|
232
|
+
}
|
|
233
|
+
const callee = node.callee
|
|
234
|
+
if (node.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
|
|
235
|
+
if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return false
|
|
236
|
+
const only = node.arguments ?? []
|
|
237
|
+
const from = bare(only[0])
|
|
238
|
+
return only.length === 1 && from?.type === 'Identifier' && from.name === name
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function consulted(held: unknown, name: string, seen: Set<unknown>): boolean {
|
|
242
|
+
if (held === null || typeof held !== 'object' || seen.has(held)) return false
|
|
243
|
+
seen.add(held)
|
|
244
|
+
if (Array.isArray(held)) return held.some((one) => consulted(one, name, seen))
|
|
245
|
+
if (consults(held as Held, name)) return true
|
|
246
|
+
return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && consulted(one, name, seen))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function continues(held: Held | null | undefined): boolean {
|
|
250
|
+
if (held === null || held === undefined) return false
|
|
251
|
+
if (held.type === 'ContinueStatement') return true
|
|
252
|
+
if (held.type !== 'BlockStatement') return false
|
|
253
|
+
return (held.body ?? []).some((one) => one?.type === 'ContinueStatement')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const LEAVES = 'ReturnStatement'
|
|
257
|
+
|
|
258
|
+
const THROWN = 'ThrowStatement'
|
|
259
|
+
|
|
260
|
+
const leaving = (one: Held | null | undefined): boolean => one?.type === LEAVES || one?.type === THROWN
|
|
261
|
+
|
|
262
|
+
function exits(held: Held | null | undefined): boolean {
|
|
263
|
+
if (held === null || held === undefined) return false
|
|
264
|
+
if (leaving(held)) return true
|
|
265
|
+
if (held.type !== 'BlockStatement') return false
|
|
266
|
+
return (held.body ?? []).some((one) => leaving(one))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function look(held: unknown, name: string, found: { gates: boolean; skips: boolean }, seen: Set<unknown>): void {
|
|
270
|
+
if (held === null || typeof held !== 'object' || seen.has(held)) return
|
|
271
|
+
seen.add(held)
|
|
272
|
+
if (Array.isArray(held)) {
|
|
273
|
+
for (const one of held) look(one, name, found, seen)
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
const node = held as Held
|
|
277
|
+
if (node.type === 'UnaryExpression' && node.operator === '!' && asks(node.argument ?? undefined, name)) {
|
|
278
|
+
found.skips = true
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
if (node.type === 'IfStatement' && continues(node.consequent) && asking(node.test, name, new Set())) {
|
|
282
|
+
found.skips = true
|
|
283
|
+
look(node.consequent, name, found, seen)
|
|
284
|
+
look(node.alternate, name, found, seen)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
if (node.type === 'IfStatement' && exits(node.consequent) && asking(node.test, name, new Set())) {
|
|
288
|
+
found.gates = true
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
if (asks(node, name)) {
|
|
292
|
+
found.gates = true
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
for (const [key, one] of Object.entries(node as Record<string, unknown>)) {
|
|
296
|
+
if (key !== BACK) look(one, name, found, seen)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const slopnator: Slopnator = {
|
|
301
|
+
on: ['VariableDeclarator'],
|
|
302
|
+
holds: (node, at) => {
|
|
303
|
+
const held = node as Held
|
|
304
|
+
const program = at.ancestors().find((one) => (one as Held | undefined)?.type === 'Program')
|
|
305
|
+
if (!listed(held.init, program, at, new Set<string>())) return false
|
|
306
|
+
const named = held.id?.type === 'Identifier' ? held.id.name : undefined
|
|
307
|
+
const table = bare(held.init)?.type === 'ObjectExpression'
|
|
308
|
+
if (table && (named === undefined || !consulted(program, named, new Set()))) return false
|
|
309
|
+
if (named !== undefined) {
|
|
310
|
+
const found = { gates: false, skips: false }
|
|
311
|
+
look(program, named, found, new Set())
|
|
312
|
+
if (!found.gates && found.skips) return false
|
|
313
|
+
}
|
|
314
|
+
return (
|
|
315
|
+
`\`${named ?? 'this'}\` is ${table ? 'a table' : 'a list'} of words written out here and this check refuses what is IN it, so ` +
|
|
316
|
+
'whatever breaks the rule under a name nobody typed walks straight past. Wrapping them in `as const`, nesting them under a key, ' +
|
|
317
|
+
'or building a set out of the keys leaves the same list one step further out of sight. A list that decides what a check CATCHES ' +
|
|
318
|
+
'has to come from the tree: `thread inspect <word>` names the row that coins any word you need, and a check that reads those ' +
|
|
319
|
+
'rows widens when the tree does. A list a check only SKIPS is left alone, because adding a word to one of those makes it ' +
|
|
320
|
+
'stricter and it can never go blind.'
|
|
321
|
+
)
|
|
322
|
+
},
|
|
323
|
+
proves: {
|
|
324
|
+
wrong: [
|
|
325
|
+
"const CROSSINGS = ['fetch', 'setTimeout', 'setInterval']",
|
|
326
|
+
"const STARTS = new Set(['node:child_process', 'child_process'])\nexport const opens = (from: string): boolean => {\n if (!STARTS.has(from)) return false\n return true\n}",
|
|
327
|
+
"const GLOBALS = new Set(['Date', 'performance', 'Math'])",
|
|
328
|
+
"const MOVES = new Set(['push', 'replace'])\nexport const moved = (one: string): boolean => MOVES.has(one)",
|
|
329
|
+
"const SURFACES = ['cli', 'api', 'mcp'] as const\nexport const surfaced = (one: string): boolean => (SURFACES as readonly string[]).includes(one)",
|
|
330
|
+
"const MOVING = { globals: ['window', 'globalThis'], histories: ['router', 'history'] }\nexport const moves = (one: string): boolean => MOVING.globals.includes(one)",
|
|
331
|
+
"const HANDED_BY = { postgres: 'store', pglite: 'store', https: 'http' }\nexport const handedBy = (reach: string): string => HANDED_BY[reach] ?? reach",
|
|
332
|
+
"const HANDS = { postgres: 'store', pglite: 'store' }\nconst PINS = new Set(Object.keys(HANDS))\nexport const pinned = (one: string): boolean => PINS.has(one)",
|
|
333
|
+
{
|
|
334
|
+
files: [
|
|
335
|
+
{ at: 'threads/held.ts', source: "export const PINNED = { clock: ['Date.now', 'setTimeout'] }\n" },
|
|
336
|
+
{ at: 'threads/slopnators/sl_probe_followed.ts', source: "import { PINNED } from '../held.ts'\n\nconst PINS = new Set(Object.keys(PINNED))\nexport const pinned = (one: string): boolean => PINS.has(one)\n" },
|
|
337
|
+
],
|
|
338
|
+
at: 'threads/slopnators/sl_probe_followed.ts',
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
right: [
|
|
342
|
+
'const CROSSINGS = declared()',
|
|
343
|
+
"const UNDER = 'operations/'",
|
|
344
|
+
"const COMMON = new Set(['a', 'the'])\nexport const words = (all: readonly string[]): readonly string[] => all.filter((one) => !COMMON.has(one))",
|
|
345
|
+
"const SKIP = new Set(['dist', 'node_modules'])\nexport function walk(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if (SKIP.has(name)) continue\n seen += 1\n }\n return seen\n}",
|
|
346
|
+
"const LEFT = ['dist', 'node_modules'] as const\nexport function walked(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if ((LEFT as readonly string[]).includes(name)) continue\n seen += 1\n }\n return seen\n}",
|
|
347
|
+
"export const SAYS = { cli: 'runs at a terminal', api: 'answers a request' }",
|
|
348
|
+
{
|
|
349
|
+
files: [
|
|
350
|
+
{ at: 'threads/made.ts', source: "export const MADE = declared()\n" },
|
|
351
|
+
{ at: 'threads/slopnators/sl_probe_derived.ts', source: "import { MADE } from '../made.ts'\n\nconst PICKED = new Set(Object.keys(MADE))\nexport const picked = (one: string): boolean => PICKED.has(one)\n" },
|
|
352
|
+
],
|
|
353
|
+
at: 'threads/slopnators/sl_probe_derived.ts',
|
|
354
|
+
},
|
|
355
|
+
"export const slopnator = { on: ['CallExpression', 'NewExpression'], holds: () => false, proves: { wrong: ['new Date()'], right: ['const at = 1'] } }",
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
}
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { dirname, join, normalize } from 'node:path';
|
|
2
|
-
import { here, index, parses, unreadable } from
|
|
3
|
-
/*
|
|
4
|
-
* Every string this file writes, whatever it writes it for.
|
|
5
|
-
*
|
|
6
|
-
* The first reading matched `node: '<name>'`, which was the shape when an operation named its node
|
|
7
|
-
* in a string beside a step of its own; a node builds its own span now, so that matched nothing on
|
|
8
|
-
* any real file. The second matched `import … from '<path>'`, which was the one route anybody had
|
|
9
|
-
* thought of — and a dynamic `import()`, a `require()` and an `export … from` each walked straight
|
|
10
|
-
* through it, measured on a real tree. Enumerating the ways in is how a refusal ends up as a list of
|
|
11
|
-
* whatever somebody happened to try. What matters is the path, not the syntax carrying it.
|
|
12
|
-
*/
|
|
2
|
+
import { here, index, parses, unreadable } from './sl_unthreaded.js';
|
|
13
3
|
const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g;
|
|
14
4
|
const RELATIVE = /^\.\.?\//;
|
|
15
5
|
const UNDER = 'nodes/';
|
|
@@ -3,16 +3,6 @@ import { dirname, join, normalize } from 'node:path'
|
|
|
3
3
|
import type { Slopnator } from '../slopnator.ts'
|
|
4
4
|
import { here, index, parses, unreadable } from './sl_unthreaded.ts'
|
|
5
5
|
|
|
6
|
-
/*
|
|
7
|
-
* Every string this file writes, whatever it writes it for.
|
|
8
|
-
*
|
|
9
|
-
* The first reading matched `node: '<name>'`, which was the shape when an operation named its node
|
|
10
|
-
* in a string beside a step of its own; a node builds its own span now, so that matched nothing on
|
|
11
|
-
* any real file. The second matched `import … from '<path>'`, which was the one route anybody had
|
|
12
|
-
* thought of — and a dynamic `import()`, a `require()` and an `export … from` each walked straight
|
|
13
|
-
* through it, measured on a real tree. Enumerating the ways in is how a refusal ends up as a list of
|
|
14
|
-
* whatever somebody happened to try. What matters is the path, not the syntax carrying it.
|
|
15
|
-
*/
|
|
16
6
|
const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g
|
|
17
7
|
|
|
18
8
|
const RELATIVE = /^\.\.?\//
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { descend, read, reached } from
|
|
2
|
-
import { rowsIn } from
|
|
1
|
+
import { descend, read, reached } from '../../core/descend.js';
|
|
2
|
+
import { rowsIn } from './sl_unthreaded.js';
|
|
3
3
|
const UNDER = 'operations/';
|
|
4
4
|
const DISPATCH = 'dispatch';
|
|
5
5
|
function declared() {
|
|
@@ -15,16 +15,111 @@ function declared() {
|
|
|
15
15
|
continue;
|
|
16
16
|
if (one.id !== undefined)
|
|
17
17
|
found.add(one.id);
|
|
18
|
-
const named = where.split('/').at(-1)?.replace(/\.[^.]+$/, '');
|
|
19
|
-
if (named !== undefined)
|
|
20
|
-
found.add(named);
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
20
|
return found;
|
|
24
21
|
}
|
|
22
|
+
function bodies() {
|
|
23
|
+
const found = new Set();
|
|
24
|
+
descend();
|
|
25
|
+
for (const at of reached()) {
|
|
26
|
+
const text = read(at);
|
|
27
|
+
if (text === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
for (const one of rowsIn(text)) {
|
|
30
|
+
const where = one.at?.split('\\').join('/');
|
|
31
|
+
if (where === undefined || !where.includes(UNDER))
|
|
32
|
+
continue;
|
|
33
|
+
if (one.id !== undefined)
|
|
34
|
+
found.add(where.replace(/\.[^./]+$/, ''));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
function declaring() {
|
|
40
|
+
const found = new Set();
|
|
41
|
+
descend();
|
|
42
|
+
for (const at of reached()) {
|
|
43
|
+
const text = read(at);
|
|
44
|
+
if (text === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
for (const one of rowsIn(text)) {
|
|
47
|
+
const where = one.at?.split('\\').join('/');
|
|
48
|
+
if (where === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
found.add(where.replace(/\.[^./]+$/, ''));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
function importedFrom(name, ancestors) {
|
|
56
|
+
const program = ancestors.find((one) => one?.type === 'Program');
|
|
57
|
+
for (const one of program?.body ?? []) {
|
|
58
|
+
if (one.type !== 'ImportDeclaration' || typeof one.source?.value !== 'string')
|
|
59
|
+
continue;
|
|
60
|
+
if (!(one.specifiers ?? []).some((each) => each.local?.name === name))
|
|
61
|
+
continue;
|
|
62
|
+
return one.source.value;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function namedIn(said) {
|
|
67
|
+
if (said === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (typeof said.typeName?.name === 'string')
|
|
70
|
+
return said.typeName.name;
|
|
71
|
+
const inner = namedIn(said.typeAnnotation);
|
|
72
|
+
if (inner !== undefined)
|
|
73
|
+
return inner;
|
|
74
|
+
for (const one of said.types ?? []) {
|
|
75
|
+
const found = namedIn(one);
|
|
76
|
+
if (found !== undefined)
|
|
77
|
+
return found;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const typeNamed = (held) => namedIn(held?.typeAnnotation?.typeAnnotation);
|
|
82
|
+
function annotating(name, ancestors) {
|
|
83
|
+
for (const held of ancestors) {
|
|
84
|
+
for (const param of held.params ?? []) {
|
|
85
|
+
if (param.type !== 'Identifier' || param.name !== name)
|
|
86
|
+
continue;
|
|
87
|
+
const said = typeNamed(param);
|
|
88
|
+
if (said !== undefined)
|
|
89
|
+
return said;
|
|
90
|
+
}
|
|
91
|
+
const body = held.body;
|
|
92
|
+
for (const one of Array.isArray(body) ? body : []) {
|
|
93
|
+
if (one.type !== 'VariableDeclaration')
|
|
94
|
+
continue;
|
|
95
|
+
for (const each of one.declarations ?? []) {
|
|
96
|
+
if (each.id?.type !== 'Identifier' || each.id.name !== name)
|
|
97
|
+
continue;
|
|
98
|
+
const said = typeNamed(each.id);
|
|
99
|
+
if (said !== undefined)
|
|
100
|
+
return said;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const settled = (from, source) => {
|
|
107
|
+
if (!source.startsWith('.'))
|
|
108
|
+
return source.replace(/\.[^./]+$/, '');
|
|
109
|
+
const parts = from.split('/').slice(0, -1);
|
|
110
|
+
for (const step of source.replace(/\.[^./]+$/, '').split('/')) {
|
|
111
|
+
if (step === '.' || step === '')
|
|
112
|
+
continue;
|
|
113
|
+
if (step === '..')
|
|
114
|
+
parts.pop();
|
|
115
|
+
else
|
|
116
|
+
parts.push(step);
|
|
117
|
+
}
|
|
118
|
+
return parts.join('/');
|
|
119
|
+
};
|
|
25
120
|
export const slopnator = {
|
|
26
121
|
on: ['CallExpression'],
|
|
27
|
-
holds: (node) => {
|
|
122
|
+
holds: (node, at) => {
|
|
28
123
|
const call = node;
|
|
29
124
|
const callee = call?.callee;
|
|
30
125
|
const named = callee?.type === 'Identifier'
|
|
@@ -39,33 +134,20 @@ export const slopnator = {
|
|
|
39
134
|
return ('dispatch is called with nothing to dispatch, so no operation is named and none can be found — the name ' +
|
|
40
135
|
'is the whole of what makes an operation reachable');
|
|
41
136
|
}
|
|
42
|
-
/*
|
|
43
|
-
* THE OPERATION ITSELF IS A BETTER ANSWER THAN ITS NAME, AND THIS USED TO REFUSE IT.
|
|
44
|
-
*
|
|
45
|
-
* This asked for a string literal and refused everything else, which was right when the only
|
|
46
|
-
* caller was a UI seam handed a bare name. It has been wrong since dispatch stopped taking one:
|
|
47
|
-
* the note above says so in as many words — "`dispatch` in packages/core/operations/src/dispatch.ts
|
|
48
|
-
* takes an Operation object rather than a name". What ships now imports the compiled operation
|
|
49
|
-
* and hands over the binding, and a binding cannot be assembled at run time: if it named nothing,
|
|
50
|
-
* the file would not compile.
|
|
51
|
-
*
|
|
52
|
-
* Measured 2026-08-31 on a registry install: an operations-only app, everything made by the CLI,
|
|
53
|
-
* failed its own sweep on `operations/_client.ts` — a file its own generator had just written,
|
|
54
|
-
* naming an operation the same tree declares. The gate refuses the build now, so a law that
|
|
55
|
-
* refuses a generated file is an app that cannot ship.
|
|
56
|
-
*
|
|
57
|
-
* Narrow on purpose. An identifier is accepted only when it IS one of the declared operations,
|
|
58
|
-
* which is the shape the generators emit — `import { operation as wf_charge }` and then
|
|
59
|
-
* `dispatch(wf_charge, …)`. A variable holding a computed name is still refused, because it does
|
|
60
|
-
* not match a declaration, and that is the hole this law was written for.
|
|
61
|
-
*/
|
|
62
137
|
const all = declared();
|
|
63
138
|
if (first.type === 'Identifier' && typeof first.name === 'string') {
|
|
64
139
|
if (all.has(first.name))
|
|
65
140
|
return false;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
141
|
+
const source = importedFrom(first.name, at.ancestors());
|
|
142
|
+
if (source !== undefined && bodies().has(settled(at.filename, source)))
|
|
143
|
+
return false;
|
|
144
|
+
const named = annotating(first.name, at.ancestors());
|
|
145
|
+
const typed = named === undefined ? undefined : importedFrom(named, at.ancestors());
|
|
146
|
+
if (typed !== undefined && declaring().has(settled(at.filename, typed)))
|
|
147
|
+
return false;
|
|
148
|
+
return (`dispatch is handed \`${first.name}\`, and it is not imported from a file any row declares as an operation. ` +
|
|
149
|
+
'A binding is only checkable when it is the operation — import the compiled operation and hand that over, so ' +
|
|
150
|
+
'what runs and what was declared are the same thing rather than two things that agree today');
|
|
69
151
|
}
|
|
70
152
|
if (first.type !== 'Literal' || typeof first.value !== 'string') {
|
|
71
153
|
return ('dispatch is handed a name this file does not write down, so nothing can hold it against what is declared. ' +
|