@napi-rs/cli 3.7.3 → 3.8.0

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 (51) hide show
  1. package/README.md +33 -0
  2. package/cli.mjs +9 -12
  3. package/dist/cli.js +9277 -1313
  4. package/dist/index.cjs +9266 -1302
  5. package/dist/index.d.cts +176 -158
  6. package/dist/index.d.ts +176 -158
  7. package/dist/index.js +9277 -1313
  8. package/docs/artifacts.md +33 -0
  9. package/docs/build.md +58 -0
  10. package/docs/create-npm-dirs.md +32 -0
  11. package/docs/new.md +39 -0
  12. package/docs/pre-publish.md +37 -0
  13. package/docs/rename.md +37 -0
  14. package/docs/universalize.md +31 -0
  15. package/docs/version.md +31 -0
  16. package/docs/wasi.md +161 -0
  17. package/package.json +11 -10
  18. package/src/api/__tests__/__snapshots__/templates.spec.ts.md +1019 -114
  19. package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
  20. package/src/api/__tests__/artifacts.spec.ts +17 -0
  21. package/src/api/__tests__/create-npm-dirs.spec.ts +14 -15
  22. package/src/api/__tests__/new.spec.ts +41 -5
  23. package/src/api/artifacts.ts +934 -108
  24. package/src/api/build.ts +1325 -194
  25. package/src/api/create-npm-dirs.ts +472 -101
  26. package/src/api/new.ts +354 -35
  27. package/src/api/pre-publish.ts +2845 -99
  28. package/src/api/rename.ts +1638 -124
  29. package/src/api/templates/js-binding.ts +216 -38
  30. package/src/api/templates/load-wasi-template.ts +1641 -111
  31. package/src/api/templates/wasi-worker-template.ts +32 -3
  32. package/src/commands/rename.ts +8 -3
  33. package/src/def/artifacts.ts +2 -2
  34. package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
  35. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
  36. package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
  37. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
  38. package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
  39. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
  40. package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
  41. package/src/utils/__tests__/__snapshots__/target.spec.ts.md +2 -2
  42. package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
  43. package/src/utils/__tests__/metadata.spec.ts +43 -2
  44. package/src/utils/__tests__/misc.spec.ts +124 -3
  45. package/src/utils/__tests__/version.spec.ts +47 -1
  46. package/src/utils/config.ts +15 -2
  47. package/src/utils/metadata.ts +206 -7
  48. package/src/utils/misc.ts +6079 -0
  49. package/src/utils/target.ts +66 -8
  50. package/src/utils/typegen.ts +1223 -63
  51. package/src/utils/version.ts +96 -0
@@ -1,11 +1,49 @@
1
+ import { createRequire } from 'node:module'
2
+ import { dirname, relative, resolve } from 'node:path'
3
+
1
4
  import { sortBy } from 'es-toolkit'
5
+ import type {
6
+ CompilerHost,
7
+ CompilerOptions,
8
+ Diagnostic,
9
+ EntityName,
10
+ Identifier,
11
+ NodeArray,
12
+ SourceFile,
13
+ Statement,
14
+ SymbolFlags,
15
+ } from 'typescript'
2
16
 
3
17
  import { readFileAsync } from './misc.js'
4
18
 
19
+ const require = createRequire(import.meta.url)
20
+ type TypeScriptModule = typeof import('typescript')
5
21
  const TOP_LEVEL_NAMESPACE = '__TOP_LEVEL_MODULE__'
6
22
  export const DEFAULT_TYPE_DEF_HEADER = `/* auto-generated by NAPI-RS */
7
23
  /* eslint-disable */
8
24
  `
25
+ const ITERATOR_OBJECT_COMPATIBILITY_DECLARATION = ` interface IteratorObject<T, TReturn = unknown, TNext = unknown>
26
+ extends globalThis.Iterator<T, TReturn, TNext> {
27
+ [globalThis.Symbol.iterator](): globalThis.IteratorObject<T, TReturn, TNext>
28
+ }`
29
+
30
+ function asyncGeneratorDeclaration(name: string): string {
31
+ // TOwner keeps downstream declaration emit importing the module that
32
+ // installs this global augmentation, even though it is structurally unused.
33
+ return ` interface ${name}<TOwner, T, TReturn, TNext> {
34
+ next(...[value]: [] | [TNext]): globalThis.Promise<globalThis.IteratorResult<T, TReturn | undefined>>
35
+ return(...[value]: [] | [TReturn]): globalThis.Promise<globalThis.IteratorResult<T, TReturn | undefined>>
36
+ throw(exception?: unknown): globalThis.Promise<globalThis.IteratorResult<T, TReturn | undefined>>
37
+ [globalThis.Symbol.asyncIterator](): this
38
+ }`
39
+ }
40
+
41
+ export function createWasmModuleTypeDef() {
42
+ return `${DEFAULT_TYPE_DEF_HEADER}
43
+ declare const wasmModule: WebAssembly.Module
44
+ export default wasmModule
45
+ `
46
+ }
9
47
 
