Skip to content

Commit 2ffe686

Browse files
authored
Respect importModuleSpecifierEnding inside node_modules packages (microsoft#48995)
* Respect importModuleSpecifierEnding inside node_modules packages * Add tests for missing package.json
1 parent 4765355 commit 2ffe686

File tree

4 files changed

+63
-23
lines changed

4 files changed

+63
-23
lines changed

src/compiler/moduleSpecifiers.ts

+24-23
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ namespace ts.moduleSpecifiers {
105105
const info = getInfo(importingSourceFile.path, host);
106106
const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options);
107107
return firstDefined(modulePaths,
108-
modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, /*packageNameOnly*/ true, options.overrideImportMode));
108+
modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode));
109109
}
110110

111111
function getModuleSpecifierWorker(
@@ -120,7 +120,7 @@ namespace ts.moduleSpecifiers {
120120
): string {
121121
const info = getInfo(importingSourceFileName, host);
122122
const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options);
123-
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, /*packageNameOnly*/ undefined, options.overrideImportMode)) ||
123+
return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) ||
124124
getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences);
125125
}
126126

@@ -248,7 +248,7 @@ namespace ts.moduleSpecifiers {
248248
let pathsSpecifiers: string[] | undefined;
249249
let relativeSpecifiers: string[] | undefined;
250250
for (const modulePath of modulePaths) {
251-
const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, /*packageNameOnly*/ undefined, options.overrideImportMode);
251+
const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode);
252252
nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier);
253253
if (specifier && modulePath.isRedirect) {
254254
// If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar",
@@ -666,7 +666,7 @@ namespace ts.moduleSpecifiers {
666666
: removeFileExtension(relativePath);
667667
}
668668

669-
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined {
669+
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined {
670670
if (!host.fileExists || !host.readFile) {
671671
return undefined;
672672
}
@@ -680,8 +680,9 @@ namespace ts.moduleSpecifiers {
680680
let moduleSpecifier = path;
681681
let isPackageRootPath = false;
682682
if (!packageNameOnly) {
683+
const preferences = getPreferences(host, userPreferences, options, importingSourceFile);
683684
let packageRootIndex = parts.packageRootIndex;
684-
let moduleFileNameForExtensionless: string | undefined;
685+
let moduleFileName: string | undefined;
685686
while (true) {
686687
// If the module could be imported by a directory name, use that directory's name
687688
const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex);
@@ -698,12 +699,12 @@ namespace ts.moduleSpecifiers {
698699
isPackageRootPath = true;
699700
break;
700701
}
701-
if (!moduleFileNameForExtensionless) moduleFileNameForExtensionless = moduleFileToTry;
702+
if (!moduleFileName) moduleFileName = moduleFileToTry;
702703

703704
// try with next level of directory
704705
packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1);
705706
if (packageRootIndex === -1) {
706-
moduleSpecifier = getExtensionlessFileName(moduleFileNameForExtensionless);
707+
moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host);
707708
break;
708709
}
709710
}
@@ -768,28 +769,22 @@ namespace ts.moduleSpecifiers {
768769
}
769770
}
770771
// If the file is the main module, it can be imported by the package name
771-
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
772+
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js";
772773
if (isString(mainFileRelative)) {
773774
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
774775
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) {
775776
return { packageRootPath, moduleFileToTry };
776777
}
777778
}
778779
}
779-
return { moduleFileToTry };
780-
}
781-
782-
function getExtensionlessFileName(path: string): string {
783-
// We still have a file name - remove the extension
784-
const fullModulePathWithoutExtension = removeFileExtension(path);
785-
786-
// If the file is /index, it can be imported by its directory name
787-
// IFF there is not _also_ a file by the same name
788-
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index" && !tryGetAnyFileFromPath(host, fullModulePathWithoutExtension.substring(0, parts.fileNameIndex))) {
789-
return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex);
780+
else {
781+
// No package.json exists; an index.js will still resolve as the package name
782+
const fileName = getCanonicalFileName(moduleFileToTry.substring(parts.packageRootIndex + 1));
783+
if (fileName === "index.d.ts" || fileName === "index.js" || fileName === "index.ts" || fileName === "index.tsx") {
784+
return { moduleFileToTry, packageRootPath };
785+
}
790786
}
791-
792-
return fullModulePathWithoutExtension;
787+
return { moduleFileToTry };
793788
}
794789
}
795790

@@ -812,13 +807,19 @@ namespace ts.moduleSpecifiers {
812807
});
813808
}
814809

815-
function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string {
810+
function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string {
816811
if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName;
817812
const noExtension = removeFileExtension(fileName);
818813
if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options);
819814
switch (ending) {
820815
case Ending.Minimal:
821-
return removeSuffix(noExtension, "/index");
816+
const withoutIndex = removeSuffix(noExtension, "/index");
817+
if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) {
818+
// Can't remove index if there's a file by the same name as the directory.
819+
// Probably more callers should pass `host` so we can determine this?
820+
return noExtension;
821+
}
822+
return withoutIndex;
822823
case Ending.Index:
823824
return noExtension;
824825
case Ending.JsExtension:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @module: commonjs
4+
5+
// @Filename: /node_modules/lit/index.d.cts
6+
//// export declare function customElement(name: string): any;
7+
8+
// @Filename: /a.ts
9+
//// customElement/**/
10+
11+
verify.importFixModuleSpecifiers("", ["lit/index.cjs"]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @module: nodenext
4+
5+
// @Filename: /node_modules/lit/index.d.cts
6+
//// export declare function customElement(name: string): any;
7+
8+
// @Filename: /a.ts
9+
//// customElement/**/
10+
11+
verify.importFixModuleSpecifiers("", ["lit/index.cjs"]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts"/>
2+
3+
// @module: commonjs
4+
5+
// @Filename: /node_modules/lit/package.json
6+
//// { "name": "lit", "version": "1.0.0" }
7+
8+
// @Filename: /node_modules/lit/index.d.ts
9+
//// import "./decorators";
10+
11+
// @Filename: /node_modules/lit/decorators.d.ts
12+
//// export declare function customElement(name: string): any;
13+
14+
// @Filename: /a.ts
15+
//// customElement/**/
16+
17+
verify.importFixModuleSpecifiers("", ["lit/decorators.js"], { importModuleSpecifierEnding: "js" })

0 commit comments

Comments
 (0)