Skip to content

Commit 86eb070

Browse files
committed
Do two passes of exports resolution
1 parent 78db89f commit 86eb070

File tree

5 files changed

+134
-23
lines changed

5 files changed

+134
-23
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,15 +1600,17 @@ namespace ts {
16001600
entrypoints = append(entrypoints, requireResolution?.path);
16011601

16021602
if (features & NodeResolutionFeatures.Exports && packageJsonInfo.packageJsonContent.exports) {
1603-
const exportState = { ...requireState, failedLookupLocations: [], conditions: ["node", "import", "require", "types"] };
1604-
const exportResolutions = loadEntrypointsFromExportMap(
1605-
packageJsonInfo,
1606-
packageJsonInfo.packageJsonContent.exports,
1607-
exportState,
1608-
extensions);
1609-
if (exportResolutions) {
1610-
for (const resolution of exportResolutions) {
1611-
entrypoints = appendIfUnique(entrypoints, resolution.path);
1603+
for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) {
1604+
const exportState = { ...requireState, failedLookupLocations: [], conditions };
1605+
const exportResolutions = loadEntrypointsFromExportMap(
1606+
packageJsonInfo,
1607+
packageJsonInfo.packageJsonContent.exports,
1608+
exportState,
1609+
extensions);
1610+
if (exportResolutions) {
1611+
for (const resolution of exportResolutions) {
1612+
entrypoints = appendIfUnique(entrypoints, resolution.path);
1613+
}
16121614
}
16131615
}
16141616
}

src/compiler/moduleSpecifiers.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,44 +59,55 @@ namespace ts.moduleSpecifiers {
5959
};
6060
}
6161

62+
// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`?
63+
// Because when this is called by the file renamer, `importingSourceFile` is the file being renamed,
64+
// while `importingSourceFileName` its *new* name. We need a source file just to get its
65+
// `impliedNodeFormat` and to detect certain preferences from existing import module specifiers.
6266
export function updateModuleSpecifier(
6367
compilerOptions: CompilerOptions,
68+
importingSourceFile: SourceFile,
6469
importingSourceFileName: Path,
6570
toFileName: string,
6671
host: ModuleSpecifierResolutionHost,
6772
oldImportSpecifier: string,
6873
): string | undefined {
69-
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {});
74+
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFile.path, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {});
7075
if (res === oldImportSpecifier) return undefined;
7176
return res;
7277
}
7378

74-
// Note: importingSourceFile is just for usesJsExtensionOnImports
79+
// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`?
80+
// Because when this is called by the declaration emitter, `importingSourceFile` is the implementation
81+
// file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the
82+
// one currently being produced; the latter to the one being imported). We need an implementation file
83+
// just to get its `impliedNodeFormat` and to detect certain preferences from existing import module
84+
// specifiers.
7585
export function getModuleSpecifier(
7686
compilerOptions: CompilerOptions,
7787
importingSourceFile: SourceFile,
7888
importingSourceFileName: Path,
7989
toFileName: string,
8090
host: ModuleSpecifierResolutionHost,
8191
): string {
82-
return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {});
92+
return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {});
8393
}
8494

8595
export function getNodeModulesPackageName(
8696
compilerOptions: CompilerOptions,
87-
importingSourceFileName: Path,
97+
importingSourceFile: SourceFile,
8898
nodeModulesFileName: string,
8999
host: ModuleSpecifierResolutionHost,
90100
preferences: UserPreferences,
91101
): string | undefined {
92-
const info = getInfo(importingSourceFileName, host);
93-
const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host, preferences);
102+
const info = getInfo(importingSourceFile.path, host);
103+
const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences);
94104
return firstDefined(modulePaths,
95-
modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true));
105+
modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, /*packageNameOnly*/ true));
96106
}
97107

98108
function getModuleSpecifierWorker(
99109
compilerOptions: CompilerOptions,
110+
importingSourceFile: SourceFile,
100111
importingSourceFileName: Path,
101112
toFileName: string,
102113
host: ModuleSpecifierResolutionHost,
@@ -105,7 +116,7 @@ namespace ts.moduleSpecifiers {
105116
): string {
106117
const info = getInfo(importingSourceFileName, host);
107118
const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences);
108-
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) ||
119+
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions)) ||
109120
getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences);
110121
}
111122