10
48
  enum TypeDefKind {
11
49
  Const = 'const',
@@ -24,9 +62,118 @@ interface TypeDefLine {
24
62
  name: string
25
63
  original_name?: string
26
64
  def: string
65
+ def_with_type_import_markers?: string
27
66
  extends?: string
67
+ asyncIterator?: [yieldType: string, returnType: string, nextType: string]
28
68
  js_doc?: string
29
69
  js_mod?: string
70
+ type_imports?: TypeImport[]
71
+ }
72
+
73
+ export interface TypeImport {
74
+ marker?: string
75
+ name: string
76
+ module: string
77
+ }
78
+
79
+ function parseTypeParameters(
80
+ source: string,
81
+ ): [yieldType: string, returnType: string, nextType: string] | undefined {
82
+ const parameters: string[] = []
83
+ let start = 0
84
+ const depths = {
85
+ angle: 0,
86
+ brace: 0,
87
+ bracket: 0,
88
+ parenthesis: 0,
89
+ }
90
+ let quote: "'" | '"' | '`' | undefined
91
+ let escaped = false
92
+
93
+ for (let index = 0; index < source.length; index += 1) {
94
+ const character = source[index]
95
+ if (quote) {
96
+ if (escaped) {
97
+ escaped = false
98
+ } else if (character === '\\') {
99
+ escaped = true
100
+ } else if (character === quote) {
101
+ quote = undefined
102
+ }
103
+ continue
104
+ }
105
+
106
+ if (character === "'" || character === '"' || character === '`') {
107
+ quote = character
108
+ continue
109
+ }
110
+
111
+ switch (character) {
112
+ case '<':
113
+ depths.angle += 1
114
+ break
115
+ case '>':
116
+ if (source[index - 1] !== '=') {
117
+ depths.angle -= 1
118
+ }
119
+ break
120
+ case '{':
121
+ depths.brace += 1
122
+ break
123
+ case '}':
124
+ depths.brace -= 1
125
+ break
126
+ case '[':
127
+ depths.bracket += 1
128
+ break
129
+ case ']':
130
+ depths.bracket -= 1
131
+ break
132
+ case '(':
133
+ depths.parenthesis += 1
134
+ break
135
+ case ')':
136
+ depths.parenthesis -= 1
137
+ break
138
+ case ',':
139
+ if (Object.values(depths).every((depth) => depth === 0)) {
140
+ parameters.push(source.slice(start, index).trim())
141
+ start = index + 1
142
+ }
143
+ break
144
+ }
145
+
146
+ if (Object.values(depths).some((depth) => depth < 0)) {
147
+ return
148
+ }
149
+ }
150
+
151
+ if (quote || Object.values(depths).some((depth) => depth !== 0)) {
152
+ return
153
+ }
154
+
155
+ parameters.push(source.slice(start).trim())
156
+ if (parameters.length !== 3 || parameters.some((parameter) => !parameter)) {
157
+ return
158
+ }
159
+
160
+ return parameters as [string, string, string]
161
+ }
162
+
163
+ function parseIteratorExtends(
164
+ extendsDef: string,
165
+ ): [yieldType: string, returnType: string, nextType: string] | undefined {
166
+ const match = extendsDef.trim().match(/^Iterator\s*<([\s\S]*)>$/)
167
+ return match ? parseTypeParameters(match[1]) : undefined
168
+ }
169
+
170
+ function parseAsyncGeneratorImpl(
171
+ implDef: string,
172
+ ): [yieldType: string, returnType: string, nextType: string] | undefined {
173
+ const match = implDef
174
+ .trim()
175
+ .match(/^\[Symbol\.asyncIterator\]\(\):\s*AsyncGenerator\s*<([\s\S]*)>$/)
176
+ return match ? parseTypeParameters(match[1]) : undefined
30
177
  }
31
178
 
32
179
  /**
@@ -51,6 +198,7 @@ function prettyPrint(
51
198
  runtimeStringEnum: boolean,
52
199
  ident: number,
53
200
  ambient = false,
201
+ asyncGeneratorHelperName?: string,
54
202
  ): string {
55
203
  let s = line.js_doc ?? ''
56
204
  switch (line.kind) {
@@ -79,25 +227,42 @@ function prettyPrint(
79
227
  break
80
228
  }
81
229
 
82
- case TypeDefKind.Struct:
83
- const extendsDef = line.extends ? ` extends ${line.extends}` : ''
84
- if (line.extends) {
85
- // Extract generic params from extends type like Iterator<T, TResult, TNext>
86
- const genericMatch = line.extends.match(/Iterator<(.+)>$/)
87
- if (genericMatch) {
88
- const [T, TResult, TNext] = genericMatch[1]
89
- .split(',')
90
- .map((p) => p.trim())
91
- line.def =
92
- line.def +
93
- `\nnext(value?: ${TNext}): IteratorResult<${T}, ${TResult}>`
230
+ case TypeDefKind.Struct: {
231
+ let classDef = line.def
232
+ let extendsDef = line.extends ? ` extends ${line.extends}` : ''
233
+ let iteratorInterface = ''
234
+ const iteratorTypes = line.extends
235
+ ? parseIteratorExtends(line.extends)
236
+ : undefined
237
+ if (iteratorTypes) {
238
+ // Runtime instances inherit from Iterator.prototype when it exists,
239
+ // but the generated constructor does not extend the global Iterator.
240
+ const [T, TResult, TNext] = iteratorTypes
241
+ const resultType = `(${TResult}) | undefined`
242
+ classDef +=
243
+ `\n[globalThis.Symbol.iterator](): this` +
244
+ `\nnext(...[value]: [] | [${TNext}]): globalThis.IteratorResult<${T}, ${resultType}>` +
245
+ `\nreturn(...[value]: [] | [${TResult}]): globalThis.IteratorResult<${T}, ${resultType}>` +
246
+ `\nthrow(exception?: unknown): globalThis.IteratorResult<${T}, ${resultType}>`
247
+ extendsDef = ''
248
+ iteratorInterface =
249
+ `\n\nexport interface ${line.name} ` +
250
+ `extends globalThis.Omit<globalThis.IteratorObject<${T}, ${resultType}, ${TNext}>, 'next' | 'return' | 'throw'> {}`
251
+ }
252
+ if (line.asyncIterator) {
253
+ if (!asyncGeneratorHelperName) {
254
+ throw new Error('Async generator helper name was not initialized')
94
255
  }
256
+ const [T, TResult, TNext] = line.asyncIterator
257
+ classDef += `\n[globalThis.Symbol.asyncIterator](): globalThis.${asyncGeneratorHelperName}<${line.name}, ${T}, ${TResult}, ${TNext}>`
95
258
  }
96
- s += `${exportDeclare(ambient)} class ${line.name}${extendsDef} {\n${line.def}\n}`
259
+ s += `${exportDeclare(ambient)} class ${line.name}${extendsDef} {\n${classDef}\n}`
260
+ s += iteratorInterface
97
261
  if (line.original_name && line.original_name !== line.name) {
98
262
  s += `\nexport type ${line.original_name} = ${line.name}`
99
263
  }
100
264
  break
265
+ }
101
266
 
102
267
  case TypeDefKind.Fn:
103
268
  s += `${exportDeclare(ambient)} ${line.def}`
@@ -132,53 +297,1040 @@ export async function processTypeDef(
132
297
  intermediateTypeFile: string,
133
298
  constEnum: boolean,
134
299
  runtimeStringEnum: boolean = false,
300
+ ) {
301
+ return processTypeDefs([intermediateTypeFile], constEnum, runtimeStringEnum)
302
+ }
303
+
304
+ export async function processTypeDefs(
305
+ intermediateTypeFiles: string[],
306
+ constEnum: boolean,
307
+ runtimeStringEnum: boolean = false,
308
+ reservedDeclarationText: string = '',
135
309
  ) {
136
310
  const exports: string[] = []
137
- const defs = await readIntermediateTypeFile(intermediateTypeFile)
138
- const groupedDefs = preprocessTypeDef(defs)
311
+ const typeDefs = await Promise.all(
312
+ intermediateTypeFiles.map((file) => readIntermediateTypeFile(file)),
313
+ )
314
+ const typeDefsWithUniqueMarkers = makeTypeImportMarkersUnique(
315
+ typeDefs,
316
+ reservedDeclarationText,
317
+ )
318
+ const typeImports = collectTypeImports(typeDefsWithUniqueMarkers.flat())
319
+ const dtsWithTypeImportMarkers = renderTypeDefs(
320
+ typeDefsWithUniqueMarkers.map((defs) =>
321
+ preprocessTypeDef(preserveTypeImportMarkers(defs)),
322
+ ),
323
+ constEnum,
324
+ runtimeStringEnum,
325
+ exports,
326
+ reservedDeclarationText,
327
+ )
328
+ const dts = rewriteTypeImportReferences(
329
+ dtsWithTypeImportMarkers,
330
+ typeImports,
331
+ false,
332
+ )
139
333
 
140
- const dts =
141
- sortBy(Array.from(groupedDefs), [([namespace]) => namespace])
142
- .map(([namespace, defs]) => {
334
+ return {
335
+ dts,
336
+ dtsWithTypeImportMarkers,
337
+ exports,
338
+ typeImports,
339
+ }
340
+ }
341
+
342
+ function renderTypeDefs(
343
+ groupedTypeDefs: Map<string, TypeDefLine[]>[],
344
+ constEnum: boolean,
345
+ runtimeStringEnum: boolean,
346
+ exports: string[],
347
+ reservedDeclarationText: string,
348
+ ): string {
349
+ const topLevelExportNames = new Set<string>()
350
+ for (const groupedDefs of groupedTypeDefs) {
351
+ for (const [namespace, namespaceDefs] of groupedDefs) {
352
+ if (namespace !== TOP_LEVEL_NAMESPACE) {
353
+ if (namespace === 'globalThis') {
354
+ throw new Error(
355
+ 'The export name `globalThis` is reserved by NAPI-RS type generation',
356
+ )
357
+ }
358
+ topLevelExportNames.add(namespace)
359
+ }
360
+ for (const def of namespaceDefs) {
361
+ if (def.name === 'globalThis' || def.original_name === 'globalThis') {
362
+ throw new Error(
363
+ 'The export name `globalThis` is reserved by NAPI-RS type generation',
364
+ )
365
+ }
143
366
  if (namespace === TOP_LEVEL_NAMESPACE) {
144
- return defs
145
- .map((def) => {
146
- switch (def.kind) {
147
- case TypeDefKind.Const:
148
- case TypeDefKind.Enum:
149
- case TypeDefKind.StringEnum:
150
- case TypeDefKind.Fn:
151
- case TypeDefKind.Struct: {
152
- exports.push(def.name)
153
- if (def.original_name && def.original_name !== def.name) {
154
- exports.push(def.original_name)
367
+ topLevelExportNames.add(def.name)
368
+ if (def.original_name) {
369
+ topLevelExportNames.add(def.original_name)
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ const hasIteratorClass = groupedTypeDefs.some((groupedDefs) =>
376
+ Array.from(groupedDefs.values()).some((defs) =>
377
+ defs.some(
378
+ (def) =>
379
+ def.kind === TypeDefKind.Struct &&
380
+ def.extends !== undefined &&
381
+ parseIteratorExtends(def.extends) !== undefined,
382
+ ),
383
+ ),
384
+ )
385
+ const hasAsyncIteratorClass = groupedTypeDefs.some((groupedDefs) =>
386
+ Array.from(groupedDefs.values()).some((defs) =>
387
+ defs.some(
388
+ (def) =>
389
+ def.kind === TypeDefKind.Struct && def.asyncIterator !== undefined,
390
+ ),
391
+ ),
392
+ )
393
+ let asyncGeneratorHelperName = '__NapiRsAsyncGenerator'
394
+ for (
395
+ let suffix = 1;
396
+ topLevelExportNames.has(asyncGeneratorHelperName) ||
397
+ reservedDeclarationText.includes(asyncGeneratorHelperName);
398
+ suffix += 1
399
+ ) {
400
+ asyncGeneratorHelperName = `__NapiRsAsyncGenerator_${suffix}`
401
+ }
402
+ const renderedDefs = groupedTypeDefs
403
+ .map((groupedDefs) =>
404
+ sortBy(Array.from(groupedDefs), [([namespace]) => namespace])
405
+ .map(([namespace, defs]) => {
406
+ if (namespace === TOP_LEVEL_NAMESPACE) {
407
+ return defs
408
+ .map((def) => {
409
+ switch (def.kind) {
410
+ case TypeDefKind.Const:
411
+ case TypeDefKind.Enum:
412
+ case TypeDefKind.StringEnum:
413
+ case TypeDefKind.Fn:
414
+ case TypeDefKind.Struct: {
415
+ exports.push(def.name)
416
+ if (def.original_name && def.original_name !== def.name) {
417
+ exports.push(def.original_name)
418
+ }
419
+ break
155
420
  }
156
- break
421
+ default:
422
+ break
157
423
  }
158
- default:
159
- break
160
- }
161
- return prettyPrint(def, constEnum, runtimeStringEnum, 0)
162
- })
163
- .join('\n\n')
164
- } else {
165
- exports.push(namespace)
166
- let declaration = ''
167
- declaration += `export declare namespace ${namespace} {\n`
168
- for (const def of defs) {
169
- declaration +=
170
- prettyPrint(def, constEnum, runtimeStringEnum, 2, true) + '\n'
424
+ return prettyPrint(
425
+ def,
426
+ constEnum,
427
+ runtimeStringEnum,
428
+ 0,
429
+ false,
430
+ asyncGeneratorHelperName,
431
+ )
432
+ })
433
+ .join('\n\n')
434
+ } else {
435
+ exports.push(namespace)
436
+ let declaration = ''
437
+ declaration += `export declare namespace ${namespace} {\n`
438
+ for (const def of defs) {
439
+ declaration +=
440
+ prettyPrint(
441
+ def,
442
+ constEnum,
443
+ runtimeStringEnum,
444
+ 2,
445
+ true,
446
+ asyncGeneratorHelperName,
447
+ ) + '\n'
448
+ }
449
+ declaration += '}'
450
+ return declaration
171
451
  }
172
- declaration += '}'
173
- return declaration
452
+ })
453
+ .join('\n\n'),
454
+ )
455
+ .join('\n')
456
+ const globalDeclarations = []
457
+ if (hasIteratorClass) {
458
+ globalDeclarations.push(ITERATOR_OBJECT_COMPATIBILITY_DECLARATION)
459
+ }
460
+ if (hasAsyncIteratorClass) {
461
+ globalDeclarations.push(asyncGeneratorDeclaration(asyncGeneratorHelperName))
462
+ }
463
+ const dts =
464
+ (globalDeclarations.length
465
+ ? `declare global {\n${globalDeclarations.join('\n\n')}\n}\n\n`
466
+ : '') +
467
+ renderedDefs +
468
+ '\n'
469
+ return dts
470
+ }
471
+
472
+ function makeTypeImportMarkersUnique(
473
+ typeDefGroups: TypeDefLine[][],
474
+ reservedDeclarationText: string,
475
+ ): TypeDefLine[][] {
476
+ const reservedText = [
477
+ reservedDeclarationText,
478
+ ...typeDefGroups
479
+ .flat()
480
+ .flatMap((def) => [def.def, def.js_doc ?? '', def.name]),
481
+ ].join('\n')
482
+ const allocated = new Set<string>()
483
+
484
+ return typeDefGroups.map((defs) =>
485
+ defs.map((def) => {
486
+ const markedDefinition = def.def_with_type_import_markers
487
+ const reallocations: Array<{
488
+ importedName: string
489
+ nextMarker: string
490
+ previousMarker: string
491
+ }> = []
492
+ const typeImports = def.type_imports?.map((typeImport) => {
493
+ if (!typeImport.marker || markedDefinition === undefined) {
494
+ return { ...typeImport }
495
+ }
496
+ const base = typeImport.marker
497
+ let marker = base
498
+ for (
499
+ let suffix = 1;
500
+ allocated.has(marker) || reservedText.includes(marker);
501
+ suffix += 1
502
+ ) {
503
+ marker = `${base}_${suffix}`
174
504
  }
505
+ allocated.add(marker)
506
+ reallocations.push({
507
+ importedName: typeImport.name,
508
+ nextMarker: marker,
509
+ previousMarker: typeImport.marker,
510
+ })
511
+ return { ...typeImport, marker }
175
512
  })
176
- .join('\n\n') + '\n'
513
+ return {
514
+ ...def,
515
+ def_with_type_import_markers:
516
+ markedDefinition === undefined
517
+ ? undefined
518
+ : reallocateTypeImportMarkers(
519
+ def.def,
520
+ markedDefinition,
521
+ reallocations,
522
+ ),
523
+ type_imports: typeImports,
524
+ }
525
+ }),
526
+ )
527
+ }
177
528
 
178
- return {
179
- dts,
180
- exports,
529
+ function reallocateTypeImportMarkers(
530
+ definition: string,
531
+ markedDefinition: string,
532
+ reallocations: Array<{
533
+ importedName: string
534
+ nextMarker: string
535
+ previousMarker: string
536
+ }>,
537
+ ) {
538
+ const changedReallocations = reallocations
539
+ .filter(({ nextMarker, previousMarker }) => previousMarker !== nextMarker)
540
+ .sort(
541
+ (left, right) => right.previousMarker.length - left.previousMarker.length,
542
+ )
543
+ if (changedReallocations.length === 0) {
544
+ return markedDefinition
545
+ }
546
+
547
+ let definitionOffset = 0
548
+ let markedOffset = 0
549
+ let rewritten = ''
550
+ while (markedOffset < markedDefinition.length) {
551
+ let markerOffset = -1
552
+ let reallocation: (typeof changedReallocations)[number] | undefined
553
+ for (const candidate of changedReallocations) {
554
+ const candidateOffset = markedDefinition.indexOf(
555
+ candidate.previousMarker,
556
+ markedOffset,
557
+ )
558
+ if (
559
+ candidateOffset !== -1 &&
560
+ (markerOffset === -1 || candidateOffset < markerOffset)
561
+ ) {
562
+ markerOffset = candidateOffset
563
+ reallocation = candidate
564
+ }
565
+ }
566
+ if (markerOffset === -1 || reallocation === undefined) {
567
+ break
568
+ }
569
+ const prefix = markedDefinition.slice(markedOffset, markerOffset)
570
+ if (!definition.startsWith(prefix, definitionOffset)) {
571
+ throw new Error(
572
+ `Imported type marker ${reallocation.previousMarker} does not match its declaration`,
573
+ )
574
+ }
575
+ rewritten += prefix
576
+ definitionOffset += prefix.length
577
+ markedOffset = markerOffset + reallocation.previousMarker.length
578
+
579
+ if (definition.startsWith(reallocation.previousMarker, definitionOffset)) {
580
+ rewritten += reallocation.previousMarker
581
+ definitionOffset += reallocation.previousMarker.length
582
+ } else if (
583
+ definition.startsWith(reallocation.importedName, definitionOffset)
584
+ ) {
585
+ rewritten += reallocation.nextMarker
586
+ definitionOffset += reallocation.importedName.length
587
+ } else {
588
+ throw new Error(
589
+ `Imported type marker ${reallocation.previousMarker} does not match ${reallocation.importedName} in its declaration`,
590
+ )
591
+ }
592
+ }
593
+
594
+ const markedRemainder = markedDefinition.slice(markedOffset)
595
+ const definitionRemainder = definition.slice(definitionOffset)
596
+ if (markedRemainder !== definitionRemainder) {
597
+ throw new Error('Imported type markers leave mismatched declaration text')
598
+ }
599
+ return rewritten + markedRemainder
600
+ }
601
+
602
+ function preserveTypeImportMarkers(defs: TypeDefLine[]): TypeDefLine[] {
603
+ return defs.map((def) => ({
604
+ ...def,
605
+ def: def.def_with_type_import_markers ?? def.def,
606
+ }))
607
+ }
608
+
609
+ function collectTypeImports(defs: TypeDefLine[]): TypeImport[] {
610
+ const imports = new Map<string, TypeImport>()
611
+ for (const typeImport of defs.flatMap((def) => def.type_imports ?? [])) {
612
+ imports.set(
613
+ `${typeImport.marker ?? ''}\0${typeImport.module}\0${typeImport.name}`,
614
+ typeImport,
615
+ )
181
616
  }
617
+ return [...imports.values()].sort(
618
+ (left, right) =>
619
+ left.module.localeCompare(right.module) ||
620
+ left.name.localeCompare(right.name) ||
621
+ (left.marker ?? '').localeCompare(right.marker ?? ''),
622
+ )
623
+ }
624
+
625
+ const BUFFER_TYPE_REFERENCE = 'import("buffer").Buffer'
626
+ const BUFFER_HERITAGE_ALIAS = '__NapiRsBuffer'
627
+ const IN_MEMORY_DECLARATION_FILE = '/__napi_rs_typegen__.d.ts'
628
+
629
+ export function rewriteTypeImportReferences(
630
+ source: string,
631
+ typeImports: TypeImport[],
632
+ inlineImports: boolean,
633
+ ): string {
634
+ const markerImports = new Map<string, TypeImport>()
635
+ for (const typeImport of typeImports) {
636
+ const { marker } = typeImport
637
+ if (marker) {
638
+ markerImports.set(marker, typeImport)
639
+ }
640
+ }
641
+ const rewriteUnboundBuffer = inlineImports && source.includes('Buffer')
642
+ if (markerImports.size === 0 && !rewriteUnboundBuffer) {
643
+ return source
644
+ }
645
+
646
+ const typeScript = loadTypeScript()
647
+ const { program, sourceFile } = createDeclarationProgram(source)
648
+ const checker = rewriteUnboundBuffer ? program.getTypeChecker() : undefined
649
+ let bufferHeritageAlias: string | undefined
650
+ const getBufferHeritageAlias = () => {
651
+ bufferHeritageAlias ??= createCollisionSafeIdentifier(
652
+ typeScript,
653
+ sourceFile,
654
+ BUFFER_HERITAGE_ALIAS,
655
+ )
656
+ return bufferHeritageAlias
657
+ }
658
+ const replacements: Array<{
659
+ end: number
660
+ replacement: string
661
+ start: number
662
+ }> = []
663
+
664
+ const visit = (node: import('typescript').Node) => {
665
+ if (typeScript.isIdentifier(node)) {
666
+ const markerImport = markerImports.get(node.text)
667
+ if (
668
+ markerImport !== undefined &&
669
+ typeImportReferenceMeaning(typeScript, node) !== undefined
670
+ ) {
671
+ const useBufferHeritageAlias =
672
+ inlineImports &&
673
+ markerImport.module === 'buffer' &&
674
+ markerImport.name === 'Buffer' &&
675
+ isHeritageReference(typeScript, node)
676
+ replacements.push({
677
+ start: node.getStart(sourceFile),
678
+ end: node.end,
679
+ replacement: inlineImports
680
+ ? useBufferHeritageAlias
681
+ ? getBufferHeritageAlias()
682
+ : `import(${JSON.stringify(markerImport.module)}).${markerImport.name}`
683
+ : markerImport.name,
684
+ })
685
+ } else if (
686
+ checker !== undefined &&
687
+ node.text === 'Buffer' &&
688
+ isUnboundBufferReference(typeScript, checker, node)
689
+ ) {
690
+ replacements.push({
691
+ start: node.getStart(sourceFile),
692
+ end: node.end,
693
+ replacement: isHeritageReference(typeScript, node)
694
+ ? getBufferHeritageAlias()
695
+ : BUFFER_TYPE_REFERENCE,
696
+ })
697
+ }
698
+ }
699
+ typeScript.forEachChild(node, visit)
700
+ }
701
+ visit(sourceFile)
702
+
703
+ let rewritten = source
704
+ for (const replacement of replacements.reverse()) {
705
+ rewritten =
706
+ rewritten.slice(0, replacement.start) +
707
+ replacement.replacement +
708
+ rewritten.slice(replacement.end)
709
+ }
710
+ if (bufferHeritageAlias) {
711
+ rewritten = appendAliasedImport(
712
+ rewritten,
713
+ 'buffer',
714
+ 'Buffer',
715
+ bufferHeritageAlias,
716
+ )
717
+ }
718
+ return rewritten
719
+ }
720
+
721
+ export function rebaseDeclarationSpecifiers(
722
+ source: string,
723
+ sourcePath: string,
724
+ destinationPath: string,
725
+ ): string {
726
+ const references = collectRelativeDeclarationSpecifierReferences(source)
727
+ const replacements: Array<{
728
+ end: number
729
+ replacement: string
730
+ start: number
731
+ }> = []
732
+
733
+ for (const reference of references) {
734
+ const absoluteTarget = resolve(dirname(sourcePath), reference.specifier)
735
+ let rebased = relative(dirname(destinationPath), absoluteTarget).replaceAll(
736
+ '\\',
737
+ '/',
738
+ )
739
+ if (!rebased.startsWith('.')) {
740
+ rebased = `./${rebased}`
741
+ }
742
+ replacements.push({
743
+ start: reference.start,
744
+ end: reference.end,
745
+ replacement: rebased,
746
+ })
747
+ }
748
+
749
+ let rebasedSource = source
750
+ for (const replacement of replacements
751
+ .filter(
752
+ (replacement, index, all) =>
753
+ all.findIndex(
754
+ (candidate) =>
755
+ candidate.start === replacement.start &&
756
+ candidate.end === replacement.end,
757
+ ) === index,
758
+ )
759
+ .sort((left, right) => right.start - left.start)) {
760
+ rebasedSource =
761
+ rebasedSource.slice(0, replacement.start) +
762
+ replacement.replacement +
763
+ rebasedSource.slice(replacement.end)
764
+ }
765
+ return rebasedSource
766
+ }
767
+
768
+ interface DeclarationSpecifierReference {
769
+ end: number
770
+ specifier: string
771
+ start: number
772
+ }
773
+
774
+ export function collectRelativeDeclarationSpecifiers(source: string): string[] {
775
+ return [
776
+ ...new Set(
777
+ collectRelativeDeclarationSpecifierReferences(source).map(
778
+ ({ specifier }) => specifier,
779
+ ),
780
+ ),
781
+ ]
782
+ }
783
+
784
+ function collectRelativeDeclarationSpecifierReferences(
785
+ source: string,
786
+ ): DeclarationSpecifierReference[] {
787
+ const typeScript = loadTypeScript()
788
+ const sourceFile = typeScript.createSourceFile(
789
+ IN_MEMORY_DECLARATION_FILE,
790
+ source,
791
+ typeScript.ScriptTarget.Latest,
792
+ true,
793
+ typeScript.ScriptKind.TS,
794
+ )
795
+ const references: DeclarationSpecifierReference[] = []
796
+ const addStringLiteral = (node: import('typescript').StringLiteralLike) => {
797
+ if (!node.text.startsWith('.')) {
798
+ return
799
+ }
800
+ references.push({
801
+ start: node.getStart(sourceFile) + 1,
802
+ end: node.end - 1,
803
+ specifier: node.text,
804
+ })
805
+ }
806
+ const visit = (node: import('typescript').Node) => {
807
+ if (
808
+ (typeScript.isImportDeclaration(node) ||
809
+ typeScript.isExportDeclaration(node)) &&
810
+ node.moduleSpecifier &&
811
+ typeScript.isStringLiteralLike(node.moduleSpecifier)
812
+ ) {
813
+ addStringLiteral(node.moduleSpecifier)
814
+ } else if (
815
+ typeScript.isImportTypeNode(node) &&
816
+ typeScript.isLiteralTypeNode(node.argument) &&
817
+ typeScript.isStringLiteralLike(node.argument.literal)
818
+ ) {
819
+ addStringLiteral(node.argument.literal)
820
+ } else if (
821
+ typeScript.isExternalModuleReference(node) &&
822
+ node.expression &&
823
+ typeScript.isStringLiteralLike(node.expression)
824
+ ) {
825
+ addStringLiteral(node.expression)
826
+ } else if (
827
+ typeScript.isCallExpression(node) &&
828
+ node.arguments.length === 1 &&
829
+ typeScript.isStringLiteralLike(node.arguments[0]) &&
830
+ (node.expression.kind === typeScript.SyntaxKind.ImportKeyword ||
831
+ (typeScript.isIdentifier(node.expression) &&
832
+ node.expression.text === 'require'))
833
+ ) {
834
+ addStringLiteral(node.arguments[0])
835
+ }
836
+ typeScript.forEachChild(node, visit)
837
+ }
838
+ visit(sourceFile)
839
+
840
+ const preprocessed = typeScript.preProcessFile(source, true, true)
841
+ for (const reference of [
842
+ ...preprocessed.referencedFiles,
843
+ ...preprocessed.typeReferenceDirectives,
844
+ ]) {
845
+ if (reference.fileName.startsWith('.')) {
846
+ references.push({
847
+ start: reference.pos,
848
+ end: reference.end,
849
+ specifier: reference.fileName,
850
+ })
851
+ }
852
+ }
853
+
854
+ return references
855
+ .filter(
856
+ (reference, index, all) =>
857
+ all.findIndex(
858
+ (candidate) =>
859
+ candidate.start === reference.start &&
860
+ candidate.end === reference.end,
861
+ ) === index,
862
+ )
863
+ .sort((left, right) => left.start - right.start)
864
+ }
865
+
866
+ function createCollisionSafeIdentifier(
867
+ typeScript: TypeScriptModule,
868
+ sourceFile: SourceFile,
869
+ baseName: string,
870
+ ): string {
871
+ const identifiers = new Set<string>()
872
+ const visit = (node: import('typescript').Node) => {
873
+ if (typeScript.isIdentifier(node)) {
874
+ identifiers.add(node.text)
875
+ }
876
+ typeScript.forEachChild(node, visit)
877
+ }
878
+ visit(sourceFile)
879
+
880
+ let identifier = baseName
881
+ let suffix = 1
882
+ while (identifiers.has(identifier)) {
883
+ identifier = `${baseName}_${suffix}`
884
+ suffix += 1
885
+ }
886
+ return identifier
887
+ }
888
+
889
+ function isHeritageReference(
890
+ typeScript: TypeScriptModule,
891
+ identifier: Identifier,
892
+ ): boolean {
893
+ const parent = identifier.parent
894
+ return (
895
+ typeScript.isExpressionWithTypeArguments(parent) &&
896
+ parent.expression === identifier &&
897
+ typeScript.isHeritageClause(parent.parent)
898
+ )
899
+ }
900
+
901
+ function isUnboundBufferReference(
902
+ typeScript: TypeScriptModule,
903
+ checker: import('typescript').TypeChecker,
904
+ identifier: Identifier,
905
+ ): boolean {
906
+ const meaning = typeImportReferenceMeaning(typeScript, identifier)
907
+ return (
908
+ meaning !== undefined &&
909
+ checker.resolveName(identifier.text, identifier, meaning, false) ===
910
+ undefined
911
+ )
912
+ }
913
+
914
+ function typeImportReferenceMeaning(
915
+ typeScript: TypeScriptModule,
916
+ identifier: Identifier,
917
+ ): SymbolFlags | undefined {
918
+ let entityName: EntityName = identifier
919
+ while (
920
+ typeScript.isQualifiedName(entityName.parent) &&
921
+ entityName.parent.left === entityName
922
+ ) {
923
+ entityName = entityName.parent
924
+ }
925
+
926
+ const parent = entityName.parent
927
+ if (
928
+ typeScript.isTypeReferenceNode(parent) &&
929
+ parent.typeName === entityName
930
+ ) {
931
+ return typeScript.SymbolFlags.Type
932
+ }
933
+ if (typeScript.isTypeQueryNode(parent) && parent.exprName === entityName) {
934
+ return typeScript.SymbolFlags.Value
935
+ }
936
+ if (
937
+ typeScript.isExpressionWithTypeArguments(parent) &&
938
+ parent.expression === entityName &&
939
+ typeScript.isHeritageClause(parent.parent)
940
+ ) {
941
+ const heritageClause = parent.parent
942
+ const declaration = heritageClause.parent
943
+ return heritageClause.token === typeScript.SyntaxKind.ExtendsKeyword &&
944
+ (typeScript.isClassDeclaration(declaration) ||
945
+ typeScript.isClassExpression(declaration))
946
+ ? typeScript.SymbolFlags.Value
947
+ : typeScript.SymbolFlags.Type
948
+ }
949
+ }
950
+
951
+ function createDeclarationProgram(source: string): {
952
+ program: import('typescript').Program
953
+ sourceFile: SourceFile
954
+ } {
955
+ const typeScript = loadTypeScript()
956
+ const options: CompilerOptions = {
957
+ module: typeScript.ModuleKind.ESNext,
958
+ noLib: true,
959
+ noResolve: true,
960
+ skipLibCheck: true,
961
+ target: typeScript.ScriptTarget.Latest,
962
+ types: [],
963
+ }
964
+ const sourceFile = typeScript.createSourceFile(
965
+ IN_MEMORY_DECLARATION_FILE,
966
+ source,
967
+ options.target!,
968
+ true,
969
+ typeScript.ScriptKind.TS,
970
+ )
971
+ const host: CompilerHost = {
972
+ fileExists: (fileName) => fileName === IN_MEMORY_DECLARATION_FILE,
973
+ getCanonicalFileName: (fileName) => fileName,
974
+ getCurrentDirectory: () => '/',
975
+ getDefaultLibFileName: () => '',
976
+ getDirectories: () => [],
977
+ getNewLine: () => '\n',
978
+ getSourceFile: (fileName) =>
979
+ fileName === IN_MEMORY_DECLARATION_FILE ? sourceFile : undefined,
980
+ readFile: (fileName) =>
981
+ fileName === IN_MEMORY_DECLARATION_FILE ? source : undefined,
982
+ useCaseSensitiveFileNames: () => true,
983
+ writeFile: () => {},
984
+ }
985
+ const program = typeScript.createProgram({
986
+ rootNames: [IN_MEMORY_DECLARATION_FILE],
987
+ options,
988
+ host,
989
+ })
990
+ const diagnostics = program.getSyntacticDiagnostics(sourceFile)
991
+ if (diagnostics.length > 0) {
992
+ throwDeclarationDiagnostics(typeScript, sourceFile, diagnostics)
993
+ }
994
+ return { program, sourceFile }
995
+ }
996
+
997
+ function throwDeclarationDiagnostics(
998
+ typeScript: TypeScriptModule,
999
+ sourceFile: SourceFile,
1000
+ diagnostics: readonly Diagnostic[],
1001
+ ): never {
1002
+ const messages = diagnostics.slice(0, 3).map((diagnostic) => {
1003
+ const message = typeScript.flattenDiagnosticMessageText(
1004
+ diagnostic.messageText,
1005
+ '\n',
1006
+ )
1007
+ if (diagnostic.start === undefined) {
1008
+ return message
1009
+ }
1010
+ const { character, line } = sourceFile.getLineAndCharacterOfPosition(
1011
+ diagnostic.start,
1012
+ )
1013
+ return `${line + 1}:${character + 1} ${message}`
1014
+ })
1015
+ throw new Error(`Failed to parse declaration source:\n${messages.join('\n')}`)
1016
+ }
1017
+
1018
+ function decodeStructuralNewlines(source: string): string {
1019
+ let output = ''
1020
+ let index = 0
1021
+
1022
+ while (index < source.length) {
1023
+ const character = source[index]
1024
+ if (character === "'" || character === '"' || character === '`') {
1025
+ const quote = character
1026
+ output += character
1027
+ index += 1
1028
+ while (index < source.length) {
1029
+ const current = source[index]
1030
+ output += current
1031
+ index += 1
1032
+ if (current === '\\' && index < source.length) {
1033
+ output += source[index]
1034
+ index += 1
1035
+ } else if (current === quote) {
1036
+ break
1037
+ }
1038
+ }
1039
+ continue
1040
+ }
1041
+ if (character === '/' && source[index + 1] === '/') {
1042
+ const end = source.indexOf('\n', index + 2)
1043
+ if (end === -1) {
1044
+ output += source.slice(index)
1045
+ break
1046
+ }
1047
+ output += source.slice(index, end)
1048
+ index = end
1049
+ continue
1050
+ }
1051
+ if (character === '/' && source[index + 1] === '*') {
1052
+ const end = source.indexOf('*/', index + 2)
1053
+ if (end === -1) {
1054
+ output += source.slice(index)
1055
+ break
1056
+ }
1057
+ output += source.slice(index, end + 2)
1058
+ index = end + 2
1059
+ continue
1060
+ }
1061
+ if (character === '\\' && source[index + 1] === 'n') {
1062
+ output += '\n'
1063
+ index += 2
1064
+ continue
1065
+ }
1066
+ output += character
1067
+ index += 1
1068
+ }
1069
+ return output
1070
+ }
1071
+
1072
+ export function appendTypeImports(
1073
+ source: string,
1074
+ typeImports: TypeImport[],
1075
+ ): string {
1076
+ const typeScript = loadTypeScript()
1077
+ const parsed = parseDeclarationSource(source)
1078
+ const missingImports = typeImports.filter(
1079
+ (typeImport) => !hasNamedImport(parsed.statements, typeImport),
1080
+ )
1081
+ if (missingImports.length === 0) {
1082
+ return source
1083
+ }
1084
+
1085
+ const importsByModule = new Map<string, string[]>()
1086
+ for (const { module, name } of missingImports) {
1087
+ const names = importsByModule.get(module) ?? []
1088
+ names.push(name)
1089
+ importsByModule.set(module, names)
1090
+ }
1091
+ const newline = source.includes('\r\n') ? '\r\n' : '\n'
1092
+ const importSource = [...importsByModule]
1093
+ .sort(([left], [right]) => left.localeCompare(right))
1094
+ .map(
1095
+ ([module, names]) =>
1096
+ `import type { ${[...new Set(names)].sort().join(', ')} } from ${JSON.stringify(module)}`,
1097
+ )
1098
+ .join(newline)
1099
+ const importDeclarations = parsed.statements.filter(
1100
+ typeScript.isImportDeclaration,
1101
+ )
1102
+ const insertionOffset = importDeclarations.length
1103
+ ? endOfLine(source, importDeclarations.at(-1)!.end)
1104
+ : leadingDeclarationPreambleEnd(source, parsed)
1105
+
1106
+ return insertDeclarationSource(source, insertionOffset, importSource, newline)
1107
+ }
1108
+
1109
+ function appendAliasedImport(
1110
+ source: string,
1111
+ module: string,
1112
+ importedName: string,
1113
+ localName: string,
1114
+ ): string {
1115
+ const typeScript = loadTypeScript()
1116
+ const parsed = parseDeclarationSource(source)
1117
+ const newline = source.includes('\r\n') ? '\r\n' : '\n'
1118
+ const importDeclarations = parsed.statements.filter(
1119
+ typeScript.isImportDeclaration,
1120
+ )
1121
+ const insertionOffset = importDeclarations.length
1122
+ ? endOfLine(source, importDeclarations.at(-1)!.end)
1123
+ : leadingDeclarationPreambleEnd(source, parsed)
1124
+ return insertDeclarationSource(
1125
+ source,
1126
+ insertionOffset,
1127
+ `import { ${importedName} as ${localName} } from ${JSON.stringify(module)}`,
1128
+ newline,
1129
+ )
1130
+ }
1131
+
1132
+ export function removeNodeStreamWebTypeImports(source: string): string {
1133
+ const typeScript = loadTypeScript()
1134
+ const parsed = parseDeclarationSource(source)
1135
+ const removals: Array<{ start: number; end: number }> = []
1136
+
1137
+ for (const statement of parsed.statements) {
1138
+ if (
1139
+ !typeScript.isImportDeclaration(statement) ||
1140
+ !typeScript.isStringLiteral(statement.moduleSpecifier) ||
1141
+ statement.moduleSpecifier.text !== 'node:stream/web'
1142
+ ) {
1143
+ continue
1144
+ }
1145
+ const importClause = statement.importClause
1146
+ const namedBindings = importClause?.namedBindings
1147
+ const unaliasedNamedTypeImports =
1148
+ importClause !== undefined &&
1149
+ namedBindings !== undefined &&
1150
+ typeScript.isNamedImports(namedBindings) &&
1151
+ namedBindings.elements.length > 0 &&
1152
+ namedBindings.elements.every(
1153
+ (specifier) =>
1154
+ (importClause.isTypeOnly || specifier.isTypeOnly) &&
1155
+ specifier.propertyName === undefined,
1156
+ )
1157
+ if (!unaliasedNamedTypeImports) {
1158
+ throw new Error(
1159
+ 'Threadless declaration headers may only import unaliased types from node:stream/web so DOM globals can replace them',
1160
+ )
1161
+ }
1162
+ removals.push(
1163
+ expandToDeclarationLine(
1164
+ source,
1165
+ statement.getStart(parsed),
1166
+ statement.end,
1167
+ ),
1168
+ )
1169
+ }
1170
+
1171
+ let result = source
1172
+ for (const removal of removals.reverse()) {
1173
+ result = result.slice(0, removal.start) + result.slice(removal.end)
1174
+ }
1175
+ return result
1176
+ }
1177
+
1178
+ export function rewriteUnboundNodeGlobalTypeQueries(source: string): string {
1179
+ if (!source.includes('global')) {
1180
+ return source
1181
+ }
1182
+ const typeScript = loadTypeScript()
1183
+ const { program, sourceFile } = createDeclarationProgram(source)
1184
+ const checker = program.getTypeChecker()
1185
+ const replacements: Array<{ start: number; end: number }> = []
1186
+ const visit = (node: import('typescript').Node) => {
1187
+ if (
1188
+ typeScript.isIdentifier(node) &&
1189
+ node.text === 'global' &&
1190
+ typeScript.isTypeQueryNode(node.parent) &&
1191
+ node.parent.exprName === node &&
1192
+ checker.resolveName(
1193
+ node.text,
1194
+ node,
1195
+ typeScript.SymbolFlags.Value,
1196
+ false,
1197
+ ) === undefined
1198
+ ) {
1199
+ replacements.push({
1200
+ start: node.getStart(sourceFile),
1201
+ end: node.end,
1202
+ })
1203
+ }
1204
+ typeScript.forEachChild(node, visit)
1205
+ }
1206
+ visit(sourceFile)
1207
+
1208
+ let rewritten = source
1209
+ for (const replacement of replacements.reverse()) {
1210
+ rewritten =
1211
+ rewritten.slice(0, replacement.start) +
1212
+ 'globalThis' +
1213
+ rewritten.slice(replacement.end)
1214
+ }
1215
+ return rewritten
1216
+ }
1217
+
1218
+ function parseDeclarationSource(source: string) {
1219
+ const typeScript = loadTypeScript()
1220
+ const parsed = typeScript.createSourceFile(
1221
+ 'index.d.ts',
1222
+ source,
1223
+ typeScript.ScriptTarget.Latest,
1224
+ true,
1225
+ typeScript.ScriptKind.TS,
1226
+ )
1227
+ const diagnostics = (
1228
+ parsed as typeof parsed & {
1229
+ parseDiagnostics: readonly Diagnostic[]
1230
+ }
1231
+ ).parseDiagnostics
1232
+ if (diagnostics.length > 0) {
1233
+ throw new Error(
1234
+ `Failed to parse declaration source:\n${diagnostics
1235
+ .slice(0, 3)
1236
+ .map((diagnostic) =>
1237
+ typeScript.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
1238
+ )
1239
+ .join('\n')}`,
1240
+ )
1241
+ }
1242
+ return parsed
1243
+ }
1244
+
1245
+ function hasNamedImport(
1246
+ statements: NodeArray<Statement>,
1247
+ typeImport: TypeImport,
1248
+ ): boolean {
1249
+ const typeScript = loadTypeScript()
1250
+ return statements.some(
1251
+ (statement) =>
1252
+ typeScript.isImportDeclaration(statement) &&
1253
+ typeScript.isStringLiteral(statement.moduleSpecifier) &&
1254
+ statement.moduleSpecifier.text === typeImport.module &&
1255
+ statement.importClause?.namedBindings !== undefined &&
1256
+ typeScript.isNamedImports(statement.importClause.namedBindings) &&
1257
+ statement.importClause.namedBindings.elements.some(
1258
+ (specifier) => specifier.name.text === typeImport.name,
1259
+ ),
1260
+ )
1261
+ }
1262
+
1263
+ function leadingDeclarationPreambleEnd(
1264
+ source: string,
1265
+ parsed: ReturnType<typeof parseDeclarationSource>,
1266
+ ): number {
1267
+ const typeScript = loadTypeScript()
1268
+ const firstStatementStart =
1269
+ parsed.statements[0]?.getStart(parsed) ?? source.length
1270
+ const leadingComments =
1271
+ typeScript.getLeadingCommentRanges(
1272
+ source,
1273
+ parsed.statements[0]?.pos ?? 0,
1274
+ ) ?? []
1275
+ let offset = 0
1276
+
1277
+ for (const comment of leadingComments) {
1278
+ if (
1279
+ comment.end > firstStatementStart ||
1280
+ source.slice(offset, comment.pos).trim()
1281
+ ) {
1282
+ break
1283
+ }
1284
+ if (source.startsWith('/**', comment.pos)) {
1285
+ break
1286
+ }
1287
+ offset = endOfLine(source, comment.end)
1288
+ }
1289
+
1290
+ return offset
1291
+ }
1292
+
1293
+ let loadedTypeScript: TypeScriptModule | undefined
1294
+
1295
+ function loadTypeScript(): TypeScriptModule {
1296
+ loadedTypeScript ??= require('typescript') as TypeScriptModule
1297
+ return loadedTypeScript
1298
+ }
1299
+
1300
+ function endOfLine(source: string, offset: number): number {
1301
+ const newline = source.indexOf('\n', offset)
1302
+ return newline === -1 ? source.length : newline + 1
1303
+ }
1304
+
1305
+ function insertDeclarationSource(
1306
+ source: string,
1307
+ offset: number,
1308
+ inserted: string,
1309
+ newline: string,
1310
+ ): string {
1311
+ const before = source.slice(0, offset)
1312
+ const after = source.slice(offset)
1313
+ const beforeSeparator =
1314
+ before.length > 0 && !before.endsWith('\n') ? newline : ''
1315
+ const afterSeparator =
1316
+ after.length > 0 && !after.startsWith('\n') ? newline : ''
1317
+ const finalNewline = after.length === 0 ? newline : ''
1318
+ return `${before}${beforeSeparator}${inserted}${afterSeparator}${after}${finalNewline}`
1319
+ }
1320
+
1321
+ function expandToDeclarationLine(
1322
+ source: string,
1323
+ start: number,
1324
+ end: number,
1325
+ ): { start: number; end: number } {
1326
+ const lineStart = source.lastIndexOf('\n', start - 1) + 1
1327
+ const newline = source.indexOf('\n', end)
1328
+ const lineEnd = newline === -1 ? source.length : newline + 1
1329
+ const prefix = source.slice(lineStart, start)
1330
+ const suffix = source.slice(end, newline === -1 ? source.length : newline)
1331
+ return prefix.trim() || suffix.trim()
1332
+ ? { start, end }
1333
+ : { start: lineStart, end: lineEnd }
182
1334
  }
