@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.
- package/README.md +33 -0
- package/cli.mjs +9 -12
- package/dist/cli.js +9277 -1313
- package/dist/index.cjs +9266 -1302
- package/dist/index.d.cts +176 -158
- package/dist/index.d.ts +176 -158
- package/dist/index.js +9277 -1313
- package/docs/artifacts.md +33 -0
- package/docs/build.md +58 -0
- package/docs/create-npm-dirs.md +32 -0
- package/docs/new.md +39 -0
- package/docs/pre-publish.md +37 -0
- package/docs/rename.md +37 -0
- package/docs/universalize.md +31 -0
- package/docs/version.md +31 -0
- package/docs/wasi.md +161 -0
- package/package.json +11 -10
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +1019 -114
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/artifacts.spec.ts +17 -0
- package/src/api/__tests__/create-npm-dirs.spec.ts +14 -15
- package/src/api/__tests__/new.spec.ts +41 -5
- package/src/api/artifacts.ts +934 -108
- package/src/api/build.ts +1325 -194
- package/src/api/create-npm-dirs.ts +472 -101
- package/src/api/new.ts +354 -35
- package/src/api/pre-publish.ts +2845 -99
- package/src/api/rename.ts +1638 -124
- package/src/api/templates/js-binding.ts +216 -38
- package/src/api/templates/load-wasi-template.ts +1641 -111
- package/src/api/templates/wasi-worker-template.ts +32 -3
- package/src/commands/rename.ts +8 -3
- package/src/def/artifacts.ts +2 -2
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__snapshots__/target.spec.ts.md +2 -2
- package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
- package/src/utils/__tests__/metadata.spec.ts +43 -2
- package/src/utils/__tests__/misc.spec.ts +124 -3
- package/src/utils/__tests__/version.spec.ts +47 -1
- package/src/utils/config.ts +15 -2
- package/src/utils/metadata.ts +206 -7
- package/src/utils/misc.ts +6079 -0
- package/src/utils/target.ts +66 -8
- package/src/utils/typegen.ts +1223 -63
- package/src/utils/version.ts +96 -0
package/src/utils/typegen.ts
CHANGED
|
@@ -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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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${
|
|
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
|
|
138
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
421
|
+
default:
|
|
422
|
+
break
|
|
157
423
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
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(
|
|
1390
|
+
classDefs.set(classKey, def)
|
|
236
1391
|
} else if (def.kind === TypeDefKind.Extends) {
|
|
237
|
-
const classDef = classDefs.get(
|
|
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(
|
|
1398
|
+
const classDef = classDefs.get(classKey)
|
|
244
1399
|
if (classDef) {
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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 {
|