dsh-ptc-plus 0.4.1 → 0.4.2

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 (53) hide show
  1. package/README.en.md +6 -10
  2. package/README.md +6 -10
  3. package/client.js +262 -31
  4. package/compiler-core.cjs +132 -130
  5. package/compiler-core.cjs.map +3 -3
  6. package/docs/README.md +1 -0
  7. package/docs/adr/0023-global-user-bindings.md +6 -6
  8. package/docs/adr/0025-use-versioned-logical-binding-identities.md +3 -3
  9. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +5 -5
  10. package/docs/architecture.md +9 -5
  11. package/docs/client-ui.md +1 -1
  12. package/docs/durability-design.md +6 -3
  13. package/docs/installation.md +4 -4
  14. package/docs/runtime-reference.md +5 -1
  15. package/docs/stateful-binding-updates-proposal.md +8 -4
  16. package/docs/user-bindings.en.md +5 -3
  17. package/docs/user-bindings.md +4 -2
  18. package/docs/verification-optimization-work.md +41 -0
  19. package/docs/verification.md +15 -1
  20. package/internal/async-local-storage-intrinsics.js +38 -0
  21. package/internal/binding-declaration-contract.js +8 -0
  22. package/internal/binding-source-analysis.js +381 -64
  23. package/internal/binding-update-policy.js +7 -1
  24. package/internal/cell-analysis.js +21 -8
  25. package/internal/compiler-intrinsics.js +9 -43
  26. package/internal/compiler-module-links.js +7 -4
  27. package/internal/dynamic-environment-integration.js +8 -4
  28. package/internal/dynamic-environment-runtime.js +24 -3
  29. package/internal/dynamic-native-calls.js +4 -1
  30. package/internal/dynamic-native-runtime.js +15 -1
  31. package/internal/failure-reporting.js +38 -30
  32. package/internal/isolated-worker.js +17 -10
  33. package/internal/kernel-worker.js +493 -331
  34. package/internal/module-compilation.js +5 -4
  35. package/internal/module-transform-contract.js +32 -2
  36. package/internal/record-utils.js +24 -15
  37. package/internal/repl-value-observer.js +6 -3
  38. package/internal/runtime-intrinsics.js +101 -0
  39. package/internal/session-cell-executor.js +2 -1
  40. package/internal/session-journal-schema.js +30 -13
  41. package/internal/session-journal.js +19 -4
  42. package/internal/session-runtime.js +1 -0
  43. package/internal/source-position-map.js +19 -0
  44. package/internal/stateful-root-compiler.js +3 -1
  45. package/internal/typescript-transform.js +4 -2
  46. package/internal/user-binding-console-worker.js +101 -41
  47. package/internal/user-bindings.js +10 -2
  48. package/internal/value-wire.js +101 -71
  49. package/internal/worker-cwd-virtualization.js +80 -53
  50. package/internal/worker-environment.js +20 -0
  51. package/internal/worker-repl-realm.js +134 -0
  52. package/package.json +2 -1
  53. package/src/client-activity.js +8 -1
@@ -2,13 +2,14 @@ import { parse } from '@babel/parser'
2
2
  import { AMBIENT_GLOBALS, renderDurabilityReasons } from './module-policy.js'
3
3
  import { CELL_PARSER_PLUGINS, normalizeTypeScriptValues } from './repl-scope-normalizer.js'
4
4
  import { identitySourceMap } from './source-position-map.js'
5
- import { LEGACY_USER_BINDING_TRANSFORM, transformTypeScriptSource } from './typescript-transform.js'
5
+ import { LEGACY_USER_BINDING_TRANSFORM, USER_BINDING_TRANSFORM, transformTypeScriptSource } from './typescript-transform.js'
6
6
  import { compileStatefulModule } from './module-compilation.js'
7
7
  import { prepareProgram, classifyDurability } from './cell-analysis.js'
8
8
  import { createCompilerSourceCache } from './compiler-source-cache.js'
9
+ import { bindingTypeNamespaceToken } from './binding-declaration-contract.js'
9
10
 
10
11
  const MAX_NAME_LENGTH = 128