183
1335
 
184
1336
  async function readIntermediateTypeFile(file: string) {
@@ -190,14 +1342,16 @@ async function readIntermediateTypeFile(file: string) {
190
1342
  .map((line) => {
191
1343
  line = line.trim()
192
1344
  const parsed = JSON.parse(line) as TypeDefLine
193
- // Convert escaped newlines back to actual newlines in js_doc fields
194
1345
  if (parsed.js_doc) {
195
1346
  parsed.js_doc = parsed.js_doc.replace(/\\n/g, '\n')
196
1347
  }
197
- // Convert escaped newlines to actual newlines in def fields for struct/class/interface/type types
198
- // where \n represents method/field separators that should be actual newlines
199
1348
  if (parsed.def) {
200
- parsed.def = parsed.def.replace(/\\n/g, '\n')
1349
+ parsed.def = decodeStructuralNewlines(parsed.def)
1350
+ }
1351
+ if (parsed.def_with_type_import_markers) {
1352
+ parsed.def_with_type_import_markers = decodeStructuralNewlines(
1353
+ parsed.def_with_type_import_markers,
1354
+ )
201
1355
  }
202
1356
  return parsed
203
1357
  })
@@ -229,27 +1383,33 @@ function preprocessTypeDef(defs: TypeDefLine[]): Map<string, TypeDefLine[]> {
229
1383
  }
230
1384
 
231
1385
  const group = namespaceGrouped.get(namespace)!
1386
+ const classKey = `${namespace}\0${def.name}`
232
1387
 
233
1388
  if (def.kind === TypeDefKind.Struct) {
234
1389
  group.push(def)
235
- classDefs.set(def.name, def)
1390
+ classDefs.set(classKey, def)
236
1391
  } else if (def.kind === TypeDefKind.Extends) {
237
- const classDef = classDefs.get(def.name)
1392
+ const classDef = classDefs.get(classKey)
238
1393
  if (classDef) {
239
1394
  classDef.extends = def.def
240
1395
  }
241
1396
  } else if (def.kind === TypeDefKind.Impl) {
242
1397
  // merge `impl` into class definition
243
- const classDef = classDefs.get(def.name)
1398
+ const classDef = classDefs.get(classKey)
244
1399
  if (classDef) {
245
- if (classDef.def) {
246
- classDef.def += '\n'
247
- }
1400
+ const asyncIterator = parseAsyncGeneratorImpl(def.def)
1401
+ if (asyncIterator) {
1402
+ classDef.asyncIterator = asyncIterator
1403
+ } else {
1404
+ if (classDef.def) {
1405
+ classDef.def += '\n'
1406
+ }
248
1407
 
249
- classDef.def += def.def
250
- // Convert any remaining \n sequences in the merged def to actual newlines
251
- if (classDef.def) {
252
- classDef.def = classDef.def.replace(/\\n/g, '\n')
1408
+ classDef.def += def.def
1409
+ // Convert any remaining \n sequences in the merged def to actual newlines
1410
+ if (classDef.def) {
1411
+ classDef.def = classDef.def.replace(/\\n/g, '\n')
1412
+ }
253
1413
  }
254
1414
  }
255
1415
  } else {