@napi-rs/cli 3.10.2 → 3.10.4
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/dist/cli.js +215 -19
- package/dist/index.cjs +219 -23
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +215 -19
- package/package.json +1 -1
- package/src/api/__tests__/build.spec.ts +257 -0
- package/src/api/build.ts +19 -5
- package/src/utils/__tests__/misc.spec.ts +339 -0
- package/src/utils/misc.ts +178 -36
- package/src/utils/typegen.ts +221 -2
package/src/utils/misc.ts
CHANGED
|
@@ -4353,7 +4353,83 @@ export async function retireFailedSnapshotLeftover(
|
|
|
4353
4353
|
return { outcome: 'kept' }
|
|
4354
4354
|
}
|
|
4355
4355
|
|
|
4356
|
-
|
|
4356
|
+
interface FileSystemTransactionSourceDrift {
|
|
4357
|
+
/**
|
|
4358
|
+
* Differences that make the copy an unfaithful image of the opened file, or
|
|
4359
|
+
* that mean the path no longer names it. Always fatal.
|
|
4360
|
+
*/
|
|
4361
|
+
hard: string[]
|
|
4362
|
+
/**
|
|
4363
|
+
* Differences a re-copy can settle: an inode attribute re-stamp on an
|
|
4364
|
+
* otherwise identical file, or content that has not stopped moving yet.
|
|
4365
|
+
* Retryable.
|
|
4366
|
+
*/
|
|
4367
|
+
soft: string[]
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4370
|
+
/**
|
|
4371
|
+
* Compare the source as it was before the copy against the pinned descriptor
|
|
4372
|
+
* and the path after it, and name every field that moved.
|
|
4373
|
+
*
|
|
4374
|
+
* One message used to stand for eight independent conditions, which made a
|
|
4375
|
+
* failure from a platform the author cannot reach (a FreeBSD CI VM, say)
|
|
4376
|
+
* impossible to diagnose: a truncated read, a chmod, a timestamp re-stamp and a
|
|
4377
|
+
* replaced path all read identically.
|
|
4378
|
+
*/
|
|
4379
|
+
function describeFileSystemTransactionSourceDrift(
|
|
4380
|
+
before: BigIntStats,
|
|
4381
|
+
after: BigIntStats,
|
|
4382
|
+
bytesRead: number,
|
|
4383
|
+
pathStats: BigIntStats | undefined,
|
|
4384
|
+
): FileSystemTransactionSourceDrift {
|
|
4385
|
+
const hard: string[] = []
|
|
4386
|
+
const soft: string[] = []
|
|
4387
|
+
const note = (
|
|
4388
|
+
into: string[],
|
|
4389
|
+
field: string,
|
|
4390
|
+
beforeValue: bigint,
|
|
4391
|
+
afterValue: bigint,
|
|
4392
|
+
) => {
|
|
4393
|
+
if (beforeValue !== afterValue) {
|
|
4394
|
+
into.push(`${field} ${beforeValue} -> ${afterValue}`)
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
note(hard, 'dev', before.dev, after.dev)
|
|
4398
|
+
note(hard, 'ino', before.ino, after.ino)
|
|
4399
|
+
note(hard, 'size', before.size, after.size)
|
|
4400
|
+
if (after.size !== BigInt(bytesRead)) {
|
|
4401
|
+
hard.push(`bytesRead ${bytesRead} != size ${after.size}`)
|
|
4402
|
+
}
|
|
4403
|
+
note(soft, 'mode', before.mode, after.mode)
|
|
4404
|
+
note(soft, 'mtimeNs', before.mtimeNs, after.mtimeNs)
|
|
4405
|
+
note(soft, 'ctimeNs', before.ctimeNs, after.ctimeNs)
|
|
4406
|
+
if (pathStats === undefined) {
|
|
4407
|
+
hard.push('path identity vanished')
|
|
4408
|
+
} else if (!pathStats.isFile()) {
|
|
4409
|
+
hard.push('path identity is no longer a regular file')
|
|
4410
|
+
} else if (!statIdentitiesMatch(before, pathStats)) {
|
|
4411
|
+
hard.push(
|
|
4412
|
+
`path identity ${before.dev}/${before.ino} -> ${pathStats.dev}/${pathStats.ino}`,
|
|
4413
|
+
)
|
|
4414
|
+
}
|
|
4415
|
+
return { hard, soft }
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
/**
|
|
4419
|
+
* How many times a snapshot re-copies a source that moved underneath it before
|
|
4420
|
+
* giving up. See the drift classification comment inside
|
|
4421
|
+
* {@link snapshotFileSystemTransactionInput}.
|
|
4422
|
+
*/
|
|
4423
|
+
const fileSystemTransactionSnapshotAttempts = 3
|
|
4424
|
+
|
|
4425
|
+
/**
|
|
4426
|
+
* Exported for unit tests; not part of the supported `@napi-rs/cli` surface.
|
|
4427
|
+
*
|
|
4428
|
+
* @param onAfterCopy test seam invoked with the 1-based attempt number after
|
|
4429
|
+
* each copy pass and before the post-copy re-verification, so a test can mutate
|
|
4430
|
+
* the source deterministically instead of racing a timer against the copy.
|
|
4431
|
+
*/
|
|
4432
|
+
export async function snapshotFileSystemTransactionInput(
|
|
4357
4433
|
source: string,
|
|
4358
4434
|
destination: string,
|
|
4359
4435
|
mode?: number,
|
|
@@ -4364,6 +4440,7 @@ async function snapshotFileSystemTransactionInput(
|
|
|
4364
4440
|
recordDestinationIdentity?: (
|
|
4365
4441
|
identity: FileSystemTransactionFileIdentity,
|
|
4366
4442
|
) => Promise<void>,
|
|
4443
|
+
onAfterCopy?: (attempt: number) => Promise<void>,
|
|
4367
4444
|
): Promise<FileSystemTransactionJournalFileState> {
|
|
4368
4445
|
// All stats in this flow are bigint so every path-vs-handle continuity check
|
|
4369
4446
|
// below compares exact 64-bit identity, never the lossy Number dev/ino: a
|
|
@@ -4416,7 +4493,6 @@ async function snapshotFileSystemTransactionInput(
|
|
|
4416
4493
|
'changed before it could be snapshotted',
|
|
4417
4494
|
)
|
|
4418
4495
|
}
|
|
4419
|
-
const finalMode = mode ?? Number(sourceStats.mode & 0o7777n)
|
|
4420
4496
|
if (createDestinationParent) {
|
|
4421
4497
|
await mkdir(dirname(destination), { recursive: true })
|
|
4422
4498
|
}
|
|
@@ -4463,50 +4539,116 @@ async function snapshotFileSystemTransactionInput(
|
|
|
4463
4539
|
}
|
|
4464
4540
|
await recordDestinationIdentity(destinationIdentity)
|
|
4465
4541
|
}
|
|
4466
|
-
const hash = createHash('sha256')
|
|
4467
4542
|
const buffer = Buffer.allocUnsafe(64 * 1024)
|
|
4543
|
+
// A snapshot's job is to record a *consistent* copy, so the source is
|
|
4544
|
+
// re-verified after the copy against the stats taken before it. Drift
|
|
4545
|
+
// splits in two:
|
|
4546
|
+
//
|
|
4547
|
+
// hard — dev, ino, size, bytes-read, path identity. The copy is not a
|
|
4548
|
+
// faithful image of the file that was opened, or the path no longer
|
|
4549
|
+
// names it. Never tolerated.
|
|
4550
|
+
// soft — mode, mtimeNs, ctimeNs on an otherwise identical inode. This
|
|
4551
|
+
// is a metadata re-stamp: a kernel, a permission normalization or a
|
|
4552
|
+
// stray `utimes` moves these without touching a byte, and it is a
|
|
4553
|
+
// routine thing to happen to a file this process itself just wrote
|
|
4554
|
+
// into its own staging directory. Failing a release build over it is
|
|
4555
|
+
// wrong — the recorded sha256 plus dev/ino/size already guarantee the
|
|
4556
|
+
// content — so redo the snapshot instead.
|
|
4557
|
+
//
|
|
4558
|
+
// Metadata alone cannot decide that, though, so a retry also has to
|
|
4559
|
+
// reproduce the bytes. Each attempt rebases the baseline on what it just
|
|
4560
|
+
// observed, and a timestamp only moves as far as its filesystem can
|
|
4561
|
+
// express: where the clock is coarse, or where several writes land in one
|
|
4562
|
+
// tick, a writer that rewrites the file in place at the same length moves
|
|
4563
|
+
// no field this function compares. Metadata would read as settled while
|
|
4564
|
+
// the copy mixed two versions of the file, and the hash of that mixture
|
|
4565
|
+
// would be recorded as the authoritative one — nothing downstream ever
|
|
4566
|
+
// reads the source again to notice. So an attempt that follows drift is
|
|
4567
|
+
// accepted only when it hashes to exactly what the attempt before it
|
|
4568
|
+
// hashed to: two passes in a row agreeing on the content is the evidence
|
|
4569
|
+
// the source is settled. A hash that keeps moving is named as
|
|
4570
|
+
// `contentHash` drift and spends the attempt bound like any other.
|
|
4571
|
+
//
|
|
4572
|
+
// The source descriptor is deliberately *not* re-opened between attempts
|
|
4573
|
+
// — it pins the inode validated on the way in, so a retry can never adopt
|
|
4574
|
+
// a successor swapped into the path.
|
|
4575
|
+
let baselineStats = sourceStats
|
|
4576
|
+
let sourceHash = ''
|
|
4577
|
+
let previousHash: string | undefined
|
|
4468
4578
|
let position = 0
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4579
|
+
let drift: FileSystemTransactionSourceDrift | undefined
|
|
4580
|
+
for (
|
|
4581
|
+
let attempt = 1;
|
|
4582
|
+
attempt <= fileSystemTransactionSnapshotAttempts;
|
|
4583
|
+
attempt++
|
|
4584
|
+
) {
|
|
4585
|
+
if (attempt > 1) {
|
|
4586
|
+
// Discard the previous attempt's bytes. The destination inode is
|
|
4587
|
+
// transaction-owned and unpublished, and its identity — already
|
|
4588
|
+
// recorded above — is unaffected by a truncate.
|
|
4589
|
+
await destinationHandle.truncate(0)
|
|
4590
|
+
}
|
|
4591
|
+
const hash = createHash('sha256')
|
|
4592
|
+
position = 0
|
|
4593
|
+
while (true) {
|
|
4594
|
+
const { bytesRead } = await sourceHandle.read(
|
|
4595
|
+
buffer,
|
|
4596
|
+
0,
|
|
4597
|
+
buffer.length,
|
|
4598
|
+
position,
|
|
4599
|
+
)
|
|
4600
|
+
if (bytesRead === 0) {
|
|
4601
|
+
break
|
|
4602
|
+
}
|
|
4603
|
+
hash.update(buffer.subarray(0, bytesRead))
|
|
4604
|
+
let written = 0
|
|
4605
|
+
while (written < bytesRead) {
|
|
4606
|
+
const result = await destinationHandle.write(
|
|
4607
|
+
buffer,
|
|
4608
|
+
written,
|
|
4609
|
+
bytesRead - written,
|
|
4610
|
+
position + written,
|
|
4611
|
+
)
|
|
4612
|
+
written += result.bytesWritten
|
|
4613
|
+
}
|
|
4614
|
+
position += bytesRead
|
|
4615
|
+
}
|
|
4616
|
+
sourceHash = hash.digest('hex')
|
|
4617
|
+
await onAfterCopy?.(attempt)
|
|
4618
|
+
const [finalSourceStats, finalPathStats] = await Promise.all([
|
|
4619
|
+
sourceHandle.stat({ bigint: true }),
|
|
4620
|
+
lstatIfExists(source, { bigint: true }),
|
|
4621
|
+
])
|
|
4622
|
+
drift = describeFileSystemTransactionSourceDrift(
|
|
4623
|
+
baselineStats,
|
|
4624
|
+
finalSourceStats,
|
|
4474
4625
|
position,
|
|
4626
|
+
finalPathStats,
|
|
4475
4627
|
)
|
|
4476
|
-
|
|
4628
|
+
// Rebase on what was just observed either way: on success it is the
|
|
4629
|
+
// settled metadata the journal should record, and on soft drift it is
|
|
4630
|
+
// the baseline the next attempt has to hold still against.
|
|
4631
|
+
baselineStats = finalSourceStats
|
|
4632
|
+
if (previousHash !== undefined && previousHash !== sourceHash) {
|
|
4633
|
+
drift.soft.push(`contentHash ${previousHash} -> ${sourceHash}`)
|
|
4634
|
+
}
|
|
4635
|
+
previousHash = sourceHash
|
|
4636
|
+
if (drift.hard.length > 0 || drift.soft.length === 0) {
|
|
4477
4637
|
break
|
|
4478
4638
|
}
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
bytesRead - written,
|
|
4486
|
-
position + written,
|
|
4639
|
+
}
|
|
4640
|
+
if (drift && (drift.hard.length > 0 || drift.soft.length > 0)) {
|
|
4641
|
+
const reasons = [...drift.hard, ...drift.soft]
|
|
4642
|
+
if (drift.hard.length === 0) {
|
|
4643
|
+
reasons.push(
|
|
4644
|
+
`still drifting after ${fileSystemTransactionSnapshotAttempts} snapshot attempts`,
|
|
4487
4645
|
)
|
|
4488
|
-
written += result.bytesWritten
|
|
4489
4646
|
}
|
|
4490
|
-
position += bytesRead
|
|
4491
|
-
}
|
|
4492
|
-
const [finalSourceStats, finalPathStats] = await Promise.all([
|
|
4493
|
-
sourceHandle.stat({ bigint: true }),
|
|
4494
|
-
lstatIfExists(source, { bigint: true }),
|
|
4495
|
-
])
|
|
4496
|
-
if (
|
|
4497
|
-
!statIdentitiesMatch(sourceStats, finalSourceStats) ||
|
|
4498
|
-
finalSourceStats.size !== sourceStats.size ||
|
|
4499
|
-
finalSourceStats.size !== BigInt(position) ||
|
|
4500
|
-
finalSourceStats.mode !== sourceStats.mode ||
|
|
4501
|
-
finalSourceStats.mtimeNs !== sourceStats.mtimeNs ||
|
|
4502
|
-
finalSourceStats.ctimeNs !== sourceStats.ctimeNs ||
|
|
4503
|
-
finalPathStats?.isFile() !== true ||
|
|
4504
|
-
!statIdentitiesMatch(sourceStats, finalPathStats)
|
|
4505
|
-
) {
|
|
4506
4647
|
throw new Error(
|
|
4507
|
-
`Filesystem transaction source changed while it was snapshotted: ${source}`,
|
|
4648
|
+
`Filesystem transaction source changed while it was snapshotted: ${source} (${reasons.join('; ')})`,
|
|
4508
4649
|
)
|
|
4509
4650
|
}
|
|
4651
|
+
const finalMode = mode ?? Number(baselineStats.mode & 0o7777n)
|
|
4510
4652
|
await applyFileSystemTransactionMode(destinationHandle, destinationMode)
|
|
4511
4653
|
await destinationHandle.sync()
|
|
4512
4654
|
const finalDestinationStats = await destinationHandle.stat({
|
|
@@ -4538,7 +4680,7 @@ async function snapshotFileSystemTransactionInput(
|
|
|
4538
4680
|
const sourceIdentityStats = await sourceHandle.stat({ bigint: true })
|
|
4539
4681
|
return {
|
|
4540
4682
|
dev: String(sourceIdentityStats.dev),
|
|
4541
|
-
hash:
|
|
4683
|
+
hash: sourceHash,
|
|
4542
4684
|
ino: String(sourceIdentityStats.ino),
|
|
4543
4685
|
mode: finalMode,
|
|
4544
4686
|
}
|
package/src/utils/typegen.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'
|
|
2
|
-
import { dirname, relative, resolve } from 'node:path'
|
|
2
|
+
import { dirname, parse, relative, resolve } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { sortBy } from 'es-toolkit'
|
|
5
5
|
import type {
|
|
@@ -971,6 +971,220 @@ export function collectRelativeDeclarationSpecifiers(source: string): string[] {
|
|
|
971
971
|
]
|
|
972
972
|
}
|
|
973
973
|
|
|
974
|
+
/**
|
|
975
|
+
* The first construct in `source` a CommonJS declaration file cannot carry,
|
|
976
|
+
* or `undefined` when copying `source` into a `.d.cts` is honest.
|
|
977
|
+
*
|
|
978
|
+
* A `.d.cts` is read under `require` module-resolution mode, so a declaration
|
|
979
|
+
* that means one thing in an ESM file can mean another — or be a syntax error
|
|
980
|
+
* — once it lands there. The generated typedef needs none of those forms: its
|
|
981
|
+
* imports name packages or `node:` builtins, and everything it exports is a
|
|
982
|
+
* named `export declare`. A `--dts-header` can carry any declaration a module
|
|
983
|
+
* package can write, though, and silently copying one that resolves
|
|
984
|
+
* differently produces a `.d.cts` describing a module the loader is not.
|
|
985
|
+
* Those sources keep refusing, with the offending construct named, so the
|
|
986
|
+
* header can be fixed or `--dts` pointed at a `.d.cts` source instead.
|
|
987
|
+
*
|
|
988
|
+
* What refuses, and why:
|
|
989
|
+
*
|
|
990
|
+
* - `export default`, in any of its spellings — `export default x`,
|
|
991
|
+
* `export default class`/`function`/`interface`/`enum`/`namespace`,
|
|
992
|
+
* `export { x as default }`, `export * as default`. In a `.d.cts` `default`
|
|
993
|
+
* is `module.exports.default` — a property the generated loader never
|
|
994
|
+
* assigns — not the whole-callable shape a default export is in ESM. Inside
|
|
995
|
+
* a `declare module` block `export default` describes that module's own
|
|
996
|
+
* shape and carries over honestly, so only top-level defaults refuse.
|
|
997
|
+
* - A relative specifier that can land on a mode-dependent declaration:
|
|
998
|
+
* `.js`-family spellings (`.js`, `.jsx`, `.ts`, `.tsx`) or extensionless
|
|
999
|
+
* ones, which resolve `.d.cts`-first under `require` mode and
|
|
1000
|
+
* `.d.mts`-first under `import` mode — possibly a different file, and
|
|
1001
|
+
* certainly a different interpretation of the file they find. Explicit
|
|
1002
|
+
* module-format extensions (`.mjs`, `.mts`, `.cjs`, `.cts`) and non-script
|
|
1003
|
+
* targets (`.json`, `.wasm`, `.node`, assets) resolve identically either
|
|
1004
|
+
* way. Bare specifiers (`buffer`, `node:stream/web`, package names) are
|
|
1005
|
+
* allowed — a dual-condition package's `types` entry can still diverge
|
|
1006
|
+
* (`import` vs `require` condition), but refusing them would defeat the
|
|
1007
|
+
* derivation: the generated typedef's own imports are all bare.
|
|
1008
|
+
* - `with { … }` attributes on a runtime `import`/`export … from` statement:
|
|
1009
|
+
* TS2856 in a `.cts` file. Type-only statements may keep theirs, and a
|
|
1010
|
+
* `resolution-mode` attribute pins the specifier to one mode — either way
|
|
1011
|
+
* the same mode `.d.cts` or the source's own — so it is honest again.
|
|
1012
|
+
*
|
|
1013
|
+
* Everything else — `export declare`, interfaces, enums, namespaces,
|
|
1014
|
+
* `export =`, `declare global`, `/// <reference path>` — reads the same on
|
|
1015
|
+
* both sides. One deliberate gap: `import('…')` types inside JSDoc comments
|
|
1016
|
+
* are not scanned — they need a separate JSDoc parse for a source class the
|
|
1017
|
+
* generator never emits.
|
|
1018
|
+
*/
|
|
1019
|
+
export function commonJsDeclarationBarrier(source: string): string | undefined {
|
|
1020
|
+
const typeScript = loadTypeScript()
|
|
1021
|
+
const sourceFile = parseDeclarationFile(source)
|
|
1022
|
+
|
|
1023
|
+
const specifierBarrier = (
|
|
1024
|
+
literal: import('typescript').StringLiteralLike,
|
|
1025
|
+
): string | undefined => {
|
|
1026
|
+
if (!literal.text.startsWith('.')) {
|
|
1027
|
+
return undefined
|
|
1028
|
+
}
|
|
1029
|
+
const extension = parse(literal.text).ext.toLowerCase()
|
|
1030
|
+
if (
|
|
1031
|
+
extension !== '' &&
|
|
1032
|
+
!['.js', '.jsx', '.ts', '.tsx'].includes(extension)
|
|
1033
|
+
) {
|
|
1034
|
+
return undefined
|
|
1035
|
+
}
|
|
1036
|
+
return `a relative '${literal.text}' specifier`
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const hasResolutionMode = (
|
|
1040
|
+
attributes: import('typescript').ImportAttributes | undefined,
|
|
1041
|
+
) =>
|
|
1042
|
+
attributes?.elements.some(
|
|
1043
|
+
(element) =>
|
|
1044
|
+
element.name.text === 'resolution-mode' &&
|
|
1045
|
+
typeScript.isStringLiteral(element.value) &&
|
|
1046
|
+
(element.value.text === 'import' || element.value.text === 'require'),
|
|
1047
|
+
) === true
|
|
1048
|
+
|
|
1049
|
+
const hasDefaultModifier = (node: Node) =>
|
|
1050
|
+
typeScript.canHaveModifiers(node) === true &&
|
|
1051
|
+
typeScript
|
|
1052
|
+
.getModifiers(node)
|
|
1053
|
+
?.some(
|
|
1054
|
+
(modifier) => modifier.kind === typeScript.SyntaxKind.DefaultKeyword,
|
|
1055
|
+
) === true
|
|
1056
|
+
|
|
1057
|
+
const isTypeOnlyStatement = (
|
|
1058
|
+
node:
|
|
1059
|
+
| import('typescript').ImportDeclaration
|
|
1060
|
+
| import('typescript').ExportDeclaration,
|
|
1061
|
+
) =>
|
|
1062
|
+
typeScript.isImportDeclaration(node)
|
|
1063
|
+
? node.importClause?.isTypeOnly === true
|
|
1064
|
+
: node.isTypeOnly === true
|
|
1065
|
+
|
|
1066
|
+
let barrier: string | undefined
|
|
1067
|
+
const visit = (node: Node, insideModuleDeclaration: boolean): void => {
|
|
1068
|
+
if (barrier !== undefined) {
|
|
1069
|
+
return
|
|
1070
|
+
}
|
|
1071
|
+
if (!insideModuleDeclaration && hasDefaultModifier(node)) {
|
|
1072
|
+
barrier = 'an `export default` declaration'
|
|
1073
|
+
return
|
|
1074
|
+
}
|
|
1075
|
+
if (typeScript.isModuleDeclaration(node)) {
|
|
1076
|
+
if (typeScript.isStringLiteralLike(node.name)) {
|
|
1077
|
+
barrier = specifierBarrier(node.name)
|
|
1078
|
+
}
|
|
1079
|
+
if (barrier === undefined) {
|
|
1080
|
+
// A `declare module`/`declare global` block's statements describe that
|
|
1081
|
+
// module's own shape, so a `default` export inside one is honest; its
|
|
1082
|
+
// specifiers still resolve relative to this file.
|
|
1083
|
+
typeScript.forEachChild(node, (child) => visit(child, true))
|
|
1084
|
+
}
|
|
1085
|
+
return
|
|
1086
|
+
}
|
|
1087
|
+
if (
|
|
1088
|
+
typeScript.isImportDeclaration(node) ||
|
|
1089
|
+
typeScript.isExportDeclaration(node)
|
|
1090
|
+
) {
|
|
1091
|
+
if (node.attributes !== undefined && !isTypeOnlyStatement(node)) {
|
|
1092
|
+
barrier = '`with` attributes on a runtime module statement'
|
|
1093
|
+
return
|
|
1094
|
+
}
|
|
1095
|
+
if (
|
|
1096
|
+
node.moduleSpecifier !== undefined &&
|
|
1097
|
+
typeScript.isStringLiteralLike(node.moduleSpecifier) &&
|
|
1098
|
+
!hasResolutionMode(node.attributes)
|
|
1099
|
+
) {
|
|
1100
|
+
barrier = specifierBarrier(node.moduleSpecifier)
|
|
1101
|
+
if (barrier !== undefined) {
|
|
1102
|
+
return
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (
|
|
1106
|
+
!insideModuleDeclaration &&
|
|
1107
|
+
typeScript.isExportDeclaration(node) &&
|
|
1108
|
+
node.exportClause !== undefined &&
|
|
1109
|
+
((typeScript.isNamedExports(node.exportClause) &&
|
|
1110
|
+
node.exportClause.elements.some(
|
|
1111
|
+
(element) => element.name.text === 'default',
|
|
1112
|
+
)) ||
|
|
1113
|
+
(typeScript.isNamespaceExport(node.exportClause) &&
|
|
1114
|
+
node.exportClause.name.text === 'default'))
|
|
1115
|
+
) {
|
|
1116
|
+
barrier = 'a `default` re-export'
|
|
1117
|
+
}
|
|
1118
|
+
return
|
|
1119
|
+
}
|
|
1120
|
+
if (typeScript.isImportEqualsDeclaration(node)) {
|
|
1121
|
+
if (
|
|
1122
|
+
typeScript.isExternalModuleReference(node.moduleReference) &&
|
|
1123
|
+
typeScript.isStringLiteralLike(node.moduleReference.expression)
|
|
1124
|
+
) {
|
|
1125
|
+
barrier = specifierBarrier(node.moduleReference.expression)
|
|
1126
|
+
}
|
|
1127
|
+
return
|
|
1128
|
+
}
|
|
1129
|
+
if (typeScript.isExportAssignment(node)) {
|
|
1130
|
+
if (node.isExportEquals !== true && !insideModuleDeclaration) {
|
|
1131
|
+
barrier = 'an `export default` declaration'
|
|
1132
|
+
}
|
|
1133
|
+
return
|
|
1134
|
+
}
|
|
1135
|
+
if (typeScript.isImportTypeNode(node)) {
|
|
1136
|
+
if (
|
|
1137
|
+
typeScript.isLiteralTypeNode(node.argument) &&
|
|
1138
|
+
typeScript.isStringLiteralLike(node.argument.literal) &&
|
|
1139
|
+
!hasResolutionMode(node.attributes)
|
|
1140
|
+
) {
|
|
1141
|
+
barrier = specifierBarrier(node.argument.literal)
|
|
1142
|
+
}
|
|
1143
|
+
// `import('pkg').T<import('./x.js').U>` nests another specifier in the
|
|
1144
|
+
// type arguments — keep descending.
|
|
1145
|
+
if (barrier === undefined) {
|
|
1146
|
+
typeScript.forEachChild(node, (child) =>
|
|
1147
|
+
visit(child, insideModuleDeclaration),
|
|
1148
|
+
)
|
|
1149
|
+
}
|
|
1150
|
+
return
|
|
1151
|
+
}
|
|
1152
|
+
if (
|
|
1153
|
+
typeScript.isCallExpression(node) &&
|
|
1154
|
+
node.arguments.length >= 1 &&
|
|
1155
|
+
typeScript.isStringLiteralLike(node.arguments[0]) &&
|
|
1156
|
+
(node.expression.kind === typeScript.SyntaxKind.ImportKeyword ||
|
|
1157
|
+
(typeScript.isIdentifier(node.expression) &&
|
|
1158
|
+
node.expression.text === 'require'))
|
|
1159
|
+
) {
|
|
1160
|
+
barrier = specifierBarrier(node.arguments[0])
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1163
|
+
typeScript.forEachChild(node, (child) =>
|
|
1164
|
+
visit(child, insideModuleDeclaration),
|
|
1165
|
+
)
|
|
1166
|
+
}
|
|
1167
|
+
for (const statement of sourceFile.statements) {
|
|
1168
|
+
visit(statement, false)
|
|
1169
|
+
}
|
|
1170
|
+
if (barrier === undefined) {
|
|
1171
|
+
// `/// <reference types>` resolves through module resolution — a relative
|
|
1172
|
+
// name can diverge the same way a relative specifier can. `path`
|
|
1173
|
+
// references are file-path resolution and carry over honestly.
|
|
1174
|
+
for (const reference of typeScript.preProcessFile(source, true, true)
|
|
1175
|
+
.typeReferenceDirectives) {
|
|
1176
|
+
if (
|
|
1177
|
+
reference.fileName.startsWith('.') &&
|
|
1178
|
+
reference.resolutionMode === undefined
|
|
1179
|
+
) {
|
|
1180
|
+
barrier = `a relative '/// <reference types="${reference.fileName}" />' directive`
|
|
1181
|
+
break
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return barrier
|
|
1186
|
+
}
|
|
1187
|
+
|
|
974
1188
|
function collectRelativeDeclarationSpecifierReferences(
|
|
975
1189
|
source: string,
|
|
976
1190
|
): DeclarationSpecifierReference[] {
|
|
@@ -1007,6 +1221,11 @@ function collectRelativeDeclarationSpecifierReferences(
|
|
|
1007
1221
|
typeScript.isStringLiteralLike(node.argument.literal)
|
|
1008
1222
|
) {
|
|
1009
1223
|
addStringLiteral(node.argument.literal)
|
|
1224
|
+
} else if (
|
|
1225
|
+
typeScript.isModuleDeclaration(node) &&
|
|
1226
|
+
typeScript.isStringLiteralLike(node.name)
|
|
1227
|
+
) {
|
|
1228
|
+
addStringLiteral(node.name)
|
|
1010
1229
|
} else if (
|
|
1011
1230
|
typeScript.isExternalModuleReference(node) &&
|
|
1012
1231
|
node.expression &&
|
|
@@ -1015,7 +1234,7 @@ function collectRelativeDeclarationSpecifierReferences(
|
|
|
1015
1234
|
addStringLiteral(node.expression)
|
|
1016
1235
|
} else if (
|
|
1017
1236
|
typeScript.isCallExpression(node) &&
|
|
1018
|
-
node.arguments.length
|
|
1237
|
+
node.arguments.length >= 1 &&
|
|
1019
1238
|
typeScript.isStringLiteralLike(node.arguments[0]) &&
|
|
1020
1239
|
(node.expression.kind === typeScript.SyntaxKind.ImportKeyword ||
|
|
1021
1240
|
(typeScript.isIdentifier(node.expression) &&
|