@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.
Files changed (138) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.js +6 -3
  4. package/dist/core/generators/commit.js +55 -3
  5. package/dist/core/generators/dev.d.ts +1 -0
  6. package/dist/core/generators/dev.js +69 -0
  7. package/dist/derivation/generators/build.d.ts +3 -1
  8. package/dist/derivation/generators/build.js +104 -22
  9. package/dist/derivation/generators/release.js +74 -25
  10. package/dist/derivation/release.js +3 -3
  11. package/dist/integrations/cli.d.ts +0 -1
  12. package/dist/integrations/cli.js +69 -11
  13. package/dist/integrations/declared.d.ts +2 -0
  14. package/dist/integrations/declared.js +6 -1
  15. package/dist/integrations/pglite.js +13 -3
  16. package/dist/integrations/target.d.ts +3 -0
  17. package/dist/integrity/attach.js +1 -5
  18. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  19. package/dist/operations/access.js +3 -2
  20. package/dist/operations/capabilities.d.ts +2 -0
  21. package/dist/operations/capabilities.js +2 -0
  22. package/dist/operations/generators/capability.js +237 -24
  23. package/dist/operations/generators/compile.js +150 -50
  24. package/dist/operations/generators/seed.d.ts +3 -0
  25. package/dist/operations/generators/seed.js +25 -3
  26. package/dist/operations/nodes.d.ts +2 -2
  27. package/package.json +1 -1
  28. package/dist/core/actor.ts +0 -21
  29. package/dist/core/attach.ts +0 -613
  30. package/dist/core/chain.ts +0 -145
  31. package/dist/core/condition.ts +0 -227
  32. package/dist/core/descend.ts +0 -1185
  33. package/dist/core/errors/catalogue.ts +0 -314
  34. package/dist/core/errors/classes.ts +0 -51
  35. package/dist/core/errors/error.ts +0 -152
  36. package/dist/core/errors/namespaces.ts +0 -19
  37. package/dist/core/finding.ts +0 -37
  38. package/dist/core/generators/commit.ts +0 -157
  39. package/dist/core/init.ts +0 -222
  40. package/dist/core/invoked.ts +0 -52
  41. package/dist/core/round.ts +0 -74
  42. package/dist/core/rows.ts +0 -506
  43. package/dist/core/scope.ts +0 -20
  44. package/dist/core/thread.ts +0 -29
  45. package/dist/core/tree.ts +0 -90
  46. package/dist/core/types.ts +0 -351
  47. package/dist/derivation/attach.ts +0 -57
  48. package/dist/derivation/generators/build.ts +0 -371
  49. package/dist/derivation/generators/check.ts +0 -118
  50. package/dist/derivation/generators/determinism.ts +0 -145
  51. package/dist/derivation/generators/release.ts +0 -236
  52. package/dist/derivation/release.ts +0 -43
  53. package/dist/integrations/attach.ts +0 -187
  54. package/dist/integrations/cli.ts +0 -606
  55. package/dist/integrations/declared.ts +0 -34
  56. package/dist/integrations/disk.ts +0 -466
  57. package/dist/integrations/generators/read.ts +0 -127
  58. package/dist/integrations/generators/vault.ts +0 -210
  59. package/dist/integrations/https.ts +0 -216
  60. package/dist/integrations/pglite.ts +0 -206
  61. package/dist/integrations/secret.ts +0 -54
  62. package/dist/integrations/target.ts +0 -672
  63. package/dist/integrations/vault.ts +0 -148
  64. package/dist/integrity/attach.ts +0 -150
  65. package/dist/integrity/check-threads.ts +0 -166
  66. package/dist/integrity/constraint.ts +0 -1246
  67. package/dist/integrity/generators/compiler.ts +0 -138
  68. package/dist/integrity/generators/gate.ts +0 -227
  69. package/dist/integrity/generators/issues.ts +0 -160
  70. package/dist/integrity/generators/publish.ts +0 -233
  71. package/dist/integrity/generators/trial.ts +0 -359
  72. package/dist/integrity/lint.ts +0 -214
  73. package/dist/integrity/lsp/bin.ts +0 -44
  74. package/dist/integrity/lsp/diagnostics.ts +0 -92
  75. package/dist/integrity/lsp/findings.ts +0 -63
  76. package/dist/integrity/lsp/lint.ts +0 -124
  77. package/dist/integrity/lsp/protocol.ts +0 -78
  78. package/dist/integrity/lsp/server.ts +0 -224
  79. package/dist/integrity/slopnator.ts +0 -324
  80. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  81. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  82. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  83. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  84. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  85. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  86. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  87. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  88. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  89. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  90. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  91. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  92. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  93. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  94. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  95. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  96. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  97. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  98. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  99. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  100. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  101. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  102. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  103. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  104. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  105. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  106. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  107. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  108. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  109. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  110. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  111. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  112. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  113. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  114. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  115. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  116. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  117. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  118. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  119. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  120. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  121. package/dist/integrity/sweep.ts +0 -395
  122. package/dist/knowledge/attach.ts +0 -327
  123. package/dist/knowledge/generators/docsgen.ts +0 -186
  124. package/dist/knowledge/generators/glossary.ts +0 -73
  125. package/dist/knowledge/glossary.ts +0 -6
  126. package/dist/operations/access.ts +0 -131
  127. package/dist/operations/attach.ts +0 -408
  128. package/dist/operations/capabilities.ts +0 -88
  129. package/dist/operations/dispatch.ts +0 -294
  130. package/dist/operations/error-results.ts +0 -31
  131. package/dist/operations/generators/capability.ts +0 -1054
  132. package/dist/operations/generators/compile.ts +0 -701
  133. package/dist/operations/generators/permits.ts +0 -229
  134. package/dist/operations/generators/seed.ts +0 -359
  135. package/dist/operations/generators/trial.ts +0 -666
  136. package/dist/operations/nodes.ts +0 -53
  137. package/dist/operations/operations.ts +0 -91
  138. package/dist/operations/result.ts +0 -28
