@napi-rs/cli 3.10.3 → 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 +138 -4
- package/dist/index.cjs +142 -8
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +138 -4
- package/package.json +1 -1
- package/src/api/__tests__/build.spec.ts +257 -0
- package/src/api/build.ts +19 -5
- package/src/utils/typegen.ts +221 -2
package/package.json
CHANGED
|
@@ -831,6 +831,263 @@ test('a fresh WASI declaration narrows the inherited root union', async (t) => {
|
|
|
831
831
|
const ROOT_BINDING_TARGET_DECLARATION =
|
|
832
832
|
"export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'"
|
|
833
833
|
|
|
834
|
+
test('an ESM root declaration derives the WASI declaration unless it cannot cross', async (t) => {
|
|
835
|
+
const { tmpDir, projectDir, typeDefDir } = t.context
|
|
836
|
+
await writeFile(
|
|
837
|
+
join(typeDefDir, 'sum.type'),
|
|
838
|
+
'{"kind":"fn","name":"sum","def":"function sum(a: number, b: number): number"}\n',
|
|
839
|
+
)
|
|
840
|
+
const { dts } = await generateTypeDef({
|
|
841
|
+
typeDefDir,
|
|
842
|
+
cwd: projectDir,
|
|
843
|
+
declareBindingTarget: true,
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
// A `"type": "module"` package's `index.d.ts` is an ESM declaration, and so
|
|
847
|
+
// is an explicit `.d.mts` — but the generated typedef carries nothing a
|
|
848
|
+
// `.d.cts` cannot re-declare, so the WASI declaration derives verbatim.
|
|
849
|
+
// (napi-rs#3531: an unconditional refusal failed every WASI build in a
|
|
850
|
+
// module package, including `--esm` ones.)
|
|
851
|
+
const sourcePath = join(projectDir, 'index.d.ts')
|
|
852
|
+
const destinationPath = join(projectDir, 'pkg.wasi.d.cts')
|
|
853
|
+
for (const esmSourcePath of [sourcePath, join(projectDir, 'index.d.mts')]) {
|
|
854
|
+
t.is(
|
|
855
|
+
prepareWasiBindingTypeDef(
|
|
856
|
+
dts,
|
|
857
|
+
esmSourcePath,
|
|
858
|
+
destinationPath,
|
|
859
|
+
true,
|
|
860
|
+
'module',
|
|
861
|
+
),
|
|
862
|
+
dts,
|
|
863
|
+
)
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// The threadless flavor still gets its node-global rewrites on the way, and
|
|
867
|
+
// what it emits is valid TypeScript beside the ESM source it came from.
|
|
868
|
+
const threadless = prepareWasiBindingTypeDef(
|
|
869
|
+
dts,
|
|
870
|
+
sourcePath,
|
|
871
|
+
destinationPath,
|
|
872
|
+
false,
|
|
873
|
+
'module',
|
|
874
|
+
)
|
|
875
|
+
const codes = await semanticDiagnosticCodes(join(tmpDir, 'esm-derived'), {
|
|
876
|
+
'index.d.ts': dts,
|
|
877
|
+
'pkg.wasi.d.cts': threadless,
|
|
878
|
+
})
|
|
879
|
+
t.deepEqual(codes['pkg.wasi.d.cts'], [])
|
|
880
|
+
|
|
881
|
+
// Only the constructs a CommonJS declaration cannot carry still refuse —
|
|
882
|
+
// each one named in the error, from `.d.ts` and `.d.mts` sources alike.
|
|
883
|
+
const hazards: Array<[what: string, source: string, barrier: string]> = [
|
|
884
|
+
[
|
|
885
|
+
'an `export default` declaration',
|
|
886
|
+
'export declare function sum(a: number): number\nexport default sum\n',
|
|
887
|
+
'an `export default` declaration',
|
|
888
|
+
],
|
|
889
|
+
[
|
|
890
|
+
'a default class declaration',
|
|
891
|
+
'export default class Sum {}\n',
|
|
892
|
+
'an `export default` declaration',
|
|
893
|
+
],
|
|
894
|
+
[
|
|
895
|
+
'a default function declaration',
|
|
896
|
+
'export default function sum(a: number): number\n',
|
|
897
|
+
'an `export default` declaration',
|
|
898
|
+
],
|
|
899
|
+
[
|
|
900
|
+
'a default interface declaration',
|
|
901
|
+
'export default interface Sum { a: number }\n',
|
|
902
|
+
'an `export default` declaration',
|
|
903
|
+
],
|
|
904
|
+
[
|
|
905
|
+
'a default namespace declaration',
|
|
906
|
+
'export default namespace Sum { const a: number }\n',
|
|
907
|
+
'an `export default` declaration',
|
|
908
|
+
],
|
|
909
|
+
[
|
|
910
|
+
'a `default` re-export',
|
|
911
|
+
'declare const sum: number\nexport { sum as default }\n',
|
|
912
|
+
'a `default` re-export',
|
|
913
|
+
],
|
|
914
|
+
[
|
|
915
|
+
'a `default` re-export from a package',
|
|
916
|
+
"export { sum as default } from 'sums'\n",
|
|
917
|
+
'a `default` re-export',
|
|
918
|
+
],
|
|
919
|
+
[
|
|
920
|
+
'a type-only `default` re-export',
|
|
921
|
+
'type Sum = number\nexport type { Sum as default }\n',
|
|
922
|
+
'a `default` re-export',
|
|
923
|
+
],
|
|
924
|
+
[
|
|
925
|
+
'a `default` namespace re-export',
|
|
926
|
+
"export * as default from 'sums'\n",
|
|
927
|
+
'a `default` re-export',
|
|
928
|
+
],
|
|
929
|
+
[
|
|
930
|
+
'a relative import specifier',
|
|
931
|
+
"import { sum } from './sum.js'\nexport declare function f(): typeof sum\n",
|
|
932
|
+
"a relative './sum.js' specifier",
|
|
933
|
+
],
|
|
934
|
+
[
|
|
935
|
+
'an extensionless relative specifier',
|
|
936
|
+
"import { sum } from './sum'\nexport declare function f(): typeof sum\n",
|
|
937
|
+
"a relative './sum' specifier",
|
|
938
|
+
],
|
|
939
|
+
[
|
|
940
|
+
'a relative export specifier',
|
|
941
|
+
"export { sum } from './sum.js'\n",
|
|
942
|
+
"a relative './sum.js' specifier",
|
|
943
|
+
],
|
|
944
|
+
[
|
|
945
|
+
'a relative export-star specifier',
|
|
946
|
+
"export * from './sums.js'\n",
|
|
947
|
+
"a relative './sums.js' specifier",
|
|
948
|
+
],
|
|
949
|
+
[
|
|
950
|
+
'a relative import type specifier',
|
|
951
|
+
"import type { Sum } from './sum.js'\nexport declare function f(s: Sum): void\n",
|
|
952
|
+
"a relative './sum.js' specifier",
|
|
953
|
+
],
|
|
954
|
+
[
|
|
955
|
+
'a relative import() type',
|
|
956
|
+
"export declare function f(): import('./sum.js').Sum\n",
|
|
957
|
+
"a relative './sum.js' specifier",
|
|
958
|
+
],
|
|
959
|
+
[
|
|
960
|
+
'a relative import-equals specifier',
|
|
961
|
+
"import sum = require('./sum.js')\nexport declare function f(): typeof sum\n",
|
|
962
|
+
"a relative './sum.js' specifier",
|
|
963
|
+
],
|
|
964
|
+
[
|
|
965
|
+
'a relative ambient module specifier',
|
|
966
|
+
"declare module './sum.js' { export const sum: number }\n",
|
|
967
|
+
"a relative './sum.js' specifier",
|
|
968
|
+
],
|
|
969
|
+
[
|
|
970
|
+
'a relative types reference',
|
|
971
|
+
"/// <reference types='./sum' />\nexport declare const sum: number\n",
|
|
972
|
+
'a relative \'/// <reference types="./sum" />\' directive',
|
|
973
|
+
],
|
|
974
|
+
[
|
|
975
|
+
'attributes on a runtime statement',
|
|
976
|
+
"import sum from './sum.json' with { type: 'json' }\nexport declare const s: typeof sum\n",
|
|
977
|
+
'`with` attributes on a runtime module statement',
|
|
978
|
+
],
|
|
979
|
+
]
|
|
980
|
+
const esmSources: Array<
|
|
981
|
+
[path: string, packageType: 'module' | 'commonjs' | undefined]
|
|
982
|
+
> = [
|
|
983
|
+
[sourcePath, 'module'],
|
|
984
|
+
[join(projectDir, 'index.d.mts'), undefined],
|
|
985
|
+
]
|
|
986
|
+
for (const [what, source, barrier] of hazards) {
|
|
987
|
+
for (const [esmSourcePath, packageType] of esmSources) {
|
|
988
|
+
const error = t.throws(
|
|
989
|
+
() =>
|
|
990
|
+
prepareWasiBindingTypeDef(
|
|
991
|
+
source,
|
|
992
|
+
esmSourcePath,
|
|
993
|
+
destinationPath,
|
|
994
|
+
true,
|
|
995
|
+
packageType,
|
|
996
|
+
),
|
|
997
|
+
{ instanceOf: Error },
|
|
998
|
+
)
|
|
999
|
+
t.true(
|
|
1000
|
+
error?.message.includes(barrier),
|
|
1001
|
+
`${what} names the offending construct, got: ${error?.message}`,
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Everything else a module package can write crosses honestly.
|
|
1007
|
+
const honest: Record<string, string> = {
|
|
1008
|
+
'bare and node: imports':
|
|
1009
|
+
"import { Buffer } from 'buffer'\nimport type { ReadableStream } from 'node:stream/web'\nexport declare function f(b: Buffer, s: ReadableStream): void\n",
|
|
1010
|
+
'.mjs specifiers':
|
|
1011
|
+
"import { sum } from './sum.mjs'\nexport declare function f(): typeof sum\n",
|
|
1012
|
+
'.cjs specifiers':
|
|
1013
|
+
"import { sum } from './sum.cjs'\nexport declare function f(): typeof sum\n",
|
|
1014
|
+
'an export assignment':
|
|
1015
|
+
'declare const binding: { sum(a: number): number }\nexport = binding\n',
|
|
1016
|
+
'a declare global block':
|
|
1017
|
+
'declare global { namespace Legacy { const v: number } }\nexport {}\n',
|
|
1018
|
+
'a bare ambient module':
|
|
1019
|
+
"declare module 'legacy' { export const sum: number }\n",
|
|
1020
|
+
'a .mjs ambient module':
|
|
1021
|
+
"declare module './sum.mjs' { export const sum: number }\n",
|
|
1022
|
+
'a default inside an ambient module':
|
|
1023
|
+
"declare module 'legacy' { const sum: number\nexport default sum }\n",
|
|
1024
|
+
'a type-only import with resolution-mode':
|
|
1025
|
+
"import type { Sum } from './sum.js' with { 'resolution-mode': 'import' }\nexport declare function f(s: Sum): void\n",
|
|
1026
|
+
'a type-only import pinned to require mode':
|
|
1027
|
+
"import type { Sum } from './sum.js' with { 'resolution-mode': 'require' }\nexport declare function f(s: Sum): void\n",
|
|
1028
|
+
'a type-only export with resolution-mode':
|
|
1029
|
+
"export type { Sum } from './sum.js' with { 'resolution-mode': 'import' }\n",
|
|
1030
|
+
'a json specifier':
|
|
1031
|
+
"import sum from './sum.json'\nexport declare const s: typeof sum\n",
|
|
1032
|
+
'a path reference':
|
|
1033
|
+
"/// <reference path='./sum.d.ts' />\nexport declare const sum: number\n",
|
|
1034
|
+
'a bare import() type':
|
|
1035
|
+
"export declare function f(): import('buffer').Buffer\n",
|
|
1036
|
+
'an import() type pinned to import mode':
|
|
1037
|
+
"export declare function f(): import('./sum.js', { with: { 'resolution-mode': 'import' } }).Sum\n",
|
|
1038
|
+
'a namespace export':
|
|
1039
|
+
'export declare namespace Legacy { const sum: number }\n',
|
|
1040
|
+
}
|
|
1041
|
+
for (const [what, source] of Object.entries(honest)) {
|
|
1042
|
+
t.notThrows(
|
|
1043
|
+
() =>
|
|
1044
|
+
prepareWasiBindingTypeDef(
|
|
1045
|
+
source,
|
|
1046
|
+
sourcePath,
|
|
1047
|
+
destinationPath,
|
|
1048
|
+
true,
|
|
1049
|
+
'module',
|
|
1050
|
+
),
|
|
1051
|
+
what,
|
|
1052
|
+
)
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Nothing is scanned when the source is already CommonJS — a `.d.cts` (or a
|
|
1056
|
+
// `.d.ts` in a `commonjs` package) reads the same beside its destination, so
|
|
1057
|
+
// every hazard above carries over verbatim.
|
|
1058
|
+
for (const [, source] of hazards) {
|
|
1059
|
+
for (const [cjsSourcePath, packageType] of [
|
|
1060
|
+
[join(projectDir, 'index.d.cts'), 'module'],
|
|
1061
|
+
[sourcePath, 'commonjs'],
|
|
1062
|
+
[sourcePath, undefined],
|
|
1063
|
+
] as const) {
|
|
1064
|
+
t.notThrows(() =>
|
|
1065
|
+
prepareWasiBindingTypeDef(
|
|
1066
|
+
source,
|
|
1067
|
+
cjsSourcePath,
|
|
1068
|
+
destinationPath,
|
|
1069
|
+
true,
|
|
1070
|
+
packageType,
|
|
1071
|
+
),
|
|
1072
|
+
)
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// A specifier that can cross still rebases onto the destination directory
|
|
1077
|
+
// — including a `declare module` augmentation name, which targets the file
|
|
1078
|
+
// it spells out.
|
|
1079
|
+
const relocated = prepareWasiBindingTypeDef(
|
|
1080
|
+
"/// <reference path='./sum.d.ts' />\ndeclare module './sum.mjs' { export const sum: number }\nexport declare function f(): import('./sum.mjs').Sum\nexport { sum } from './sum.mjs'\n",
|
|
1081
|
+
join(projectDir, 'types', 'index.d.mts'),
|
|
1082
|
+
destinationPath,
|
|
1083
|
+
true,
|
|
1084
|
+
)
|
|
1085
|
+
t.true(relocated.includes("declare module './types/sum.mjs'"))
|
|
1086
|
+
t.true(relocated.includes("import('./types/sum.mjs')"))
|
|
1087
|
+
t.true(relocated.includes("from './types/sum.mjs'"))
|
|
1088
|
+
t.true(relocated.includes("reference path='./types/sum.d.ts'"))
|
|
1089
|
+
})
|
|
1090
|
+
|
|
834
1091
|
test('a crate without type defs still declares the binding target', async (t) => {
|
|
835
1092
|
const { projectDir, typeDefDir } = t.context
|
|
836
1093
|
|
package/src/api/build.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type { BuildOptions as RawBuildOptions } from '../def/build.js'
|
|
|
21
21
|
import {
|
|
22
22
|
CLI_VERSION,
|
|
23
23
|
commitFileSystemTransaction,
|
|
24
|
+
commonJsDeclarationBarrier,
|
|
24
25
|
copyFileAtomic,
|
|
25
26
|
type Crate,
|
|
26
27
|
debugFactory,
|
|
@@ -795,6 +796,16 @@ export function bindingTargetDeclarationPredicate(input: {
|
|
|
795
796
|
(input.rootLoaderCandidate && (input.hasWasiFallback || exports.length > 0))
|
|
796
797
|
}
|
|
797
798
|
|
|
799
|
+
/**
|
|
800
|
+
* The `.d.cts` content for a WASI flavor loader, derived from the root
|
|
801
|
+
* declaration this build wrote.
|
|
802
|
+
*
|
|
803
|
+
* An ESM source (`*.d.mts`, or `*.d.ts` in a `"type": "module"` package) is
|
|
804
|
+
* re-declared verbatim when nothing in it changes meaning under CommonJS
|
|
805
|
+
* interpretation — which is everything a generated typedef and most
|
|
806
|
+
* `--dts-header`s contain. {@link commonJsDeclarationBarrier} names the
|
|
807
|
+
* constructs that still refuse, and why.
|
|
808
|
+
*/
|
|
798
809
|
export function prepareWasiBindingTypeDef(
|
|
799
810
|
source: string,
|
|
800
811
|
sourcePath: string,
|
|
@@ -803,12 +814,15 @@ export function prepareWasiBindingTypeDef(
|
|
|
803
814
|
packageType?: 'module' | 'commonjs',
|
|
804
815
|
) {
|
|
805
816
|
if (
|
|
806
|
-
sourcePath.endsWith('.
|
|
807
|
-
(sourcePath.endsWith('.
|
|
817
|
+
sourcePath.endsWith('.mts') ||
|
|
818
|
+
(sourcePath.endsWith('.ts') && packageType === 'module')
|
|
808
819
|
) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
820
|
+
const barrier = commonJsDeclarationBarrier(source)
|
|
821
|
+
if (barrier !== undefined) {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Cannot emit the CommonJS WASI declaration ${destinationPath} from the ESM declaration ${sourcePath}: ${barrier} cannot be re-declared in a CommonJS declaration. Use a .d.cts --dts path for WASI builds from ESM sources.`,
|
|
824
|
+
)
|
|
825
|
+
}
|
|
812
826
|
}
|
|
813
827
|
const targetSource = hasThreads
|
|
814
828
|
? source
|
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) &&
|