@napi-rs/cli 3.9.1 → 3.10.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 +17 -4
- package/dist/cli.js +1490 -178
- package/dist/index.cjs +1490 -178
- package/dist/index.d.cts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +1490 -178
- package/docs/wasi.md +318 -4
- package/package.json +3 -3
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +3963 -9
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build-regressions.spec.ts +210 -3
- package/src/api/__tests__/build.spec.ts +2139 -3
- package/src/api/__tests__/create-npm-dirs.spec.ts +101 -0
- package/src/api/__tests__/pre-publish.spec.ts +153 -1
- package/src/api/__tests__/templates.spec.ts +1247 -0
- package/src/api/build.ts +637 -31
- package/src/api/create-npm-dirs.ts +21 -9
- package/src/api/pre-publish.ts +29 -1
- package/src/api/templates/binding-target.ts +176 -0
- package/src/api/templates/index.ts +1 -0
- package/src/api/templates/js-binding.ts +48 -3
- package/src/api/templates/load-wasi-template.ts +641 -41
- package/src/utils/__tests__/reconciliation.spec.ts +676 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/misc.ts +347 -74
- package/src/utils/typegen.ts +367 -12
package/src/utils/typegen.ts
CHANGED
|
@@ -5,9 +5,11 @@ import { sortBy } from 'es-toolkit'
|
|
|
5
5
|
import type {
|
|
6
6
|
CompilerHost,
|
|
7
7
|
CompilerOptions,
|
|
8
|
+
Declaration,
|
|
8
9
|
Diagnostic,
|
|
9
10
|
EntityName,
|
|
10
11
|
Identifier,
|
|
12
|
+
Node,
|
|
11
13
|
NodeArray,
|
|
12
14
|
SourceFile,
|
|
13
15
|
Statement,
|
|
@@ -1136,10 +1138,19 @@ function typeImportReferenceMeaning(
|
|
|
1136
1138
|
}
|
|
1137
1139
|
}
|
|
1138
1140
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1141
|
+
/**
|
|
1142
|
+
* A program over one already-parsed declaration file and nothing else.
|
|
1143
|
+
*
|
|
1144
|
+
* `noLib` and `noResolve` are the point, not an optimization: the answers a
|
|
1145
|
+
* caller takes from this program have to depend on the source alone, never on
|
|
1146
|
+
* what happens to sit next to the output directory. Nothing outside the file
|
|
1147
|
+
* is loaded, so an unresolved import stays unresolved rather than resolving
|
|
1148
|
+
* differently from one machine to the next.
|
|
1149
|
+
*/
|
|
1150
|
+
function programOverDeclarationSource(
|
|
1151
|
+
sourceFile: SourceFile,
|
|
1152
|
+
source: string,
|
|
1153
|
+
): import('typescript').Program {
|
|
1143
1154
|
const typeScript = loadTypeScript()
|
|
1144
1155
|
const options: CompilerOptions = {
|
|
1145
1156
|
module: typeScript.ModuleKind.ESNext,
|
|
@@ -1149,13 +1160,6 @@ function createDeclarationProgram(source: string): {
|
|
|
1149
1160
|
target: typeScript.ScriptTarget.Latest,
|
|
1150
1161
|
types: [],
|
|
1151
1162
|
}
|
|
1152
|
-
const sourceFile = typeScript.createSourceFile(
|
|
1153
|
-
IN_MEMORY_DECLARATION_FILE,
|
|
1154
|
-
source,
|
|
1155
|
-
options.target!,
|
|
1156
|
-
true,
|
|
1157
|
-
typeScript.ScriptKind.TS,
|
|
1158
|
-
)
|
|
1159
1163
|
const host: CompilerHost = {
|
|
1160
1164
|
fileExists: (fileName) => fileName === IN_MEMORY_DECLARATION_FILE,
|
|
1161
1165
|
getCanonicalFileName: (fileName) => fileName,
|
|
@@ -1170,11 +1174,20 @@ function createDeclarationProgram(source: string): {
|
|
|
1170
1174
|
useCaseSensitiveFileNames: () => true,
|
|
1171
1175
|
writeFile: () => {},
|
|
1172
1176
|
}
|
|
1173
|
-
|
|
1177
|
+
return typeScript.createProgram({
|
|
1174
1178
|
rootNames: [IN_MEMORY_DECLARATION_FILE],
|
|
1175
1179
|
options,
|
|
1176
1180
|
host,
|
|
1177
1181
|
})
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function createDeclarationProgram(source: string): {
|
|
1185
|
+
program: import('typescript').Program
|
|
1186
|
+
sourceFile: SourceFile
|
|
1187
|
+
} {
|
|
1188
|
+
const typeScript = loadTypeScript()
|
|
1189
|
+
const sourceFile = parseDeclarationFile(source)
|
|
1190
|
+
const program = programOverDeclarationSource(sourceFile, source)
|
|
1178
1191
|
const diagnostics = program.getSyntacticDiagnostics(sourceFile)
|
|
1179
1192
|
if (diagnostics.length > 0) {
|
|
1180
1193
|
throwDeclarationDiagnostics(typeScript, sourceFile, diagnostics)
|
|
@@ -1182,6 +1195,24 @@ function createDeclarationProgram(source: string): {
|
|
|
1182
1195
|
return { program, sourceFile }
|
|
1183
1196
|
}
|
|
1184
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* Parse a declaration source without judging it. Unlike
|
|
1200
|
+
* {@link parseDeclarationSource}, a syntax error somewhere else in the file is
|
|
1201
|
+
* no reason to refuse to answer a question about it: this runs over whatever a
|
|
1202
|
+
* project put in its `--dts-header` and over declaration files kept from
|
|
1203
|
+
* earlier builds.
|
|
1204
|
+
*/
|
|
1205
|
+
function parseDeclarationFile(source: string): SourceFile {
|
|
1206
|
+
const typeScript = loadTypeScript()
|
|
1207
|
+
return typeScript.createSourceFile(
|
|
1208
|
+
IN_MEMORY_DECLARATION_FILE,
|
|
1209
|
+
source,
|
|
1210
|
+
typeScript.ScriptTarget.Latest,
|
|
1211
|
+
true,
|
|
1212
|
+
typeScript.ScriptKind.TS,
|
|
1213
|
+
)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1185
1216
|
function throwDeclarationDiagnostics(
|
|
1186
1217
|
typeScript: TypeScriptModule,
|
|
1187
1218
|
sourceFile: SourceFile,
|
|
@@ -1403,6 +1434,330 @@ export function rewriteUnboundNodeGlobalTypeQueries(source: string): string {
|
|
|
1403
1434
|
return rewritten
|
|
1404
1435
|
}
|
|
1405
1436
|
|
|
1437
|
+
/**
|
|
1438
|
+
* One top-level `export … const|let|var <name>` found by
|
|
1439
|
+
* {@link scanExportedName}.
|
|
1440
|
+
*/
|
|
1441
|
+
export interface ExportedVariableDeclaration {
|
|
1442
|
+
/**
|
|
1443
|
+
* Where the declaration block starts: the doc comment written directly above
|
|
1444
|
+
* the statement when there is one, so replacing the span swaps the comment
|
|
1445
|
+
* with it instead of stranding it.
|
|
1446
|
+
*/
|
|
1447
|
+
start: number
|
|
1448
|
+
/**
|
|
1449
|
+
* Just past the statement, the statement's own `;` included and trailing
|
|
1450
|
+
* trivia excluded. A caller that replaces `[start, end)` therefore has to
|
|
1451
|
+
* put that terminator back, or whatever followed on the same line runs
|
|
1452
|
+
* straight into the replacement.
|
|
1453
|
+
*/
|
|
1454
|
+
end: number
|
|
1455
|
+
/**
|
|
1456
|
+
* Where this declarator starts: the name, without the `export declare const`
|
|
1457
|
+
* in front of it.
|
|
1458
|
+
*/
|
|
1459
|
+
declaratorStart: number
|
|
1460
|
+
/** Just past this declarator, before any `,` that separates it from a sibling. */
|
|
1461
|
+
declaratorEnd: number
|
|
1462
|
+
/**
|
|
1463
|
+
* How many names the statement declares in all. More than one and
|
|
1464
|
+
* `[start, end)` covers names besides this one, so replacing that span would
|
|
1465
|
+
* delete them.
|
|
1466
|
+
*/
|
|
1467
|
+
declaratorCount: number
|
|
1468
|
+
/** The type annotation as written, or `undefined` when there is none. */
|
|
1469
|
+
type?: string
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
/** What {@link scanExportedName} reads out of one declaration source. */
|
|
1473
|
+
export interface ExportedNameScan {
|
|
1474
|
+
/**
|
|
1475
|
+
* Whether the file exports by assignment (`export = x`) at the top level,
|
|
1476
|
+
* which is what a build without `napi-derive`'s `type-def` feature emits.
|
|
1477
|
+
* Such a file cannot carry a named export at all.
|
|
1478
|
+
*
|
|
1479
|
+
* `export default x` parses as the same node and does not count: only
|
|
1480
|
+
* `isExportEquals` does.
|
|
1481
|
+
*/
|
|
1482
|
+
exportsByAssignment: boolean
|
|
1483
|
+
/**
|
|
1484
|
+
* Whether this source already binds `name` in a way that leaves no room for
|
|
1485
|
+
* an added `export declare const name` — as an export, or as a top-level
|
|
1486
|
+
* declaration the const would redeclare.
|
|
1487
|
+
*
|
|
1488
|
+
* Not simply "is it exported": TypeScript merges a value with a declaration
|
|
1489
|
+
* that lives only in type space, and a caller that backed off there would
|
|
1490
|
+
* take away typed access to a real runtime export (TS2693 at every value
|
|
1491
|
+
* use) while preventing no collision at all. So an exported `type`,
|
|
1492
|
+
* `interface` or non-instantiated namespace is *not* an owner.
|
|
1493
|
+
*
|
|
1494
|
+
* Which leaves the question of what "value space" means for every shape a
|
|
1495
|
+
* declaration file can take, and that is TypeScript's question to answer,
|
|
1496
|
+
* not this CLI's — see {@link scanExportedName}. An owner is an export the
|
|
1497
|
+
* checker gives `SymbolFlags.Value` (a variable, `function`, `class`,
|
|
1498
|
+
* `enum`, an instantiated namespace — instantiated by the binder's rules,
|
|
1499
|
+
* which count an aliased member such as `export import x = …` that hand-
|
|
1500
|
+
* written recursion over the body kept missing) or `SymbolFlags.Alias`
|
|
1501
|
+
* (every `export { … }` clause, its `export type { … }` and
|
|
1502
|
+
* `export * as name from '…'` forms, and `export import name = …`; an alias
|
|
1503
|
+
* collides with a local declaration of the name however it is spelled —
|
|
1504
|
+
* TS2323, TS2440).
|
|
1505
|
+
*
|
|
1506
|
+
* The same two flags settle a binding that is not exported at all: a
|
|
1507
|
+
* declaration file that is not a module has no export table, and its
|
|
1508
|
+
* top-level `declare const name` is a global the generated export would
|
|
1509
|
+
* redeclare; inside a module an `import name = …` the file keeps to itself
|
|
1510
|
+
* conflicts too. A top-level `type` or `interface` does not, in either
|
|
1511
|
+
* place — and neither does a name the file puts in the *global* scope
|
|
1512
|
+
* rather than its own, through a `declare global { … }` member or the UMD
|
|
1513
|
+
* name of `export as namespace name`: the added export shadows it instead
|
|
1514
|
+
* of colliding with it.
|
|
1515
|
+
*
|
|
1516
|
+
* `export default` binds `default` rather than a name, and
|
|
1517
|
+
* `export * from '…'` is left unresolved on purpose, so it names nothing
|
|
1518
|
+
* here; neither counts.
|
|
1519
|
+
*/
|
|
1520
|
+
ownsName: boolean
|
|
1521
|
+
/**
|
|
1522
|
+
* Whether the file was bound to answer {@link ownsName}, or whether the
|
|
1523
|
+
* source was settled without it.
|
|
1524
|
+
*
|
|
1525
|
+
* Binding is skipped only for a source that can spell no such name at all —
|
|
1526
|
+
* see {@link scanExportedName}. Nothing in this CLI branches on it: it is
|
|
1527
|
+
* here so a test can hold that shortcut in place without timing anything,
|
|
1528
|
+
* which is the only way to notice it has quietly stopped applying.
|
|
1529
|
+
*/
|
|
1530
|
+
checked: boolean
|
|
1531
|
+
/**
|
|
1532
|
+
* The exported variable statements that declare `name`, in source order.
|
|
1533
|
+
* Only these carry a span a caller can rewrite; the other export forms above
|
|
1534
|
+
* have no declaration here to replace.
|
|
1535
|
+
*/
|
|
1536
|
+
declarations: ExportedVariableDeclaration[]
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
/**
|
|
1540
|
+
* How a declaration source exports `name`, and whether it exports by
|
|
1541
|
+
* assignment instead.
|
|
1542
|
+
*
|
|
1543
|
+
* Parsed rather than pattern-matched, because a declaration file spells both
|
|
1544
|
+
* in places that are not an export of it. `napi-derive` copies a crate's
|
|
1545
|
+
* `js_doc` through verbatim, so a doc comment can name either; a
|
|
1546
|
+
* `--dts-header` may carry a commented-out example of the declaration or of
|
|
1547
|
+
* `export = binding`, or a member of a `declare namespace` / `declare module`
|
|
1548
|
+
* block, which is an export of that block and not of the file. Importing any
|
|
1549
|
+
* of those yields TS2305, and a commented-out `export =` is not an export
|
|
1550
|
+
* assignment at all.
|
|
1551
|
+
*
|
|
1552
|
+
* Ownership is decided by TypeScript's own binder rather than by a list of
|
|
1553
|
+
* shapes this CLI enumerates. Every attempt at the list missed something —
|
|
1554
|
+
* a namespace export clause, an `export import` member, an alias nested one
|
|
1555
|
+
* level deeper — because the question is really "what does this file export,
|
|
1556
|
+
* and in which declaration space", and only the checker knows. So the file is
|
|
1557
|
+
* bound and {@link ExportedNameScan.ownsName} is read off the export symbol's
|
|
1558
|
+
* flags.
|
|
1559
|
+
*
|
|
1560
|
+
* The syntax is still what finds the rewritable declaration and its span, and
|
|
1561
|
+
* one parse serves both: the program is built over the source file parsed
|
|
1562
|
+
* here, so binding costs no second parse.
|
|
1563
|
+
*/
|
|
1564
|
+
export function scanExportedName(
|
|
1565
|
+
source: string,
|
|
1566
|
+
name: string,
|
|
1567
|
+
): ExportedNameScan {
|
|
1568
|
+
const typeScript = loadTypeScript()
|
|
1569
|
+
const sourceFile = parseDeclarationFile(source)
|
|
1570
|
+
const declarations: ExportedVariableDeclaration[] = []
|
|
1571
|
+
let exportsByAssignment = false
|
|
1572
|
+
for (const statement of sourceFile.statements) {
|
|
1573
|
+
if (typeScript.isExportAssignment(statement)) {
|
|
1574
|
+
// `export = x`. `export default x` is the same node without the flag,
|
|
1575
|
+
// and it binds `default`, never `name`.
|
|
1576
|
+
exportsByAssignment ||= statement.isExportEquals === true
|
|
1577
|
+
continue
|
|
1578
|
+
}
|
|
1579
|
+
// Only an exported variable statement leaves a declaration a caller can
|
|
1580
|
+
// rewrite in place. Every other owner is found through the checker below,
|
|
1581
|
+
// which has no span to offer and needs none.
|
|
1582
|
+
if (
|
|
1583
|
+
!typeScript.isVariableStatement(statement) ||
|
|
1584
|
+
!statement.modifiers?.some(
|
|
1585
|
+
(modifier) => modifier.kind === typeScript.SyntaxKind.ExportKeyword,
|
|
1586
|
+
)
|
|
1587
|
+
) {
|
|
1588
|
+
continue
|
|
1589
|
+
}
|
|
1590
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1591
|
+
if (
|
|
1592
|
+
!typeScript.isIdentifier(declaration.name) ||
|
|
1593
|
+
declaration.name.text !== name
|
|
1594
|
+
) {
|
|
1595
|
+
continue
|
|
1596
|
+
}
|
|
1597
|
+
declarations.push({
|
|
1598
|
+
start: declarationBlockStart(source, sourceFile, statement),
|
|
1599
|
+
end: statement.end,
|
|
1600
|
+
declaratorStart: declaration.getStart(sourceFile),
|
|
1601
|
+
declaratorEnd: declaration.end,
|
|
1602
|
+
declaratorCount: statement.declarationList.declarations.length,
|
|
1603
|
+
type: declaration.type?.getText(sourceFile),
|
|
1604
|
+
})
|
|
1605
|
+
break
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const { ownsName, checked } = sourceBindsName(sourceFile, source, name)
|
|
1609
|
+
return { exportsByAssignment, ownsName, checked, declarations }
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Whether anything in this source already binds `name` where a generated
|
|
1614
|
+
* `export declare const name` would land — as an export of the file, or as a
|
|
1615
|
+
* declaration at its top level that the const would redeclare — and whether
|
|
1616
|
+
* the file had to be bound to find out.
|
|
1617
|
+
*
|
|
1618
|
+
* Both questions are the checker's, and both are answered off the same
|
|
1619
|
+
* program. See {@link ExportedNameScan.ownsName}.
|
|
1620
|
+
*/
|
|
1621
|
+
function sourceBindsName(
|
|
1622
|
+
sourceFile: SourceFile,
|
|
1623
|
+
source: string,
|
|
1624
|
+
name: string,
|
|
1625
|
+
): { ownsName: boolean; checked: boolean } {
|
|
1626
|
+
const typeScript = loadTypeScript()
|
|
1627
|
+
// A binding of `name` in this file has to spell it here — but it need not
|
|
1628
|
+
// spell it in the characters the name is made of. An identifier may write
|
|
1629
|
+
// any of them as a `\uXXXX` or `\u{…}` escape; a string-literal export name
|
|
1630
|
+
// (`export { x as "…" }`) may use `\xXX` as well, and may be broken across
|
|
1631
|
+
// lines with a backslash-newline continuation. TypeScript resolves every one
|
|
1632
|
+
// of those to the same name where a text search sees nothing. Enumerating
|
|
1633
|
+
// the escapes is the game that was already lost once, so the shortcut asks
|
|
1634
|
+
// for less: a source carrying neither the name nor a backslash *anywhere*
|
|
1635
|
+
// cannot spell it, and that is what almost every header is. Everything else
|
|
1636
|
+
// goes to the checker, which has always been able to say. The other form a
|
|
1637
|
+
// text search would miss is `export * from '…'`, which the program below
|
|
1638
|
+
// leaves unresolved by design, so nothing is skipped there either.
|
|
1639
|
+
if (!source.includes(name) && !source.includes('\\')) {
|
|
1640
|
+
return { ownsName: false, checked: false }
|
|
1641
|
+
}
|
|
1642
|
+
// A value or an alias is what a `const` of the same name cannot be written
|
|
1643
|
+
// beside; a type-only declaration is what it merges with.
|
|
1644
|
+
const meaning = typeScript.SymbolFlags.Alias | typeScript.SymbolFlags.Value
|
|
1645
|
+
const checker = programOverDeclarationSource(
|
|
1646
|
+
sourceFile,
|
|
1647
|
+
source,
|
|
1648
|
+
).getTypeChecker()
|
|
1649
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile)
|
|
1650
|
+
const exported =
|
|
1651
|
+
moduleSymbol === undefined
|
|
1652
|
+
? undefined
|
|
1653
|
+
: checker
|
|
1654
|
+
.getExportsOfModule(moduleSymbol)
|
|
1655
|
+
.find((symbol) => symbol.name === name)
|
|
1656
|
+
if (exported !== undefined && (exported.flags & meaning) !== 0) {
|
|
1657
|
+
return { ownsName: true, checked: true }
|
|
1658
|
+
}
|
|
1659
|
+
// Not every binding that collides is an export. A declaration file that is
|
|
1660
|
+
// not a module has no export table at all, and its top-level `declare const`
|
|
1661
|
+
// is a global the generated export would redeclare (TS2451, TS2395). Inside
|
|
1662
|
+
// a module, an `import name = …` kept to the file is likewise no export and
|
|
1663
|
+
// still conflicts (TS2440). Ambient declaration files put most top-level
|
|
1664
|
+
// declarations in the export table by themselves, so these are the leftovers
|
|
1665
|
+
// rather than the common case — but they are the ones a rule written around
|
|
1666
|
+
// exports alone would miss.
|
|
1667
|
+
//
|
|
1668
|
+
// Scoped at the source file, so a binding nested inside a namespace or a
|
|
1669
|
+
// function body is correctly none of this file's business, and filtered to
|
|
1670
|
+
// the declarations that bind in this file's own scope — see
|
|
1671
|
+
// {@link declaresInFileScope}.
|
|
1672
|
+
return {
|
|
1673
|
+
ownsName: checker
|
|
1674
|
+
.getSymbolsInScope(sourceFile, meaning)
|
|
1675
|
+
.some(
|
|
1676
|
+
(symbol) =>
|
|
1677
|
+
symbol.name === name &&
|
|
1678
|
+
symbol.declarations?.some((declaration) =>
|
|
1679
|
+
declaresInFileScope(typeScript, declaration, sourceFile),
|
|
1680
|
+
) === true,
|
|
1681
|
+
),
|
|
1682
|
+
checked: true,
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Whether `declaration` binds its name in `sourceFile`'s own scope — the scope
|
|
1688
|
+
* a generated `export declare const` would land in.
|
|
1689
|
+
*
|
|
1690
|
+
* `getSymbolsInScope` answers what is *visible* at a location, and the global
|
|
1691
|
+
* scope is visible everywhere, so a name a file declares into that scope comes
|
|
1692
|
+
* back from it while colliding with nothing the file itself adds. Written here
|
|
1693
|
+
* is therefore not enough; written here *and at this file's top level* is the
|
|
1694
|
+
* question:
|
|
1695
|
+
*
|
|
1696
|
+
* - A `declare global { … }` member is a global, reached through a
|
|
1697
|
+
* `ModuleDeclaration`. A module-scoped `export declare const` of the same
|
|
1698
|
+
* name shadows it rather than redeclaring it. The members of an ambient
|
|
1699
|
+
* `declare module '…'` block and of a `declare namespace` belong to that
|
|
1700
|
+
* module or namespace instead of to the file; the checker already keeps
|
|
1701
|
+
* those out of scope here, and the same walk covers them without depending
|
|
1702
|
+
* on that.
|
|
1703
|
+
* - `export as namespace name` declares a UMD global for script consumers,
|
|
1704
|
+
* not a binding in the file. Its `NamespaceExportDeclaration` is a child of
|
|
1705
|
+
* the source file and so passes the walk, and it too sits happily beside an
|
|
1706
|
+
* export of the same name.
|
|
1707
|
+
*
|
|
1708
|
+
* Everything else that reaches here does bind at the top level: a script
|
|
1709
|
+
* file's own `declare const` (appending the export makes the file a module and
|
|
1710
|
+
* puts both in the same scope), and a module's unexported `import name = …`.
|
|
1711
|
+
*/
|
|
1712
|
+
function declaresInFileScope(
|
|
1713
|
+
typeScript: TypeScriptModule,
|
|
1714
|
+
declaration: Declaration,
|
|
1715
|
+
sourceFile: SourceFile,
|
|
1716
|
+
): boolean {
|
|
1717
|
+
if (
|
|
1718
|
+
declaration.getSourceFile() !== sourceFile ||
|
|
1719
|
+
typeScript.isNamespaceExportDeclaration(declaration)
|
|
1720
|
+
) {
|
|
1721
|
+
return false
|
|
1722
|
+
}
|
|
1723
|
+
for (
|
|
1724
|
+
let node: Node | undefined = declaration.parent;
|
|
1725
|
+
node !== undefined && !typeScript.isSourceFile(node);
|
|
1726
|
+
node = node.parent
|
|
1727
|
+
) {
|
|
1728
|
+
if (typeScript.isModuleDeclaration(node)) {
|
|
1729
|
+
return false
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return true
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Where a statement's block starts for replacement purposes: the doc comment
|
|
1737
|
+
* directly above it when one is there, otherwise the statement itself. Only a
|
|
1738
|
+
* `/** … *\/` comment separated from the statement by nothing but whitespace
|
|
1739
|
+
* counts, so a license banner further up is never swallowed.
|
|
1740
|
+
*/
|
|
1741
|
+
function declarationBlockStart(
|
|
1742
|
+
source: string,
|
|
1743
|
+
sourceFile: SourceFile,
|
|
1744
|
+
statement: Statement,
|
|
1745
|
+
): number {
|
|
1746
|
+
const typeScript = loadTypeScript()
|
|
1747
|
+
const start = statement.getStart(sourceFile)
|
|
1748
|
+
const comments =
|
|
1749
|
+
typeScript.getLeadingCommentRanges(source, statement.getFullStart()) ?? []
|
|
1750
|
+
for (const comment of comments) {
|
|
1751
|
+
if (
|
|
1752
|
+
source.startsWith('/**', comment.pos) &&
|
|
1753
|
+
source.slice(comment.end, start).trim() === ''
|
|
1754
|
+
) {
|
|
1755
|
+
return comment.pos
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return start
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1406
1761
|
function parseDeclarationSource(source: string) {
|
|
1407
1762
|
const typeScript = loadTypeScript()
|
|
1408
1763
|
const parsed = typeScript.createSourceFile(
|