@@ -1,481 +0,0 @@
1
- import { resolve } from 'node:path'
2
- import { fileURLToPath } from 'node:url'
3
-
4
- import ts from 'typescript'
5
-
6
- import { ROOT, THREAD_AT, configured, descend, hosting, sources, under, type Pass } from '../../core/descend.ts'
7
- import { packageOf } from '../../core/attach.ts'
8
- import { PREFIXES } from '../../core/thread.ts'
9
- import { HOLDS, OPENS, type Reach } from '../../integrations/target.ts'
10
-
11
- import type { Slopnator, SlopnatorContext } from '../slopnator.ts'
12
-
13
- const FROM_SOURCE = import.meta.url.endsWith('.ts')
14
-
15
- const SHAPE = 'operations/operations'
16
-
17
- const OPENED_AT = fileURLToPath(new URL('../../' + SHAPE + (FROM_SOURCE ? '.ts' : '.d.ts'), import.meta.url))
18
-
19
- const FRAMEWORK_AT = fileURLToPath(new URL('../../', import.meta.url))
20
-
21
- const FRAMEWORK = 'thread'
22
-
23
- const STATED_AT = resolve(ROOT, FRAMEWORK, SHAPE + '.ts')
24
-
25
- const SHAPES = [...new Set([STATED_AT, OPENED_AT])]
26
-
27
-
28
- const HANDED = 'Opened'
29
-
30
- const EVERY = ''
31
-
32
- const IMPORTED = 'import '
33
-
34
- const MADE = 'new '
35
-
36
- const ANYWHERE = 'globalThis'
37
-
38
- const REQUIRED = 'require'
39
-
40
- const RUNTIME = 'node:'
41
-
42
- const SOURCE = /[.](?:ts|tsx|mts|cts)$/
43
-
44
- const ROOTED = ROOT + '/'
45
-
46
- const here = (filename: string): string => (filename.startsWith(ROOTED) ? filename.slice(ROOTED.length) : filename)
47
-
48
- const plain = (specifier: string): string => (specifier.startsWith(RUNTIME) ? specifier.slice(RUNTIME.length) : specifier)
49
-
50
- interface Door {
51
- readonly hand: string
52
- readonly spelling: string
53
- }
54
-
55
- const HANDS: Readonly<Record<string, string>> = Object.fromEntries(
56
- (Object.keys(HOLDS) as Reach[]).map((one) => [
57
- one,
58
- HOLDS[one].takes === 'entities' ? 'store' : HOLDS[one].takes === 'calls' ? 'target' : one,
59
- ]),
60
- )
61
-
62
- function opening(): Readonly<Record<string, readonly string[]>> {
63
- const found: Record<string, string[]> = {}
64
- const add = (hand: string, spellings: readonly string[]): void => {
65
- found[hand] = [...(found[hand] ?? []), ...spellings]
66
- }
67
- for (const [reach, spellings] of Object.entries(OPENS)) add(HANDS[reach] ?? reach, spellings)
68
- return found
69
- }
70
-
71
- interface Doors {
72
- readonly modules: ReadonlyMap<string, Door>
73
- readonly members: ReadonlyMap<string, ReadonlyMap<string, Door>>
74
- readonly globals: ReadonlyMap<string, Door>
75
- readonly properties: ReadonlyMap<string, Door>
76
- readonly made: ReadonlyMap<string, Door>
77
- }
78
-
79
- function doors(): Doors {
80
- const modules = new Map<string, Door>()
81
- const members = new Map<string, Map<string, Door>>()
82
- const globals = new Map<string, Door>()
83
- const properties = new Map<string, Door>()
84
- const made = new Map<string, Door>()
85
- for (const [hand, spellings] of Object.entries(opening())) {
86
- for (const spelling of spellings) {
87
- const door = { hand, spelling }
88
- if (spelling.startsWith(IMPORTED)) {
89
- const rest = spelling.slice(IMPORTED.length)
90
- const dot = rest.lastIndexOf('.')
91
- if (dot === -1 || rest.slice(dot).includes('/')) {
92
- modules.set(plain(rest), door)
93
- continue
94
- }
95
- const specifier = plain(rest.slice(0, dot))
96
- const held = members.get(specifier) ?? new Map<string, Door>()
97
- held.set(rest.slice(dot + 1), door)
98
- members.set(specifier, held)
99
- continue
100
- }
101
- if (spelling.startsWith(MADE)) {
102
- made.set(spelling.slice(MADE.length), door)
103
- continue
104
- }
105
- if (spelling.includes('.')) properties.set(spelling, door)
106
- else globals.set(spelling, door)
107
- }
108
- }
109
- return { modules, members, globals, properties, made }
110
- }
111
-
112
- const DOORS = doors()
113
-
114
- const INTEGRATION = 'Integration'
115
-
116
- const NEEDS = 'needs'
117
-
118
- function needed(pass: Pass): ReadonlyMap<string, Door> {
119
- const found = new Map<string, Door>()
120
- const rows = [...pass.stated.values(), ...[...pass.threaded.values()].flat().map((one) => one.row)]
121
- for (const row of rows) {
122
- if (row.type !== INTEGRATION) continue
123
- const held = row.fields.get(NEEDS)
124
- if (held === undefined || !ts.isObjectLiteralExpression(held)) continue
125
- for (const one of held.properties) {
126
- if (!ts.isPropertyAssignment(one)) continue
127
- const name = ts.isStringLiteral(one.name) || ts.isIdentifier(one.name) ? one.name.text : undefined
128
- if (name !== undefined) found.set(name, { hand: HANDS['sdk'] ?? 'sdk', spelling: IMPORTED + name })
129
- }
130
- }
131
- return found
132
- }
133
-
134
- const moduleOf = (door: Door): string => door.spelling.slice(IMPORTED.length, door.spelling.lastIndexOf('.'))
135
-
136
- const stands = (door: Door): string => ', which the ' + door.hand + ' this app is handed stands in for'
137
-
138
- interface Built {
139
- readonly of: Pass
140
- readonly program: ts.Program
141
- readonly checker: ts.TypeChecker
142
- readonly hands: ReadonlyMap<ts.Symbol, string>
143
- readonly packages: ReadonlyMap<string, Door>
144
- }
145
-
146
- let built: Built | undefined
147
-
148
- function argumentsOf(type: ts.Type, checker: ts.TypeChecker): readonly ts.Type[] {
149
- const found: ts.Type[] = [...(type.aliasTypeArguments ?? [])]
150
- if ((type.flags & ts.TypeFlags.Object) !== 0 && ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0) {
151
- found.push(...checker.getTypeArguments(type as ts.TypeReference))
152
- }
153
- return found
154
- }
155
-
156
- function handed(type: ts.Type, checker: ts.TypeChecker, hand: string, into: Map<ts.Symbol, string>, seen: Set<ts.Type>, own: (symbol: ts.Symbol) => boolean): void {
157
- if (seen.has(type)) return
158
- seen.add(type)
159
- if (type.isUnionOrIntersection()) {
160
- for (const one of type.types) handed(one, checker, hand, into, seen, own)
161
- return
162
- }
163
- for (const one of argumentsOf(type, checker)) handed(one, checker, hand, into, seen, own)
164
- const index = checker.getIndexTypeOfType(type, ts.IndexKind.String)
165
- if (index !== undefined) handed(index, checker, hand, into, seen, own)
166
- const symbol = type.getSymbol()
167
- if (symbol === undefined || !own(symbol)) return
168
- if ((symbol.flags & (ts.SymbolFlags.Interface | ts.SymbolFlags.TypeLiteral | ts.SymbolFlags.Class)) === 0) return
169
- if (!into.has(symbol)) into.set(symbol, hand)
170
- }
171
- function given(node: ts.TypeNode, checker: ts.TypeChecker, into: Map<ts.Symbol, string>, own: (symbol: ts.Symbol) => boolean): void {
172
- const take = (one: ts.Node): void => {
173
- if (ts.isTypeReferenceNode(one)) {
174
- const named = ts.isIdentifier(one.typeName) ? one.typeName : one.typeName.right
175
- const found = checker.getSymbolAtLocation(named)
176
- const real = found !== undefined && (found.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(found) : found
177
- const shapes = ts.SymbolFlags.Interface | ts.SymbolFlags.Class | ts.SymbolFlags.TypeAlias
178
- if (real !== undefined && own(real) && (real.flags & shapes) !== 0 && !into.has(real)) into.set(real, EVERY)
179
- }
180
- ts.forEachChild(one, take)
181
- }
182
- const inside = (one: ts.Node): void => {
183
- if (ts.isMappedTypeNode(one)) {
184
- if (one.type !== undefined) take(one.type)
185
- return
186
- }
187
- ts.forEachChild(one, inside)
188
- }
189
- inside(node)
190
- }
191
-
192
- function compiled(): Built {
193
- const pass = descend()
194
- if (built !== undefined && built.of === pass) return built
195
- const options = configured().options
196
- const roots = [THREAD_AT, ...sources()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one))
197
- const program = ts.createProgram([...new Set([...SHAPES, ...roots])], options, hosting(options))
198
- const checker = program.getTypeChecker()
199
- const hands = new Map<ts.Symbol, string>()
200
- const own = (symbol: ts.Symbol): boolean =>
201
- (symbol.getDeclarations() ?? []).some((one) => {
202
- const from = one.getSourceFile()
203
- if (from.fileName.startsWith(FRAMEWORK_AT)) return true
204
- return !program.isSourceFileDefaultLibrary(from) && !program.isSourceFileFromExternalLibrary(from)
205
- })
206
- for (const statement of SHAPES.flatMap((one) => program.getSourceFile(one)?.statements ?? [])) {
207
- if (!ts.isTypeAliasDeclaration(statement) || statement.name.text !== HANDED) continue
208
- const alias = checker.getSymbolAtLocation(statement.name)
209
- if (alias !== undefined) hands.set(alias, EVERY)
210
- handed(checker.getTypeFromTypeNode(statement.type), checker, EVERY, hands, new Set(), own)
211
- given(statement.type, checker, hands, own)
212
- }
213
- built = { of: pass, program, checker, hands, packages: needed(pass) }
214
- return built
215
- }
216
-
217
- const INTEGRATIONS_ID = 'integrations'
218
-
219
- function integral(pass: Pass, path: string): boolean {
220
- for (const [at, threadings] of pass.threaded) {
221
- if (at !== path) continue
222
- for (const one of threadings) {
223
- if (one.by === INTEGRATIONS_ID || under(pass, one.by, INTEGRATIONS_ID)) return true
224
- }
225
- }
226
- return false
227
- }
228
-
229
- function operational(pass: Pass, path: string): boolean {
230
- const prefixes = Object.values(PREFIXES)
231
- const owned = (one: string): boolean => prefixes.some((prefix) => one.startsWith(prefix))
232
- if (path.split('/').some((part) => owned(part))) return true
233
- for (const [at, threadings] of pass.threaded) {
234
- if (at !== path) continue
235
- for (const one of threadings) {
236
- if (owned(one.by) || owned(String(one.row.id ?? ''))) return true
237
- }
238
- }
239
- return false
240
- }
241
-
242
- function handOf(type: ts.Type, checker: ts.TypeChecker, hands: ReadonlyMap<ts.Symbol, string>, seen: Set<ts.Type>): string | undefined {
243
- if (seen.has(type)) return undefined
244
- seen.add(type)
245
- const named = type.aliasSymbol === undefined ? undefined : hands.get(type.aliasSymbol)
246
- if (named !== undefined) return named
247
- if (type.isUnionOrIntersection()) {
248
- for (const one of type.types) {
249
- const found = handOf(one, checker, hands, seen)
250
- if (found !== undefined) return found
251
- }
252
- return undefined
253
- }
254
- const symbol = type.getSymbol() ?? type.aliasSymbol
255
- const own = symbol === undefined ? undefined : hands.get(symbol)
256
- if (own !== undefined) return own
257
- for (const one of argumentsOf(type, checker)) {
258
- const found = handOf(one, checker, hands, seen)
259
- if (found !== undefined) return found
260
- }
261
- return undefined
262
- }
263
-
264
- function implemented(file: ts.SourceFile, built: Built): ReadonlySet<string> {
265
- const found = new Set<string>()
266
- const { checker, hands } = built
267
- const held = checker.getSymbolAtLocation(file)
268
- if (held === undefined) return found
269
- const carries = (type: ts.Type, depth: number, seen: Set<ts.Type>): void => {
270
- if (seen.has(type)) return
271
- seen.add(type)
272
- const hand = handOf(type, checker, hands, new Set())
273
- if (hand !== undefined) found.add(hand)
274
- if (depth === 0) return
275
- for (const one of type.isUnionOrIntersection() ? type.types : [type]) {
276
- for (const signature of one.getCallSignatures()) carries(signature.getReturnType(), depth - 1, seen)
277
- for (const property of one.getProperties()) carries(checker.getTypeOfSymbolAtLocation(property, file), depth - 1, seen)
278
- }
279
- }
280
- for (const exported of checker.getExportsOfModule(held)) {
281
- const real = (exported.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(exported) : exported
282
- if ((real.flags & ts.SymbolFlags.Value) === 0) continue
283
- carries(checker.getTypeOfSymbolAtLocation(real, file), 2, new Set())
284
- }
285
- return found
286
- }
287
-
288
- export const DOORWAY = 'door'
289
-
290
- export const HANDED_TO = 'hand'
291
-
292
- interface Noted {
293
- readonly line: number
294
- readonly said: string
295
- readonly kind: string
296
- }
297
-
298
- function judged(path: string, built: Built, operates: boolean, wanted: string): readonly string[] {
299
- const file = built.program.getSourceFile(resolve(ROOT, path))
300
- if (file === undefined) return []
301
- const { checker, hands } = built
302
- let door: boolean | undefined
303
- let allowed: ReadonlySet<string> | undefined
304
- const may = (hand: string): boolean => {
305
- if (door === undefined) door = integral(built.of, path)
306
- if (allowed === undefined) allowed = implemented(file, built)
307
- return allowed.has(EVERY) || allowed.has(hand) || door || (operates && hand === EVERY)
308
- }
309
- const found: Noted[] = []
310
- const line = (node: ts.Node): number => file.getLineAndCharacterOfPosition(node.getStart(file)).line + 1
311
- const note = (node: ts.Node, said: string, kind: string): void => {
312
- found.push({ line: line(node), said: 'line ' + String(line(node)) + ': ' + said, kind })
313
- }
314
- const locals = new Map<string, Door>()
315
- const spaces = new Map<string, ReadonlyMap<string, Door>>()
316
-
317
- const global = (id: ts.Identifier): boolean => {
318
- const symbol = checker.getSymbolAtLocation(id)
319
- if (symbol === undefined) return true
320
- const declared = symbol.getDeclarations() ?? []
321
- return declared.every((one) => one.getSourceFile().isDeclarationFile)
322
- }
323
-
324
- const named = (node: ts.Node): boolean => {
325
- const parent = node.parent as { readonly name?: ts.Node } | undefined
326
- return parent !== undefined && parent.name === node
327
- }
328
-
329
- const inType = (node: ts.Node): boolean => {
330
- let up: ts.Node | undefined = node
331
- while (up !== undefined) {
332
- if (ts.isTypeQueryNode(up)) return true
333
- up = up.parent
334
- }
335
- return false
336
- }
337
-
338
- const reaching = (specifier: string, node: ts.Node): void => {
339
- const door = DOORS.modules.get(plain(specifier)) ?? built.packages.get(packageOf(specifier))
340
- if (door !== undefined && !may(door.hand)) note(node, 'imports ' + specifier + stands(door), DOORWAY)
341
- }
342
-
343
- const visit = (node: ts.Node): void => {
344
- if (ts.isImportDeclaration(node) && node.importClause?.isTypeOnly !== true && ts.isStringLiteral(node.moduleSpecifier)) {
345
- reaching(node.moduleSpecifier.text, node)
346
- const held = DOORS.members.get(plain(node.moduleSpecifier.text))
347
- const bindings = node.importClause?.namedBindings
348
- if (held !== undefined && bindings !== undefined) {
349
- if (ts.isNamespaceImport(bindings)) spaces.set(bindings.name.text, held)
350
- else {
351
- for (const element of bindings.elements) {
352
- const door = held.get((element.propertyName ?? element.name).text)
353
- if (door !== undefined) locals.set(element.name.text, door)
354
- }
355
- }
356
- }
357
- }
358
- if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier)) {
359
- reaching(node.moduleSpecifier.text, node)
360
- }
361
- if (ts.isCallExpression(node)) {
362
- const first = node.arguments[0]
363
- const loads = node.expression.kind === ts.SyntaxKind.ImportKeyword || (ts.isIdentifier(node.expression) && node.expression.text === REQUIRED)
364
- if (loads && first !== undefined && ts.isStringLiteral(first)) reaching(first.text, node)
365
- const callee = node.expression
366
- if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) {
367
- const taken = callee.expression
368
- const hand =
369
- handOf(checker.getTypeAtLocation(taken), checker, hands, new Set()) ??
370
- (ts.isPropertyAccessExpression(taken) || ts.isElementAccessExpression(taken)
371
- ? handOf(checker.getTypeAtLocation(taken.expression), checker, hands, new Set())
372
- : undefined)
373
- if (hand !== undefined && !may(hand)) {
374
- const what = ts.isPropertyAccessExpression(callee) ? callee.name.text : 'an order'
375
- note(node, 'calls ' + what + ' on ' + (hand === EVERY ? 'what this app hands an operation' : 'the ' + hand + ' it is handed'), HANDED_TO)
376
- }
377
- }
378
- }
379
- if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && (node.arguments ?? []).length === 0) {
380
- const door = DOORS.made.get(node.expression.text)
381
- if (door !== undefined && !may(door.hand) && global(node.expression)) note(node, 'reaches ' + door.spelling + '()' + stands(door), DOORWAY)
382
- }
383
- if (ts.isPropertyAccessExpression(node) && !ts.isPartOfTypeNode(node)) {
384
- const inner = ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === ANYWHERE ? node.expression.name : undefined
385
- const object = inner ?? node.expression
386
- if (ts.isIdentifier(object)) {
387
- const spaced = spaces.get(object.text)?.get(node.name.text)
388
- if (spaced !== undefined && !may(spaced.hand)) note(node, 'reaches ' + node.name.text + ' from ' + moduleOf(spaced) + stands(spaced), DOORWAY)
389
- const door = DOORS.properties.get(object.text + '.' + node.name.text)
390
- if (door !== undefined && !may(door.hand) && (inner !== undefined || global(object))) note(node, 'reaches ' + door.spelling + stands(door), DOORWAY)
391
- if (object.text === ANYWHERE) {
392
- const bare = DOORS.globals.get(node.name.text)
393
- if (bare !== undefined && !may(bare.hand)) note(node, 'reaches ' + bare.spelling + stands(bare), DOORWAY)
394
- }
395
- }
396
- }
397
- if (ts.isIdentifier(node) && !named(node) && !ts.isPartOfTypeNode(node) && !inType(node)) {
398
- const local = locals.get(node.text)
399
- if (local !== undefined && !may(local.hand)) note(node, 'reaches ' + node.text + ' from ' + moduleOf(local) + stands(local), DOORWAY)
400
- const door = DOORS.globals.get(node.text)
401
- if (door !== undefined && !may(door.hand) && global(node)) note(node, 'reaches ' + door.spelling + stands(door), DOORWAY)
402
- }
403
- ts.forEachChild(node, visit)
404
- }
405
- visit(file)
406
- return found.filter((one) => one.kind === wanted).sort((one, two) => one.line - two.line).map((one) => one.said)
407
- }
408
-
409
- export function judging(at: SlopnatorContext, wanted: string, closing: string): string | false {
410
- const path = here(at.filename)
411
- if (!SOURCE.test(path)) return false
412
- const held = compiled()
413
- if (held.hands.size === 0 && SHAPES.includes(resolve(ROOT, path))) {
414
- return 'no type called ' + HANDED + ' can be read here, so nothing can tell what an operation was opened with from any other value, and a call on it outside an operation goes unmeasured'
415
- }
416
- const found = judged(path, held, operational(held.of, path), wanted)
417
- if (found.length === 0) return false
418
- return found.join('; ') + '. ' + closing
419
- }
420
-
421
- export const slopnator: Slopnator = {
422
- holds: (_node, at) =>
423
- judging(at, DOORWAY, 'Each of these reaches past this process without going through a declared integration, and a reach this app never declared is one nothing can price, replay or unwind.'),
424
- proves: {
425
- wrong: [
426
- "import { spawnSync } from 'node:child_process'\n\nexport const ran = spawnSync('git', ['status'])",
427
- "import * as processes from 'node:child_process'\n\nexport const ran = processes.spawnSync('git', ['status'])",
428
- {
429
- files: [
430
- { at: 'threads/thread.ts', source: "export const THREAD: Threaded = {\n declares: [\n { id: 'wf_thing', at: 'operations/wf_thing.ts' },\n ],\n}\n" },
431
- { at: 'operations/wf_thing.ts', source: "export const asked = await fetch('https://example.com')" },
432
- ],
433
- at: 'operations/wf_thing.ts',
434
- },
435
- "import { PGlite } from '@electric-sql/pglite'\n\nexport const db = new PGlite()",
436
- {
437
- files: [
438
- { 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}\n' },
439
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport interface Integration extends Threaded {\n readonly needs?: Readonly<Record<string, string>>\n}\n\nexport const THREAD: Threaded = {\n declares: [\n { id: 'wf_pad', at: 'operations/wf_pad.ts' },\n ],\n}\n\nexport const INTEGRATIONS: readonly Integration[] = [\n { id: 'in_pad', describes: 'Pads text.', needs: { 'left-pad': '^1.3.0' } },\n]\n" },
440
- { at: 'operations/wf_pad.ts', source: "import pad from 'left-pad'\n\nexport const padded = pad('a', 3)\n" },
441
- ],
442
- at: 'operations/wf_pad.ts',
443
- },
444
- ],
445
- right: [
446
- 'export const began = Date.now()',
447
- 'export const later = setTimeout(() => again(), 5000)',
448
- "import { randomBytes } from 'node:crypto'\n\nexport const nonce = randomBytes(6).toString('hex')",
449
- "import { join } from 'node:path'\n\nexport const at = join('a', 'b')",
450
- "import { createHash } from 'node:crypto'\n\nexport const sum = createHash('sha256').update('a').digest('hex')",
451
- 'export const when = new Date(1767225600000)',
452
- {
453
- files: [
454
- { at: 'thread/operations/operations.ts', source: 'export interface Http {\n call(address: string): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Http }\n' },
455
- { at: 'threads/_probe_door.ts', source: "import type { Http } from '../thread/operations/operations.ts'\n\nexport const made: Http = {\n call: async (address) => {\n await fetch(address)\n },\n}\n" },
456
- ],
457
- at: 'threads/_probe_door.ts',
458
- },
459
- {
460
- files: [
461
- { at: 'thread/operations/operations.ts', source: 'export interface Store {\n insert(row: unknown): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Store }\n' },
462
- { at: 'threads/_probe_handed.ts', source: "import type { Opened } from '../thread/operations/operations.ts'\n\nexport const carried = (opened: Opened<'pg_main'>, run: (given: Opened<'pg_main'>) => Promise<void>): Promise<void> => run(opened)\n" },
463
- ],
464
- at: 'threads/_probe_handed.ts',
465
- },
466
- {
467
- files: [
468
- { 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}\n' },
469
- { at: 'thread/operations/operations.ts', source: 'export type Calling = { readonly [call: string]: (given?: unknown) => Promise<unknown> }\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Calling }\n' },
470
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport interface Integration extends Threaded {\n readonly needs?: Readonly<Record<string, string>>\n}\n\nexport const THREAD: Threaded = {\n declares: [\n { id: 'sdk_pad_calls', at: 'src/sdk/sdk_pad.ts' },\n ],\n}\n\nexport const INTEGRATIONS: readonly Integration[] = [\n { id: 'in_pad', describes: 'Pads text.', needs: { 'left-pad': '^1.3.0' } },\n]\n" },
471
- { at: 'src/sdk/sdk_pad.ts', source: "import type { Calling } from '../../thread/operations/operations.ts'\nimport pad from 'left-pad'\n\nexport const calls: Calling = {\n padded: async (given) => pad(String(given), 3),\n}\n" },
472
- ],
473
- at: 'src/sdk/sdk_pad.ts',
474
- },
475
- ],
476
- },
477
- }
478
-
479
-
480
-
481
-
@@ -1,113 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- import { descend, read, reached } from '../../core/descend.ts'
4
- import { rowsIn } from './sl_unthreaded.ts'
5
-
6
- interface Held {
7
- readonly type?: string
8
- readonly name?: unknown
9
- readonly value?: unknown
10
- readonly computed?: boolean
11
- readonly object?: Held
12
- readonly property?: Held
13
- readonly key?: Held
14
- }
15
-
16
- const HREF = 'href'
17
-
18
- const named = (held: Held | undefined): string | undefined => {
19
- if (held?.type === 'Identifier' || held?.type === 'JSXIdentifier') return typeof held.name === 'string' ? held.name : undefined
20
- if (held?.type === 'Literal' && typeof held.value === 'string') return held.value
21
- return undefined
22
- }
23
-
24
- const spoken = (held: Held): string | undefined => {
25
- if (held.type === 'JSXAttribute') return named(held.name as Held | undefined)
26
- if (held.type === 'Property') return held.computed === true ? undefined : named(held.key)
27
- return undefined
28
- }
29
-
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
66
- }
67
-
68
- export const slopnator: Slopnator = {
69
- on: ['JSXAttribute', 'Property', 'Literal'],
70
- holds: (node) => {
71
- const held = node as Held
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
-
85
- return (
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 + '"'
88
- )
89
- },
90
- proves: {
91
- wrong: [
92
- "const link = { href: '/orders/42' }",
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
- },
103
- ],
104
- right: [
105
- 'const path = router.pathname',
106
- '<Link.linkInline navigateTo="Home" labelKey="home.title" />',
107
- "const to = { navigateTo: 'Order', labelKey: 'order.title' }",
108
- 'const rows = orders.push(one)',
109
- "const parts = one.split('/')",
110
- "const at = '/api/webhook'",
111
- ],
112
- },
113
- }
@@ -1,44 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- interface Tag {
4
- readonly type?: string
5
- readonly name?: unknown
6
- readonly namespace?: { readonly name?: unknown }
7
- }
8
-
9
- const HOST = /^[a-z]/
10
-
11
- function tagOf(node: unknown): string | undefined {
12
- const name = (node as { name?: Tag } | undefined)?.name
13
- if (name?.type === 'JSXNamespacedName') {
14
- const space = typeof name.namespace?.name === 'string' ? name.namespace.name : ''
15
- const local = typeof (name.name as Tag | undefined)?.name === 'string' ? String((name.name as Tag).name) : ''
16
- return `${space}:${local}`
17
- }
18
- if (name?.type !== 'JSXIdentifier') return undefined
19
- return typeof name.name === 'string' ? name.name : undefined
20
- }
21
-
22
- export const slopnator: Slopnator = {
23
- on: ['JSXOpeningElement'],
24
- holds: (node) => {
25
- const tag = tagOf(node)
26
- if (tag === undefined || !HOST.test(tag)) return false
27
- return (
28
- `<${tag}> is a host element, so it wears no declared style, sits in no catalogue, and the style guide, ` +
29
- 'a rebrand and every overlay read past it — it renders and nothing downstream knows it is there'
30
- )
31
- },
32
- proves: {
33
- wrong: [
34
- '<div className="row" />',
35
- '<button onClick={save}>save</button>',
36
- '<svg:path d="M0 0" />',
37
- ],
38
- right: [
39
- '<Box.card />',
40
- '<Column>{rows}</Column>',
41
- '<Button.buttonPrimary label="save" />',
42
- ],
43
- },
44
- }