11
- const GLOBAL_TYPE_NAMES = new Set([
12
+ const LEGACY_GLOBAL_TYPE_NAMES = new Set([
12
13
  'Array', 'ArrayBuffer', 'ArrayBufferView', 'AsyncIterable', 'AsyncIterableIterator',
13
14
  'AsyncIterator', 'Awaited', 'BigInt', 'BigInt64Array', 'BigUint64Array', 'Boolean',
14
15
  'Buffer', 'ConstructorParameters', 'DataView', 'Date', 'Error', 'EvalError', 'Exclude',
@@ -92,7 +93,13 @@ function typeReferenceRoot(node) {
92
93
  return current?.type === 'Identifier' ? current.name : undefined
93
94
  }
94
95
 
95
- function projectedType(source, node, outerTypeNames = new Set(), sourceTypeNames = new Set()) {
96
+ function typeReferenceRootNode(node) {
97
+ let current = node
98
+ while (current?.type === 'TSQualifiedName') current = current.left
99
+ return current?.type === 'Identifier' ? current : undefined
100
+ }
101
+
102
+ function projectedType(source, node, outerTypeNames, typeContext, usedTypeNames) {
96
103
  const replacements = []
97
104
  const commentRanges = new Set()
98
105
  const visit = (current, inheritedNames) => {
@@ -115,21 +122,53 @@ function projectedType(source, node, outerTypeNames = new Set(), sourceTypeNames
115
122
  if (current.type === 'TSMappedType' && typeof current.typeParameter?.name === 'string') {
116
123
  typeNames = new Set([...typeNames, current.typeParameter.name])
117
124
  }
118
- if (current.type === 'TSImportType' || current.type === 'TSTypeQuery') {
125
+ if (!typeContext.modern && (current.type === 'TSImportType' || current.type === 'TSTypeQuery')) {
119
126
  replacements.push({ start: current.start, end: current.end, text: 'unknown' })
120
127
  return
121
128
  }
122
- if (current.type === 'TSTypeReference' || current.type === 'TSExpressionWithTypeArguments') {
123
- const name = typeReferenceRoot(current.typeName ?? current.expression)
124
- if (name === undefined || (!typeNames.has(name)
125
- && (sourceTypeNames.has(name) || !GLOBAL_TYPE_NAMES.has(name)))) {
129
+ if (current.type === 'TSTypeQuery') {
130
+ const reference = current.exprName
131
+ const name = typeReferenceRoot(reference)
132
+ if (typeContext.modern && name !== undefined && !typeNames.has(name)) {
133
+ if (typeContext.importedNames.has(name)) {
134
+ throw new TypeError(`imported value ${JSON.stringify(name)} cannot be represented in the binding interface; use an explicit import(...) type query`)
135
+ }
136
+ if (typeContext.localValues.has(name)) {
137
+ const declaration = typeContext.localDeclarations.get(name)
138
+ if (declaration === undefined
139
+ || !['ClassDeclaration', 'TSEnumDeclaration'].includes(declaration.type)
140
+ || (reference.type !== 'Identifier' && declaration.type !== 'TSEnumDeclaration')) {
141
+ throw new TypeError(`local value query ${JSON.stringify(name)} cannot be represented in the binding interface; use an explicit structural type`)
142
+ }
143
+ usedTypeNames.add(name)
144
+ const root = typeReferenceRootNode(reference)
145
+ replacements.push({ start: root.start, end: root.end,
146
+ text: `${typeContext.typeNamespaceToken}.${name}` })
147
+ }
148
+ }
149
+ } else if (current.type === 'TSTypeReference' || current.type === 'TSExpressionWithTypeArguments') {
150
+ const reference = current.typeName ?? current.expression
151
+ const name = typeReferenceRoot(reference)
152
+ if (!typeContext.modern && (name === undefined || !typeNames.has(name)
153
+ && (typeContext.sourceTypeNames.has(name) || !LEGACY_GLOBAL_TYPE_NAMES.has(name)))) {
126
154
  replacements.push({ start: current.start, end: current.end, text: 'unknown' })
127
155
  return
128
156
  }
157
+ if (typeContext.modern && name !== undefined && !typeNames.has(name)) {
158
+ if (typeContext.importedNames.has(name)) {
159
+ throw new TypeError(`imported type ${JSON.stringify(name)} cannot be represented in the binding interface; use an explicit import(...) type or a local structural type`)
160
+ }
161
+ if (typeContext.localDeclarations.has(name)) {
162
+ usedTypeNames.add(name)
163
+ const root = typeReferenceRootNode(reference)
164
+ replacements.push({ start: root.start, end: root.end,
165
+ text: `${typeContext.typeNamespaceToken}.${name}` })
166
+ }
167
+ }
129
168
  }
130
169
  for (const [key, value] of Object.entries(current)) {
131
170
  if (key === 'loc' || key === 'start' || key === 'end'
132
- || key === 'typeParameters' || key === 'typeName' || key === 'expression') continue
171
+ || key === 'typeParameters' || key === 'typeName' || key === 'expression' || key === 'exprName') continue
133
172
  if (Array.isArray(value)) value.forEach(item => visit(item, typeNames))
134
173
  else visit(value, typeNames)
135
174
  }
@@ -146,50 +185,50 @@ function projectedType(source, node, outerTypeNames = new Set(), sourceTypeNames
146
185
  return text
147
186
  }
148
187
 
149
- function typeParameters(source, node, outerTypeNames = new Set(), sourceTypeNames = new Set()) {
188
+ function typeParameters(source, node, outerTypeNames, typeContext, usedTypeNames) {
150
189
  const declaration = node?.typeParameters
151
190
  if (declaration?.type !== 'TSTypeParameterDeclaration') {
152
191
  return { text: '', names: outerTypeNames }
153
192
  }
154
193
  const names = new Set([...outerTypeNames, ...typeParameterNames(declaration)])
155
- return { text: projectedType(source, declaration, names, sourceTypeNames), names }
194
+ return { text: projectedType(source, declaration, names, typeContext, usedTypeNames), names }
156
195
  }
157
196
 
158
- function annotation(source, node, typeNames = new Set(), sourceTypeNames = new Set()) {
197
+ function annotation(source, node, typeNames, typeContext, usedTypeNames) {
159
198
  const type = node?.typeAnnotation?.typeAnnotation
160
- return type === undefined ? 'unknown' : projectedType(source, type, typeNames, sourceTypeNames)
199
+ return type === undefined ? 'unknown' : projectedType(source, type, typeNames, typeContext, usedTypeNames)
161
200
  }
162
201
 
163
- function parameter(source, node, index, typeNames = new Set(), sourceTypeNames = new Set()) {
202
+ function parameter(source, node, index, typeNames, typeContext, usedTypeNames) {
164
203
  if (node?.type === 'TSParameterProperty') {
165
- return parameter(source, node.parameter, index, typeNames, sourceTypeNames)
204
+ return parameter(source, node.parameter, index, typeNames, typeContext, usedTypeNames)
166
205
  }
167
206
  if (node?.type === 'RestElement') {
168
207
  const argument = node.argument
169
208
  return argument?.type === 'Identifier'
170
- ? `...${argument.name}: ${node.typeAnnotation === undefined ? 'unknown[]' : annotation(source, node, typeNames, sourceTypeNames)}`
209
+ ? `...${argument.name}: ${node.typeAnnotation === undefined ? 'unknown[]' : annotation(source, node, typeNames, typeContext, usedTypeNames)}`
171
210
  : `...args${index}: unknown[]`
172
211
  }
173
212
  const optional = node?.type === 'AssignmentPattern'
174
213
  const target = optional ? node.left : node
175
214
  if (target?.type !== 'Identifier') return `arg${index}: unknown`
176
- return `${target.name}${optional || target.optional === true ? '?' : ''}: ${annotation(source, target, typeNames, sourceTypeNames)}`
215
+ return `${target.name}${optional || target.optional === true ? '?' : ''}: ${annotation(source, target, typeNames, typeContext, usedTypeNames)}`
177
216
  }
178
217
 
179
- function returnType(source, node, typeNames = new Set(), sourceTypeNames = new Set()) {
218
+ function returnType(source, node, typeNames, typeContext, usedTypeNames) {
180
219
  const explicit = node?.returnType?.typeAnnotation
181
- if (explicit !== undefined) return projectedType(source, explicit, typeNames, sourceTypeNames)
220
+ if (explicit !== undefined) return projectedType(source, explicit, typeNames, typeContext, usedTypeNames)
182
221
  return node?.async === true ? 'Promise<unknown>' : 'unknown'
183
222
  }
184
223
 
185
- function functionType(source, node, outerTypeNames = new Set(), sourceTypeNames = new Set()) {
186
- const generics = typeParameters(source, node, outerTypeNames, sourceTypeNames)
224
+ function functionType(source, node, outerTypeNames, typeContext, usedTypeNames) {
225
+ const generics = typeParameters(source, node, outerTypeNames, typeContext, usedTypeNames)
187
226
  const parameters = (node.params ?? [])
188
- .map((item, index) => parameter(source, item, index, generics.names, sourceTypeNames)).join(', ')
189
- return `${generics.text}(${parameters}) => ${returnType(source, node, generics.names, sourceTypeNames)}`
227
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
228
+ return `${generics.text}(${parameters}) => ${returnType(source, node, generics.names, typeContext, usedTypeNames)}`
190
229
  }
191
230
 
192
- function inferredType(source, node, depth = 0, sourceTypeNames = new Set()) {
231
+ function inferredType(source, node, depth, typeContext, usedTypeNames) {
193
232
  if (depth > 2 || node === null || node === undefined) return 'unknown'
194
233
  if (node.type === 'StringLiteral' || node.type === 'TemplateLiteral') return 'string'
195
234
  if (node.type === 'NumericLiteral') return 'number'
@@ -197,7 +236,7 @@ function inferredType(source, node, depth = 0, sourceTypeNames = new Set()) {
197
236
  if (node.type === 'BigIntLiteral') return 'bigint'
198
237
  if (node.type === 'NullLiteral') return 'null'
199
238
  if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
200
- return functionType(source, node, new Set(), sourceTypeNames)
239
+ return functionType(source, node, new Set(), typeContext, usedTypeNames)
201
240
  }
202
241
  if (node.type === 'ArrayExpression') return 'unknown[]'
203
242
  if (node.type === 'ObjectExpression') {
@@ -208,34 +247,258 @@ function inferredType(source, node, depth = 0, sourceTypeNames = new Set()) {
208
247
  ? property.key.name
209
248
  : property.key.type === 'StringLiteral' ? JSON.stringify(property.key.value) : undefined
210
249
  if (name === undefined) return 'Record<string, unknown>'
211
- members.push(`${name}: ${inferredType(source, property.value, depth + 1, sourceTypeNames)}`)
250
+ members.push(`${name}: ${inferredType(source, property.value, depth + 1, typeContext, usedTypeNames)}`)
212
251
  }
213
252
  return `{ ${members.join('; ')} }`
214
253
  }
215
254
  return 'unknown'
216
255
  }
217
256
 
218
- function classMembers(source, node, classTypeNames, sourceTypeNames) {
257
+ function classMemberName(member) {
258
+ return member.key?.type === 'Identifier'
259
+ ? member.key.name
260
+ : member.key?.type === 'StringLiteral' ? JSON.stringify(member.key.value) : undefined
261
+ }
262
+
263
+ function modernClassMemberName(member) {
264
+ const key = classMemberKey(member)
265
+ return classMemberName(member) ?? (key === undefined ? undefined : JSON.stringify(key))
266
+ }
267
+
268
+ function classMemberKey(member) {
269
+ if (member.computed && !['StringLiteral', 'NumericLiteral', 'BigIntLiteral'].includes(member.key?.type)) {
270
+ throw new TypeError('computed class member key cannot be represented in the binding interface; use a literal property key')
271
+ }
272
+ return member.key?.type === 'Identifier'
273
+ ? member.key.name
274
+ : member.key?.type === 'StringLiteral' ? member.key.value
275
+ : member.key?.type === 'NumericLiteral' ? String(member.key.value)
276
+ : member.key?.type === 'BigIntLiteral' ? String(BigInt(member.key.value)) : undefined
277
+ }
278
+
279
+ function isPublicClassMember(member) {
280
+ return !member.static
281
+ && member.accessibility !== 'private' && member.accessibility !== 'protected'
282
+ && member.key?.type !== 'PrivateName'
283
+ }
284
+
285
+ function parameterPropertyMember(source, node, classTypeNames, typeContext, usedTypeNames) {
286
+ if (node?.type !== 'TSParameterProperty' || node.accessibility === 'private' || node.accessibility === 'protected') return undefined
287
+ const parameterNode = node.parameter
288
+ const optional = parameterNode?.type === 'AssignmentPattern'
289
+ const target = optional ? parameterNode.left : parameterNode
290
+ if (target?.type !== 'Identifier') return undefined
291
+ return `${node.readonly === true ? 'readonly ' : ''}${target.name}${optional || target.optional === true ? '?' : ''}: ${annotation(source, target, classTypeNames, typeContext, usedTypeNames)}`
292
+ }
293
+
294
+ function parameterPropertyName(node) {
295
+ if (node?.type !== 'TSParameterProperty' || node.accessibility === 'private' || node.accessibility === 'protected') return undefined
296
+ const parameter = node.parameter?.type === 'AssignmentPattern' ? node.parameter.left : node.parameter
297
+ return parameter?.type === 'Identifier' ? parameter.name : undefined
298
+ }
299
+
300
+ function legacyClassMembers(source, node, classTypeNames, typeContext, usedTypeNames) {
219
301
  const members = []
302
+ const memberNames = new Set()
220
303
  let constructorParameters = '...args: unknown[]'
221
304
  for (const member of node.body?.body ?? []) {
222
- if (member.type !== 'ClassMethod' || member.static || member.computed
223
- || member.accessibility === 'private' || member.key?.type === 'PrivateName') continue
224
- const name = member.key?.type === 'Identifier'
225
- ? member.key.name
226
- : member.key?.type === 'StringLiteral' ? JSON.stringify(member.key.value) : undefined
305
+ if (!['ClassMethod', 'TSDeclareMethod'].includes(member.type)
306
+ || member.static || member.computed || member.accessibility === 'private' || member.key?.type === 'PrivateName') continue
307
+ const name = classMemberName(member)
227
308
  if (name === undefined) continue
228
- const generics = typeParameters(source, member, classTypeNames, sourceTypeNames)
309
+ const generics = typeParameters(source, member, classTypeNames, typeContext, usedTypeNames)
229
310
  const parameters = (member.params ?? [])
230
- .map((item, index) => parameter(source, item, index, generics.names, sourceTypeNames)).join(', ')
231
- if (member.kind === 'constructor') constructorParameters = parameters
311
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
312
+ if (member.kind === 'constructor') {
313
+ constructorParameters = parameters
314
+ }
315
+ else if (member.kind === 'get' || member.kind === 'set') {
316
+ const signature = `${member.kind}:${name}`
317
+ if (memberNames.has(signature)) continue
318
+ members.push(member.kind === 'get'
319
+ ? `get ${name}(): ${returnType(source, member, generics.names, typeContext, usedTypeNames)}`
320
+ : `set ${name}(${parameters})`)
321
+ memberNames.add(signature)
322
+ }
232
323
  else if (member.kind === 'method') {
233
- members.push(`${name}${generics.text}(${parameters}): ${returnType(source, member, generics.names, sourceTypeNames)}`)
324
+ members.push(`${name}${member.optional === true ? '?' : ''}${generics.text}(${parameters}): ${returnType(source, member, generics.names, typeContext, usedTypeNames)}`)
325
+ memberNames.add(name)
326
+ }
327
+ }
328
+ return { constructorParameters, members, instance: `{ ${members.join('; ')} }` }
329
+ }
330
+
331
+ function classMembers(source, node, classTypeNames, typeContext, usedTypeNames) {
332
+ if (!typeContext.modern) return legacyClassMembers(source, node, classTypeNames, typeContext, usedTypeNames)
333
+ const body = node.body?.body ?? []
334
+ const fields = new Map()
335
+ const parameterProperties = new Map()
336
+ const prototypeMembers = new Map()
337
+ let constructorNode
338
+ for (const member of body) {
339
+ if (['ClassProperty', 'TSAbstractPropertyDefinition'].includes(member.type)) {
340
+ if (!isPublicClassMember(member)) continue
341
+ const key = classMemberKey(member)
342
+ if (key !== undefined) fields.set(key, member)
343
+ continue
344
+ }
345
+ if (member.type === 'ClassAccessorProperty') {
346
+ if (!isPublicClassMember(member)) continue
347
+ const key = classMemberKey(member)
348
+ if (key !== undefined) prototypeMembers.set(key, { kind: 'accessor', get: member, set: member })
349
+ continue
350
+ }
351
+ if (!['ClassMethod', 'TSDeclareMethod'].includes(member.type) || !isPublicClassMember(member)) continue
352
+ const key = classMemberKey(member)
353
+ if (key === undefined) continue
354
+ if (member.kind === 'constructor') {
355
+ constructorNode = member
356
+ } else if (member.kind === 'get' || member.kind === 'set') {
357
+ const current = prototypeMembers.get(key)
358
+ const descriptor = current?.kind === 'accessor' ? { ...current } : { kind: 'accessor' }
359
+ descriptor[member.kind] = member
360
+ prototypeMembers.set(key, descriptor)
361
+ } else if (member.kind === 'method') {
362
+ prototypeMembers.set(key, { kind: 'method', node: member })
363
+ }
364
+ }
365
+ for (const parameter of constructorNode?.params ?? []) {
366
+ const name = parameterPropertyName(parameter)
367
+ if (name !== undefined) parameterProperties.set(name, parameter)
368
+ }
369
+ const ownMember = name => parameterProperties.get(name) ?? fields.get(name)
370
+ const members = []
371
+ let constructorParameters = '...args: unknown[]'
372
+ for (const member of body) {
373
+ if (['ClassProperty', 'TSAbstractPropertyDefinition'].includes(member.type)) {
374
+ if (!isPublicClassMember(member)) continue
375
+ const key = classMemberKey(member)
376
+ if (key === undefined || ownMember(key) !== member) continue
377
+ const name = modernClassMemberName(member)
378
+ members.push(`${member.readonly === true ? 'readonly ' : ''}${name}${member.optional === true ? '?' : ''}: ${annotation(source, member, classTypeNames, typeContext, usedTypeNames)}`)
379
+ continue
380
+ }
381
+ if (member.type === 'ClassAccessorProperty') {
382
+ if (!isPublicClassMember(member)) continue
383
+ const key = classMemberKey(member)
384
+ const name = modernClassMemberName(member)
385
+ const selected = key === undefined || ownMember(key) !== undefined ? undefined : prototypeMembers.get(key)
386
+ if (selected?.kind !== 'accessor') continue
387
+ if (selected.get === member && selected.set === member) {
388
+ members.push(`${name}${member.optional === true ? '?' : ''}: ${annotation(source, member, classTypeNames, typeContext, usedTypeNames)}`)
389
+ } else {
390
+ const type = annotation(source, member, classTypeNames, typeContext, usedTypeNames)
391
+ if (selected.get === member) members.push(`get ${name}(): ${type}`)
392
+ if (selected.set === member) members.push(`set ${name}(value: ${type})`)
393
+ }
394
+ continue
395
+ }
396
+ if (!['ClassMethod', 'TSDeclareMethod'].includes(member.type) || !isPublicClassMember(member)) continue
397
+ const key = classMemberKey(member)
398
+ if (key === undefined) continue
399
+ const name = modernClassMemberName(member)
400
+ if (member.kind === 'constructor') {
401
+ if (member !== constructorNode) continue
402
+ const generics = typeParameters(source, member, classTypeNames, typeContext, usedTypeNames)
403
+ const parameters = (member.params ?? [])
404
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
405
+ constructorParameters = parameters
406
+ for (const item of member.params ?? []) {
407
+ const fieldName = parameterPropertyName(item)
408
+ if (fieldName === undefined || ownMember(fieldName) !== item) continue
409
+ members.push(parameterPropertyMember(source, item, classTypeNames, typeContext, usedTypeNames))
410
+ }
411
+ continue
412
+ }
413
+ if (ownMember(key) !== undefined) continue
414
+ const selected = prototypeMembers.get(key)
415
+ if (member.kind === 'get' || member.kind === 'set') {
416
+ if (selected?.kind !== 'accessor' || selected[member.kind] !== member) continue
417
+ const generics = typeParameters(source, member, classTypeNames, typeContext, usedTypeNames)
418
+ const parameters = (member.params ?? [])
419
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
420
+ members.push(member.kind === 'get'
421
+ ? `get ${name}(): ${returnType(source, member, generics.names, typeContext, usedTypeNames)}`
422
+ : `set ${name}(${parameters})`)
423
+ } else if (member.kind === 'method' && selected?.kind === 'method' && selected.node === member) {
424
+ const generics = typeParameters(source, member, classTypeNames, typeContext, usedTypeNames)
425
+ const parameters = (member.params ?? [])
426
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
427
+ members.push(`${name}${member.optional === true ? '?' : ''}${generics.text}(${parameters}): ${returnType(source, member, generics.names, typeContext, usedTypeNames)}`)
234
428
  }
235
429
  }
236
430
  return { constructorParameters, members, instance: `{ ${members.join('; ')} }` }
237
431
  }
238
432
 
433
+ function valueReferenceRootNode(node) {
434
+ let current = node
435
+ while (current?.type === 'MemberExpression' && !current.computed) current = current.object
436
+ return current?.type === 'Identifier' ? current : undefined
437
+ }
438
+
439
+ function classHeritage(source, node, classTypeNames, typeContext, usedTypeNames) {
440
+ const base = node.superClass
441
+ if (base === null || base === undefined) return { clause: '', instance: '' }
442
+ const root = valueReferenceRootNode(base)
443
+ if (root === undefined || !['Identifier', 'MemberExpression'].includes(base.type)) {
444
+ throw new TypeError('class heritage cannot be represented in the binding interface; use a named class base')
445
+ }
446
+ if (typeContext.importedNames.has(root.name)) {
447
+ throw new TypeError(`imported class ${JSON.stringify(root.name)} cannot be represented in the binding interface; use an explicit structural type`)
448
+ }
449
+ let reference = sourceSlice(source, base)
450
+ if (base.type === 'Identifier' && typeContext.localDeclarations.has(root.name)) {
451
+ const declaration = typeContext.localDeclarations.get(root.name)
452
+ if (declaration.type !== 'ClassDeclaration') {
453
+ throw new TypeError(`local class base ${JSON.stringify(root.name)} cannot be represented in the binding interface; use a class declaration`)
454
+ }
455
+ usedTypeNames.add(root.name)
456
+ reference = `${typeContext.typeNamespaceToken}.${root.name}`
457
+ } else if (typeContext.localValues.has(root.name)) {
458
+ throw new TypeError(`local class base ${JSON.stringify(root.name)} cannot be represented in the binding interface; use a class declaration`)
459
+ }
460
+ const argumentsNode = node.superTypeParameters ?? node.superTypeArguments
461
+ const typeArguments = argumentsNode === undefined ? ''
462
+ : projectedType(source, argumentsNode, classTypeNames, typeContext, usedTypeNames)
463
+ const instance = `${reference}${typeArguments}`
464
+ return { clause: ` extends ${instance}`, instance }
465
+ }
466
+
467
+ function localTypeDeclaration(source, name, node, typeContext, usedTypeNames) {
468
+ if (node.type === 'TSInterfaceDeclaration' || node.type === 'TSTypeAliasDeclaration'
469
+ || node.type === 'TSEnumDeclaration') {
470
+ const ownTypeNames = new Set(typeParameterNames(node.typeParameters))
471
+ const text = projectedType(source, node, ownTypeNames, typeContext, usedTypeNames)
472
+ .replace(/^declare\s+/, '')
473
+ return `export ${text}`
474
+ }
475
+ if (node.type === 'ClassDeclaration') {
476
+ const generics = typeParameters(source, node, new Set(), typeContext, usedTypeNames)
477
+ const heritage = classHeritage(source, node, generics.names, typeContext, usedTypeNames)
478
+ const shape = classMembers(source, node, generics.names, typeContext, usedTypeNames)
479
+ return `export ${node.abstract === true ? 'abstract ' : ''}class ${name}${generics.text}${heritage.clause} { constructor(${shape.constructorParameters});${shape.members.length === 0 ? '' : ` ${shape.members.join('; ')};`} }`
480
+ }
481
+ throw new TypeError(`local type ${JSON.stringify(name)} cannot be represented in the binding interface; use a local interface, type alias, enum, or class`)
482
+ }
483
+
484
+ function typeDependencyDeclarations(source, roots, typeContext) {
485
+ if (!typeContext.modern) return []
486
+ const pending = [...roots]
487
+ const seen = new Set()
488
+ const declarations = []
489
+ while (pending.length > 0) {
490
+ const name = pending.shift()
491
+ if (seen.has(name)) continue
492
+ seen.add(name)
493
+ const node = typeContext.localDeclarations.get(name)
494
+ if (node === undefined) continue
495
+ const dependencies = new Set()
496
+ declarations.push(localTypeDeclaration(source, name, node, typeContext, dependencies))
497
+ for (const dependency of dependencies) if (!seen.has(dependency)) pending.push(dependency)
498
+ }
499
+ return declarations
500
+ }
501
+
239
502
  function jsdocPurpose(...nodes) {
240
503
  const comment = nodes.flatMap(node => node?.leadingComments ?? [])
241
504
  .find(item => item.type === 'CommentBlock' && item.value.startsWith('*'))
@@ -245,51 +508,62 @@ function jsdocPurpose(...nodes) {
245
508
  .find(line => line !== '' && !line.startsWith('@')) ?? ''
246
509
  }
247
510
 
248
- function symbolDescriptor(source, name, node, exportNode = node, sourceTypeNames = new Set()) {
511
+ function symbolDescriptor(source, name, node, exportNode, typeContext) {
512
+ const usedTypeNames = new Set()
513
+ const complete = descriptor => ({
514
+ ...descriptor,
515
+ typeNamespaceToken: typeContext.typeNamespaceToken,
516
+ typeDeclarations: typeDependencyDeclarations(source, usedTypeNames, typeContext),
517
+ })
249
518
  if (node?.type === 'FunctionDeclaration') {
250
- const generics = typeParameters(source, node, new Set(), sourceTypeNames)
519
+ const generics = typeParameters(source, node, new Set(), typeContext, usedTypeNames)
251
520
  const parameters = (node.params ?? [])
252
- .map((item, index) => parameter(source, item, index, generics.names, sourceTypeNames)).join(', ')
253
- return {
521
+ .map((item, index) => parameter(source, item, index, generics.names, typeContext, usedTypeNames)).join(', ')
522
+ return complete({
254
523
  name,
255
524
  kind: 'function',
256
525
  purpose: jsdocPurpose(exportNode, node),
257
- declaration: `function ${name}${generics.text}(${parameters}): ${returnType(source, node, generics.names, sourceTypeNames)}`,
258
- member: `${name}${generics.text}(${parameters}): ${returnType(source, node, generics.names, sourceTypeNames)}`,
259
- }
526
+ declaration: `function ${name}${generics.text}(${parameters}): ${returnType(source, node, generics.names, typeContext, usedTypeNames)}`,
527
+ member: `${name}${generics.text}(${parameters}): ${returnType(source, node, generics.names, typeContext, usedTypeNames)}`,
528
+ })
260
529
  }
261
530
  if (node?.type === 'ClassDeclaration') {
262
- const generics = typeParameters(source, node, new Set(), sourceTypeNames)
263
- const shape = classMembers(source, node, generics.names, sourceTypeNames)
264
- return {
531
+ const generics = typeParameters(source, node, new Set(), typeContext, usedTypeNames)
532
+ const heritage = classHeritage(source, node, generics.names, typeContext, usedTypeNames)
533
+ const shape = classMembers(source, node, generics.names, typeContext, usedTypeNames)
534
+ const instance = heritage.instance === '' ? shape.instance : `${heritage.instance} & ${shape.instance}`
535
+ return complete({
265
536
  name,
266
537
  kind: 'class',
267
538
  purpose: jsdocPurpose(exportNode, node),
268
- declaration: `class ${name}${generics.text} { constructor(${shape.constructorParameters});${shape.members.length === 0 ? '' : ` ${shape.members.join('; ')};`} }`,
269
- member: `${name}: { new${generics.text}(${shape.constructorParameters}): ${shape.instance} }`,
270
- }
539
+ declaration: `${node.abstract === true ? 'abstract ' : ''}class ${name}${generics.text}${heritage.clause} { constructor(${shape.constructorParameters});${shape.members.length === 0 ? '' : ` ${shape.members.join('; ')};`} }`,
540
+ member: node.abstract === true
541
+ ? `${name}: abstract new${generics.text}(${shape.constructorParameters}) => ${instance}`
542
+ : `${name}: { new${generics.text}(${shape.constructorParameters}): ${instance} }`,
543
+ })
271
544
  }
272
545
  const declarator = node?.type === 'VariableDeclarator' ? node : undefined
273
546
  const type = declarator?.id?.type !== 'Identifier'
274
547
  ? 'unknown'
275
548
  : declarator.id.typeAnnotation !== undefined
276
- ? annotation(source, declarator.id, new Set(), sourceTypeNames)
277
- : inferredType(source, declarator.init, 0, sourceTypeNames)
278
- return {
549
+ ? annotation(source, declarator.id, new Set(), typeContext, usedTypeNames)
550
+ : inferredType(source, declarator.init, 0, typeContext, usedTypeNames)
551
+ return complete({
279
552
  name,
280
553
  kind: 'variable',
281
554
  purpose: jsdocPurpose(exportNode, node),
282
555
  declaration: `const ${name}: ${type}`,
283
556
  member: `${name}: ${type}`,
284
- }
557
+ })
285
558
  }
286
559
 
287
560
  export const exportedSymbols = createCompilerSourceCache(analyzeExportedSymbols)
288
561
 
289
562
  function analyzeExportedSymbols(source, transform) {
290
- let ast
563
+ const metadataSource = source
564
+ let metadataAst
291
565
  try {
292
- ast = parse(source, {
566
+ metadataAst = parse(metadataSource, {
293
567
  sourceType: 'module',
294
568
  plugins: transform === LEGACY_USER_BINDING_TRANSFORM
295
569
  ? ['typescript', 'topLevelAwait', 'importAttributes'] : CELL_PARSER_PLUGINS,
@@ -298,20 +572,58 @@ function analyzeExportedSymbols(source, transform) {
298
572
  } catch (error) {
299
573
  throw new SyntaxError(`binding source could not be parsed: ${error.message}`)
300
574
  }
301
- const sourceTypeNames = new Set()
302
- for (const statement of ast.program.body) {
575
+ const importedNames = new Set()
576
+ const localDeclarations = new Map()
577
+ const localValueNames = new Set()
578
+ const metadataLocals = new Map()
579
+ const metadataExportNodes = new Map()
580
+ for (const statement of metadataAst.program.body) {
303
581
  const declaration = statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement
304
582
  if (statement.type === 'ImportDeclaration') {
305
- for (const specifier of statement.specifiers) sourceTypeNames.add(specifier.local.name)
583
+ for (const specifier of statement.specifiers) importedNames.add(specifier.local.name)
306
584
  }
307
585
  if (declaration?.type === 'TSTypeAliasDeclaration'
308
586
  || declaration?.type === 'TSInterfaceDeclaration'
309
587
  || declaration?.type === 'TSEnumDeclaration'
310
588
  || declaration?.type === 'TSModuleDeclaration'
311
589
  || declaration?.type === 'ClassDeclaration') {
312
- if (declaration.id?.type === 'Identifier') sourceTypeNames.add(declaration.id.name)
590
+ if (declaration.id?.type === 'Identifier') localDeclarations.set(declaration.id.name, declaration)
591
+ }
592
+ if (declaration?.type === 'TSEnumDeclaration' || declaration?.type === 'TSModuleDeclaration') {
593
+ if (declaration.id?.type === 'Identifier') localValueNames.add(declaration.id.name)
594
+ }
595
+ if (declaration?.type === 'FunctionDeclaration' || declaration?.type === 'ClassDeclaration') {
596
+ if (declaration.id !== null) {
597
+ localValueNames.add(declaration.id.name)
598
+ metadataLocals.set(declaration.id.name, declaration)
599
+ if (statement.type === 'ExportNamedDeclaration') metadataExportNodes.set(declaration.id.name, statement)
600
+ }
601
+ } else if (declaration?.type === 'VariableDeclaration') {
602
+ for (const item of declaration.declarations) {
603
+ for (const name of bindingNames(item.id)) {
604
+ localValueNames.add(name)
605
+ metadataLocals.set(name, item)
606
+ if (statement.type === 'ExportNamedDeclaration') metadataExportNodes.set(name, statement)
607
+ }
608
+ }
609
+ }
610
+ if (statement.type === 'ExportNamedDeclaration') {
611
+ for (const specifier of statement.specifiers) {
612
+ if (specifier.type === 'ExportSpecifier' && specifier.local.type === 'Identifier') {
613
+ metadataExportNodes.set(specifier.local.name, statement)
614
+ }
615
+ }
313
616
  }
314
617
  }
618
+ const typeContext = {
619
+ modern: transform === USER_BINDING_TRANSFORM,
620
+ importedNames,
621
+ localDeclarations,
622
+ localValues: localValueNames,
623
+ sourceTypeNames: new Set([...importedNames, ...localDeclarations.keys()]),
624
+ typeNamespaceToken: bindingTypeNamespaceToken(metadataSource),
625
+ }
626
+ let ast = metadataAst
315
627
  if (transform !== LEGACY_USER_BINDING_TRANSFORM) {
316
628
  source = normalizeTypeScriptValues(source, identitySourceMap(source.length), 'module').code
317
629
  ast = parse(source, { sourceType: 'module', plugins: CELL_PARSER_PLUGINS, errorRecovery: true })
@@ -328,6 +640,13 @@ function analyzeExportedSymbols(source, transform) {
328
640
  }
329
641
  }
330
642
  const exports = new Map()
643
+ const descriptor = (name, runtimeNode, exportNode, localName = name) => {
644
+ const metadataNode = metadataLocals.get(localName)
645
+ return metadataNode === undefined
646
+ ? symbolDescriptor(source, name, runtimeNode, exportNode, typeContext)
647
+ : symbolDescriptor(metadataSource, name, metadataNode,
648
+ metadataExportNodes.get(localName) ?? metadataNode, typeContext)
649
+ }
331
650
  for (const statement of ast.program.body) {
332
651
  if (statement.type === 'ExportDefaultDeclaration' || statement.type === 'ExportAllDeclaration') {
333
652
  throw new TypeError('binding source supports named exports only')
@@ -339,15 +658,13 @@ function analyzeExportedSymbols(source, transform) {
339
658
  const declaration = statement.declaration
340
659
  if (declaration?.type === 'FunctionDeclaration' || declaration?.type === 'ClassDeclaration') {
341
660
  if (declaration.id === null) throw new TypeError('binding source exports must be named')
342
- exports.set(declaration.id.name, symbolDescriptor(
343
- source, declaration.id.name, declaration, statement, sourceTypeNames,
344
- ))
661
+ exports.set(declaration.id.name, descriptor(declaration.id.name, declaration, statement))
345
662
  continue
346
663
  }
347
664
  if (declaration?.type === 'VariableDeclaration') {
348
665
  for (const item of declaration.declarations) {
349
666
  for (const name of bindingNames(item.id)) {
350
- exports.set(name, symbolDescriptor(source, name, item, statement, sourceTypeNames))
667
+ exports.set(name, descriptor(name, item, statement))
351
668
  }
352
669
  }
353
670
  continue
@@ -359,7 +676,7 @@ function analyzeExportedSymbols(source, transform) {
359
676
  identifier(exported, 'exported binding name')
360
677
  const target = locals.get(local)
361
678
  if (target === undefined) throw new TypeError(`export ${JSON.stringify(exported)} has no local value declaration`)
362
- exports.set(exported, symbolDescriptor(source, exported, target, target, sourceTypeNames))
679
+ exports.set(exported, descriptor(exported, target, target, local))
363
680
  }
364
681
  }
365
682
  if (exports.size === 0) throw new TypeError('binding source must contain at least one named value export')