@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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Slopnator } from '../slopnator.ts'
|
|
2
2
|
|
|
3
|
+
import { descend, read, reached } from '../../core/descend.ts'
|
|
4
|
+
import { rowsIn } from './sl_unthreaded.ts'
|
|
5
|
+
|
|
3
6
|
interface Held {
|
|
4
7
|
readonly type?: string
|
|
5
8
|
readonly name?: unknown
|
|
@@ -11,9 +14,6 @@ interface Held {
|
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
const HREF = 'href'
|
|
14
|
-
const GLOBALS = new Set(['window', 'globalThis', 'self'])
|
|
15
|
-
const HISTORIES = new Set(['router', 'navigation', 'history'])
|
|
16
|
-
const MOVES = new Set(['push', 'replace', 'prefetch'])
|
|
17
17
|
|
|
18
18
|
const named = (held: Held | undefined): string | undefined => {
|
|
19
19
|
if (held?.type === 'Identifier' || held?.type === 'JSXIdentifier') return typeof held.name === 'string' ? held.name : undefined
|
|
@@ -27,39 +27,87 @@ const spoken = (held: Held): string | undefined => {
|
|
|
27
27
|
return undefined
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const SCREEN = 'screen'
|
|
31
|
+
|
|
32
|
+
const PASCAL = (id: string): string =>
|
|
33
|
+
id
|
|
34
|
+
.split(/[-_]/)
|
|
35
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
36
|
+
.join('')
|
|
37
|
+
|
|
38
|
+
const nameOf = (at: string): string =>
|
|
39
|
+
PASCAL((at.split('\\').join('/').split('/').at(-1) ?? at).replace(/^scr_/, '').replace(/\.[^.]+$/, ''))
|
|
40
|
+
|
|
41
|
+
const routeFor = (id: string): string => `/${id}`
|
|
42
|
+
|
|
43
|
+
function addressed(): ReadonlyMap<string, string> {
|
|
44
|
+
const found = new Map<string, string>()
|
|
45
|
+
|
|
46
|
+
descend()
|
|
47
|
+
|
|
48
|
+
for (const at of reached()) {
|
|
49
|
+
const text = read(at)
|
|
50
|
+
if (text === undefined) continue
|
|
51
|
+
|
|
52
|
+
let waiting: { readonly id: string; readonly at: string }[] = []
|
|
53
|
+
for (const one of rowsIn(text)) {
|
|
54
|
+
if (one.id === undefined) continue
|
|
55
|
+
if (one.list === 'declares') {
|
|
56
|
+
if (one.at !== undefined) waiting.push({ id: one.id, at: one.at })
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
if (one.list !== undefined) continue
|
|
60
|
+
if (one.id === SCREEN) for (const row of waiting) found.set(routeFor(row.id), nameOf(row.at))
|
|
61
|
+
waiting = []
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return found
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
export const slopnator: Slopnator = {
|
|
41
|
-
on: ['JSXAttribute', 'Property', '
|
|
69
|
+
on: ['JSXAttribute', 'Property', 'Literal'],
|
|
42
70
|
holds: (node) => {
|
|
43
71
|
const held = node as Held
|
|
44
|
-
|
|
45
|
-
if (
|
|
72
|
+
|
|
73
|
+
if (spoken(held) === HREF) {
|
|
74
|
+
return (
|
|
75
|
+
'`href` moves somebody without naming a screen, so the map holds no link for it — ' +
|
|
76
|
+
'the destination reads as unreachable to the orphan pass, the app reads as having no path to it, and it navigates correctly'
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (held.type !== 'Literal' || typeof held.value !== 'string') return false
|
|
81
|
+
|
|
82
|
+
const screen = addressed().get(held.value)
|
|
83
|
+
if (screen === undefined) return false
|
|
84
|
+
|
|
46
85
|
return (
|
|
47
|
-
|
|
48
|
-
'the destination reads as unreachable to the orphan pass
|
|
86
|
+
'this writes the address of ' + screen + ' rather than naming it, so the map holds no link for it — ' +
|
|
87
|
+
'the destination reads as unreachable to the orphan pass and the app navigates there anyway. Name the screen: navigateTo="' + screen + '"'
|
|
49
88
|
)
|
|
50
89
|
},
|
|
51
90
|
proves: {
|
|
52
91
|
wrong: [
|
|
53
92
|
"const link = { href: '/orders/42' }",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
'<a href="/orders/42">{label}</a>',
|
|
94
|
+
{
|
|
95
|
+
files: [
|
|
96
|
+
{ 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" },
|
|
97
|
+
{ 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_screens.ts',\n },\n ],\n}\n" },
|
|
98
|
+
{ at: 'threads/_probe_screens.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const SCREENS: Threaded = {\n id: 'screen',\n declares: [\n {\n id: 'orders',\n at: 'screens/scr_orders.tsx',\n },\n ],\n}\n" },
|
|
99
|
+
{ at: 'app/_probe_address.tsx', source: "export const HELD = router.push('/orders')\n" },
|
|
100
|
+
],
|
|
101
|
+
at: 'app/_probe_address.tsx',
|
|
102
|
+
},
|
|
58
103
|
],
|
|
59
104
|
right: [
|
|
60
|
-
'
|
|
105
|
+
'const path = router.pathname',
|
|
106
|
+
'<Link.linkInline navigateTo="Home" labelKey="home.title" />',
|
|
61
107
|
"const to = { navigateTo: 'Order', labelKey: 'order.title' }",
|
|
62
108
|
'const rows = orders.push(one)',
|
|
109
|
+
"const parts = one.split('/')",
|
|
110
|
+
"const at = '/api/webhook'",
|
|
63
111
|
],
|
|
64
112
|
},
|
|
65
113
|
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { readFileSync } from '../../integrations/disk.js';
|
|
4
|
+
const LIST = 2;
|
|
5
|
+
const WIDENED = 'TSAsExpression';
|
|
6
|
+
function bare(held) {
|
|
7
|
+
let one = held ?? undefined;
|
|
8
|
+
while (one?.type === WIDENED)
|
|
9
|
+
one = one.expression;
|
|
10
|
+
return one;
|
|
11
|
+
}
|
|
12
|
+
const spoken = (held) => {
|
|
13
|
+
const one = bare(held);
|
|
14
|
+
return one?.type === 'Literal' && typeof one.value === 'string';
|
|
15
|
+
};
|
|
16
|
+
const strings = (held) => {
|
|
17
|
+
const one = bare(held);
|
|
18
|
+
if (one?.type !== 'ArrayExpression')
|
|
19
|
+
return false;
|
|
20
|
+
const all = one.elements ?? [];
|
|
21
|
+
return all.length >= LIST && all.every((each) => spoken(each) || woven(each));
|
|
22
|
+
};
|
|
23
|
+
const SPREAD = 'SpreadElement';
|
|
24
|
+
function woven(held) {
|
|
25
|
+
const one = bare(held);
|
|
26
|
+
if (one?.type !== SPREAD)
|
|
27
|
+
return false;
|
|
28
|
+
let words = 0;
|
|
29
|
+
let other = false;
|
|
30
|
+
const walk = (node, seen) => {
|
|
31
|
+
if (node === null || typeof node !== 'object' || seen.has(node))
|
|
32
|
+
return;
|
|
33
|
+
seen.add(node);
|
|
34
|
+
if (Array.isArray(node)) {
|
|
35
|
+
for (const each of node)
|
|
36
|
+
walk(each, seen);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const held = node;
|
|
40
|
+
if (held.type === 'Literal') {
|
|
41
|
+
if (typeof held.value === 'string')
|
|
42
|
+
words += 1;
|
|
43
|
+
else
|
|
44
|
+
other = true;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const [key, each] of Object.entries(node)) {
|
|
48
|
+
if (key !== BACK)
|
|
49
|
+
walk(each, seen);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(one.argument, new Set());
|
|
53
|
+
return words > 0 && !other;
|
|
54
|
+
}
|
|
55
|
+
function valued(held) {
|
|
56
|
+
const found = [];
|
|
57
|
+
for (const one of held.properties ?? []) {
|
|
58
|
+
if (one?.type === 'Property')
|
|
59
|
+
found.push(one.value);
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
function tabled(held, seen) {
|
|
64
|
+
const one = bare(held);
|
|
65
|
+
if (one === undefined || one.type !== 'ObjectExpression' || seen.has(one))
|
|
66
|
+
return false;
|
|
67
|
+
seen.add(one);
|
|
68
|
+
const all = valued(one);
|
|
69
|
+
if (all.filter((each) => spoken(each)).length >= LIST)
|
|
70
|
+
return true;
|
|
71
|
+
return all.some((each) => strings(each) || tabled(each, seen));
|
|
72
|
+
}
|
|
73
|
+
function drawn(held) {
|
|
74
|
+
if (held.type !== 'NewExpression' || held.callee?.type !== 'Identifier' || held.callee.name !== 'Set')
|
|
75
|
+
return undefined;
|
|
76
|
+
const only = held.arguments ?? [];
|
|
77
|
+
if (only.length !== 1)
|
|
78
|
+
return undefined;
|
|
79
|
+
const call = bare(only[0]);
|
|
80
|
+
const callee = call?.callee;
|
|
81
|
+
if (call?.type !== 'CallExpression' || callee?.type !== 'MemberExpression')
|
|
82
|
+
return undefined;
|
|
83
|
+
if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object')
|
|
84
|
+
return undefined;
|
|
85
|
+
const asked = callee.property?.name;
|
|
86
|
+
if (asked !== 'keys' && asked !== 'values')
|
|
87
|
+
return undefined;
|
|
88
|
+
const from = bare((call.arguments ?? [])[0]);
|
|
89
|
+
return from?.type === 'Identifier' ? from.name : undefined;
|
|
90
|
+
}
|
|
91
|
+
function adapted(node) {
|
|
92
|
+
if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
|
|
93
|
+
const inner = adapted(node.expression);
|
|
94
|
+
return inner === undefined ? undefined : { type: WIDENED, expression: inner };
|
|
95
|
+
}
|
|
96
|
+
if (ts.isStringLiteral(node))
|
|
97
|
+
return { type: 'Literal', value: node.text };
|
|
98
|
+
if (ts.isSpreadElement(node))
|
|
99
|
+
return { type: SPREAD, argument: adapted(node.expression) ?? null };
|
|
100
|
+
if (ts.isCallExpression(node)) {
|
|
101
|
+
return { type: 'CallExpression', arguments: node.arguments.map((one) => adapted(one)).filter((one) => one !== undefined) };
|
|
102
|
+
}
|
|
103
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
104
|
+
return { type: 'ArrayExpression', elements: node.elements.map((one) => adapted(one) ?? null) };
|
|
105
|
+
}
|
|
106
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
107
|
+
const properties = [];
|
|
108
|
+
for (const one of node.properties) {
|
|
109
|
+
if (!ts.isPropertyAssignment(one))
|
|
110
|
+
continue;
|
|
111
|
+
const value = adapted(one.initializer);
|
|
112
|
+
if (value !== undefined)
|
|
113
|
+
properties.push({ type: 'Property', value });
|
|
114
|
+
}
|
|
115
|
+
return { type: 'ObjectExpression', properties };
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
function parsed(at, text) {
|
|
120
|
+
return ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
121
|
+
}
|
|
122
|
+
function brought(source, name) {
|
|
123
|
+
for (const statement of source.statements) {
|
|
124
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
125
|
+
continue;
|
|
126
|
+
const bindings = statement.importClause?.namedBindings;
|
|
127
|
+
if (bindings === undefined || !ts.isNamedImports(bindings))
|
|
128
|
+
continue;
|
|
129
|
+
if (!bindings.elements.some((one) => one.name.text === name))
|
|
130
|
+
continue;
|
|
131
|
+
return statement.moduleSpecifier.text;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
function elsewhere(at, name) {
|
|
136
|
+
const specifier = brought(parsed(at.filename, at.text), name);
|
|
137
|
+
if (specifier === undefined || !specifier.startsWith('.'))
|
|
138
|
+
return undefined;
|
|
139
|
+
const path = resolve(dirname(at.filename), specifier);
|
|
140
|
+
let text;
|
|
141
|
+
try {
|
|
142
|
+
text = readFileSync(path, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
for (const statement of parsed(path, text).statements) {
|
|
148
|
+
if (!ts.isVariableStatement(statement))
|
|
149
|
+
continue;
|
|
150
|
+
for (const one of statement.declarationList.declarations) {
|
|
151
|
+
if (!ts.isIdentifier(one.name) || one.name.text !== name || one.initializer === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
return adapted(one.initializer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
function initOf(held, name, seen) {
|
|
159
|
+
if (held === null || typeof held !== 'object' || seen.has(held))
|
|
160
|
+
return undefined;
|
|
161
|
+
seen.add(held);
|
|
162
|
+
if (Array.isArray(held)) {
|
|
163
|
+
for (const one of held) {
|
|
164
|
+
const found = initOf(one, name, seen);
|
|
165
|
+
if (found !== undefined)
|
|
166
|
+
return found;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const node = held;
|
|
171
|
+
const init = node.init ?? undefined;
|
|
172
|
+
if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier' && node.id.name === name && init !== undefined)
|
|
173
|
+
return init;
|
|
174
|
+
for (const [key, one] of Object.entries(held)) {
|
|
175
|
+
if (key === BACK)
|
|
176
|
+
continue;
|
|
177
|
+
const found = initOf(one, name, seen);
|
|
178
|
+
if (found !== undefined)
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
function listed(init, program, at, through) {
|
|
184
|
+
const one = bare(init);
|
|
185
|
+
if (one === undefined)
|
|
186
|
+
return false;
|
|
187
|
+
if (strings(one))
|
|
188
|
+
return true;
|
|
189
|
+
if (tabled(one, new Set()))
|
|
190
|
+
return true;
|
|
191
|
+
if (one.type === 'NewExpression' || one.type === 'CallExpression') {
|
|
192
|
+
const only = one.arguments ?? [];
|
|
193
|
+
if (only.length === 1 && strings(only[0]))
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
const from = drawn(one);
|
|
197
|
+
if (from === undefined || through.has(from))
|
|
198
|
+
return false;
|
|
199
|
+
const followed = new Set(through);
|
|
200
|
+
followed.add(from);
|
|
201
|
+
const here = initOf(program, from, new Set());
|
|
202
|
+
return listed(here ?? elsewhere(at, from), program, at, followed);
|
|
203
|
+
}
|
|
204
|
+
const asks = (held, name) => {
|
|
205
|
+
const callee = held?.callee;
|
|
206
|
+
if (held?.type !== 'CallExpression' || callee?.type !== 'MemberExpression')
|
|
207
|
+
return false;
|
|
208
|
+
const on = bare(callee.object);
|
|
209
|
+
if (on?.type !== 'Identifier' || on.name !== name)
|
|
210
|
+
return false;
|
|
211
|
+
const said = callee.property?.name;
|
|
212
|
+
return said === 'has' || said === 'includes';
|
|
213
|
+
};
|
|
214
|
+
const BACK = 'parent';
|
|
215
|
+
function asking(held, name, seen) {
|
|
216
|
+
if (held === null || typeof held !== 'object' || seen.has(held))
|
|
217
|
+
return false;
|
|
218
|
+
seen.add(held);
|
|
219
|
+
if (Array.isArray(held))
|
|
220
|
+
return held.some((one) => asking(one, name, seen));
|
|
221
|
+
if (asks(held, name))
|
|
222
|
+
return true;
|
|
223
|
+
return Object.entries(held).some(([key, one]) => key !== BACK && asking(one, name, seen));
|
|
224
|
+
}
|
|
225
|
+
function consults(node, name) {
|
|
226
|
+
if (node.type === 'MemberExpression') {
|
|
227
|
+
const on = bare(node.object);
|
|
228
|
+
if (on?.type === 'Identifier' && on.name === name)
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
const callee = node.callee;
|
|
232
|
+
if (node.type !== 'CallExpression' || callee?.type !== 'MemberExpression')
|
|
233
|
+
return false;
|
|
234
|
+
if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object')
|
|
235
|
+
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
|
+
function consulted(held, name, seen) {
|
|
241
|
+
if (held === null || typeof held !== 'object' || seen.has(held))
|
|
242
|
+
return false;
|
|
243
|
+
seen.add(held);
|
|
244
|
+
if (Array.isArray(held))
|
|
245
|
+
return held.some((one) => consulted(one, name, seen));
|
|
246
|
+
if (consults(held, name))
|
|
247
|
+
return true;
|
|
248
|
+
return Object.entries(held).some(([key, one]) => key !== BACK && consulted(one, name, seen));
|
|
249
|
+
}
|
|
250
|
+
function continues(held) {
|
|
251
|
+
if (held === null || held === undefined)
|
|
252
|
+
return false;
|
|
253
|
+
if (held.type === 'ContinueStatement')
|
|
254
|
+
return true;
|
|
255
|
+
if (held.type !== 'BlockStatement')
|
|
256
|
+
return false;
|
|
257
|
+
return (held.body ?? []).some((one) => one?.type === 'ContinueStatement');
|
|
258
|
+
}
|
|
259
|
+
const LEAVES = 'ReturnStatement';
|
|
260
|
+
const THROWN = 'ThrowStatement';
|
|
261
|
+
const leaving = (one) => one?.type === LEAVES || one?.type === THROWN;
|
|
262
|
+
function exits(held) {
|
|
263
|
+
if (held === null || held === undefined)
|
|
264
|
+
return false;
|
|
265
|
+
if (leaving(held))
|
|
266
|
+
return true;
|
|
267
|
+
if (held.type !== 'BlockStatement')
|
|
268
|
+
return false;
|
|
269
|
+
return (held.body ?? []).some((one) => leaving(one));
|
|
270
|
+
}
|
|
271
|
+
function look(held, name, found, seen) {
|
|
272
|
+
if (held === null || typeof held !== 'object' || seen.has(held))
|
|
273
|
+
return;
|
|
274
|
+
seen.add(held);
|
|
275
|
+
if (Array.isArray(held)) {
|
|
276
|
+
for (const one of held)
|
|
277
|
+
look(one, name, found, seen);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const node = held;
|
|
281
|
+
if (node.type === 'UnaryExpression' && node.operator === '!' && asks(node.argument ?? undefined, name)) {
|
|
282
|
+
found.skips = true;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (node.type === 'IfStatement' && continues(node.consequent) && asking(node.test, name, new Set())) {
|
|
286
|
+
found.skips = true;
|
|
287
|
+
look(node.consequent, name, found, seen);
|
|
288
|
+
look(node.alternate, name, found, seen);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (node.type === 'IfStatement' && exits(node.consequent) && asking(node.test, name, new Set())) {
|
|
292
|
+
found.gates = true;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (asks(node, name)) {
|
|
296
|
+
found.gates = true;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
for (const [key, one] of Object.entries(node)) {
|
|
300
|
+
if (key !== BACK)
|
|
301
|
+
look(one, name, found, seen);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
export const slopnator = {
|
|
305
|
+
on: ['VariableDeclarator'],
|
|
306
|
+
holds: (node, at) => {
|
|
307
|
+
const held = node;
|
|
308
|
+
const program = at.ancestors().find((one) => one?.type === 'Program');
|
|
309
|
+
if (!listed(held.init, program, at, new Set()))
|
|
310
|
+
return false;
|
|
311
|
+
const named = held.id?.type === 'Identifier' ? held.id.name : undefined;
|
|
312
|
+
const table = bare(held.init)?.type === 'ObjectExpression';
|
|
313
|
+
if (table && (named === undefined || !consulted(program, named, new Set())))
|
|
314
|
+
return false;
|
|
315
|
+
if (named !== undefined) {
|
|
316
|
+
const found = { gates: false, skips: false };
|
|
317
|
+
look(program, named, found, new Set());
|
|
318
|
+
if (!found.gates && found.skips)
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return (`\`${named ?? 'this'}\` is ${table ? 'a table' : 'a list'} of words written out here and this check refuses what is IN it, so ` +
|
|
322
|
+
'whatever breaks the rule under a name nobody typed walks straight past. Wrapping them in `as const`, nesting them under a key, ' +
|
|
323
|
+
'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 ' +
|
|
324
|
+
'has to come from the tree: `thread inspect <word>` names the row that coins any word you need, and a check that reads those ' +
|
|
325
|
+
'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 ' +
|
|
326
|
+
'stricter and it can never go blind.');
|
|
327
|
+
},
|
|
328
|
+
proves: {
|
|
329
|
+
wrong: [
|
|
330
|
+
"const CROSSINGS = ['fetch', 'setTimeout', 'setInterval']",
|
|
331
|
+
"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}",
|
|
332
|
+
"const GLOBALS = new Set(['Date', 'performance', 'Math'])",
|
|
333
|
+
"const MOVES = new Set(['push', 'replace'])\nexport const moved = (one: string): boolean => MOVES.has(one)",
|
|
334
|
+
"const SURFACES = ['cli', 'api', 'mcp'] as const\nexport const surfaced = (one: string): boolean => (SURFACES as readonly string[]).includes(one)",
|
|
335
|
+
"const MOVING = { globals: ['window', 'globalThis'], histories: ['router', 'history'] }\nexport const moves = (one: string): boolean => MOVING.globals.includes(one)",
|
|
336
|
+
"const HANDED_BY = { postgres: 'store', pglite: 'store', https: 'http' }\nexport const handedBy = (reach: string): string => HANDED_BY[reach] ?? reach",
|
|
337
|
+
"const HANDS = { postgres: 'store', pglite: 'store' }\nconst PINS = new Set(Object.keys(HANDS))\nexport const pinned = (one: string): boolean => PINS.has(one)",
|
|
338
|
+
{
|
|
339
|
+
files: [
|
|
340
|
+
{ at: 'threads/held.ts', source: "export const PINNED = { clock: ['Date.now', 'setTimeout'] }\n" },
|
|
341
|
+
{ 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" },
|
|
342
|
+
],
|
|
343
|
+
at: 'threads/slopnators/sl_probe_followed.ts',
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
right: [
|
|
347
|
+
'const CROSSINGS = declared()',
|
|
348
|
+
"const UNDER = 'operations/'",
|
|
349
|
+
"const COMMON = new Set(['a', 'the'])\nexport const words = (all: readonly string[]): readonly string[] => all.filter((one) => !COMMON.has(one))",
|
|
350
|
+
"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}",
|
|
351
|
+
"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}",
|
|
352
|
+
"export const SAYS = { cli: 'runs at a terminal', api: 'answers a request' }",
|
|
353
|
+
{
|
|
354
|
+
files: [
|
|
355
|
+
{ at: 'threads/made.ts', source: "export const MADE = declared()\n" },
|
|
356
|
+
{ 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" },
|
|
357
|
+
],
|
|
358
|
+
at: 'threads/slopnators/sl_probe_derived.ts',
|
|
359
|
+
},
|
|
360
|
+
"export const slopnator = { on: ['CallExpression', 'NewExpression'], holds: () => false, proves: { wrong: ['new Date()'], right: ['const at = 1'] } }",
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
};
|