@@ -222,7 +233,7 @@ namespace ts.moduleSpecifiers {
222233
let pathsSpecifiers: string[] | undefined;
223234
let relativeSpecifiers: string[] | undefined;
224235
for (const modulePath of modulePaths) {
225-
const specifier = tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions);
236+
const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions);
226237
nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier);
227238
if (specifier && modulePath.isRedirect) {
228239
// If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar",
@@ -639,7 +650,7 @@ namespace ts.moduleSpecifiers {
639650
: removeFileExtension(relativePath);
640651
}
641652

642-
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined {
653+
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined {
643654
if (!host.fileExists || !host.readFile) {
644655
return undefined;
645656
}
@@ -706,10 +717,14 @@ namespace ts.moduleSpecifiers {
706717
let moduleFileToTry = path;
707718
if (host.fileExists(packageJsonPath)) {
708719
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
709-
// TODO: Inject `require` or `import` condition based on the intended import mode
710720
if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) {
721+
// `conditions` *could* be made to go against `importingSourceFile.impliedNodeFormat` if something wanted to generate
722+
// an ImportEqualsDeclaration in an ESM-implied file or an ImportCall in a CJS-implied file. But since this function is
723+
// usually called to conjure an import out of thin air, we don't have an existing usage to call `getModeForUsageAtIndex`
724+
// with, so for now we just stick with the mode of the file.
725+
const conditions = ["node", importingSourceFile.impliedNodeFormat === ModuleKind.ESNext ? "import" : "require", "types"];
711726
const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string"
712-
? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, ["node", "types"])
727+
? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions)
713728
: undefined;
714729
if (fromExports) {
715730
const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry)

src/services/getEditsForFileRename.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ namespace ts {
156156

157157
// Need an update if the imported file moved, or the importing file moved and was using a relative path.
158158
return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text)))
159-
? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text)
159+
? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), sourceFile, getCanonicalFileName(newImportFromPath) as Path, toImport.newFileName, createModuleSpecifierResolutionHost(program, host), importLiteral.text)
160160
: undefined;
161161
});
162162
}

src/services/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3085,7 +3085,7 @@ namespace ts {
30853085
}
30863086
const specifier = moduleSpecifiers.getNodeModulesPackageName(
30873087
host.getCompilationSettings(),
3088-
fromFile.path,
3088+
fromFile,
30893089
importedFileName,
30903090
moduleSpecifierResolutionHost,
30913091
preferences,
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// Both 'import' and 'require' should be pulled in
4+
5+
// @Filename: /tsconfig.json
6+
//// {
7+
//// "compilerOptions": {
8+
//// "module": "nodenext"
9+
//// }
10+
//// }
11+
12+
// @Filename: /package.json
13+
//// {
14+
//// "type": "module",
15+
//// "dependencies": {
16+
//// "dependency": "^1.0.0"
17+
//// }
18+
//// }
19+
20+
// @Filename: /node_modules/dependency/package.json
21+
//// {
22+
//// "type": "module",
23+
//// "name": "dependency",
24+
//// "version": "1.0.0",
25+
//// "exports": {
26+
//// "./lol": {
27+
//// "import": "./lib/index.js",
28+
//// "require": "./lib/lol.js"
29+
//// }
30+
//// }
31+
//// }
32+
33+
// @Filename: /node_modules/dependency/lib/index.d.ts
34+
//// export function fooFromIndex(): void;
35+
36+
// @Filename: /node_modules/dependency/lib/lol.d.ts
37+
//// export function fooFromLol(): void;
38+
39+
// @Filename: /src/bar.ts
40+
//// import { fooFromIndex } from "dependency";
41+
42+
// @Filename: /src/foo.cts
43+
//// fooFrom/*cts*/
44+
45+
// @Filename: /src/foo.mts
46+
//// fooFrom/*mts*/
47+
48+
goTo.marker("cts");
49+
verify.completions({
50+
marker: "cts",
51+
exact: completion.globalsPlus([{
52+
// TODO: this one will go away (see note in ./autoImportProvider_exportMap3.ts)
53+
name: "fooFromIndex",
54+
source: "../node_modules/dependency/lib/index",
55+
sourceDisplay: "../node_modules/dependency/lib/index",
56+
sortText: completion.SortText.AutoImportSuggestions,
57+
hasAction: true,
58+
}, {
59+
name: "fooFromLol",
60+
source: "dependency/lol",
61+
sourceDisplay: "dependency/lol",
62+
sortText: completion.SortText.AutoImportSuggestions,
63+
hasAction: true,
64+
}]),
65+
preferences: {
66+
includeCompletionsForModuleExports: true,
67+
includeInsertTextCompletions: true,
68+
allowIncompleteCompletions: true,
69+
},
70+
});
71+
72+
// goTo.marker("mts");
73+
// verify.completions({
74+
// marker: "mts",
75+
// exact: completion.globalsPlus([{
76+
// name: "fooFromIndex",
77+
// source: "dependency/lol",
78+
// sourceDisplay: "dependency/lol",
79+
// sortText: completion.SortText.AutoImportSuggestions,
80+
// hasAction: true,
81+
// }, {
82+
// // TODO: this one will go away (see note in ./autoImportProvider_exportMap3.ts)
83+
// name: "fooFromLol",
84+
// source: "../node_modules/dependency/lib/index.js",
85+
// sourceDisplay: "../node_modules/dependency/lib/index.js",
86+
// sortText: completion.SortText.AutoImportSuggestions,
87+
// hasAction: true,
88+
// }]),
89+
// preferences: {
90+
// includeCompletionsForModuleExports: true,
91+
// includeInsertTextCompletions: true,
92+
// allowIncompleteCompletions: true,
93+
// },
94+
// });

0 commit comments

Comments
 (0)