Skip to content

Commit 5951ee9

Browse files
a-tarasyukandrewbranchsandersn
authored
feat(48665): tsconfig "extends" field ignores "exports" field of source package (#50955)
* feat(48665): resolve configs from the exports field of the source package * add missed property * rename loadFileName to loadFileNameFromPackageJsonField * Apply suggestions from code review Co-authored-by: Nathan Shively-Sanders <[email protected]> Co-authored-by: Andrew Branch <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 0f5e037 commit 5951ee9

File tree

3 files changed

+41
-6
lines changed

3 files changed

+41
-6
lines changed

src/compiler/commandLineParser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import {
8585
NewLineKind,
8686
Node,
8787
NodeArray,
88-
nodeModuleNameResolver,
88+
nodeNextJsonConfigResolver,
8989
normalizePath,
9090
normalizeSlashes,
9191
NumericLiteral,
@@ -3413,7 +3413,7 @@ function getExtendsConfigPath(
34133413
return extendedConfigPath;
34143414
}
34153415
// If the path isn't a rooted or relative path, resolve like a module
3416-
const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.Node10 }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true);
3416+
const resolved = nodeNextJsonConfigResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), host);
34173417
if (resolved.resolvedModule) {
34183418
return resolved.resolvedModule.resolvedFileName;
34193419
}

src/compiler/moduleNameResolver.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,11 @@ export function nodeModuleNameResolver(moduleName: string, containingFile: strin
16291629
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, !!isConfigLookup, redirectedReference);
16301630
}
16311631

1632+
/** @internal */
1633+
export function nodeNextJsonConfigResolver(moduleName: string, containingFile: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
1634+
return nodeModuleNameResolverWorker(NodeResolutionFeatures.Exports, moduleName, getDirectoryPath(containingFile), { moduleResolution: ModuleResolutionKind.NodeNext }, host, /*cache*/ undefined, Extensions.Json, /*isConfigLookup*/ true, /*redirectedReference*/ undefined);
1635+
}
1636+
16321637
function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions, isConfigLookup: boolean, redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations {
16331638
const traceEnabled = isTraceEnabled(compilerOptions, host);
16341639

@@ -1868,14 +1873,25 @@ function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidat
18681873
}
18691874
}
18701875

1871-
function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
1876+
/**
1877+
* This function is only ever called with paths written in package.json files - never
1878+
* module specifiers written in source files - and so it always allows the
1879+
1880+
* candidate to end with a TS extension (but will also try substituting a JS extension for a TS extension).
1881+
*/
1882+
function loadFileNameFromPackageJsonField(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
18721883
if (extensions & Extensions.TypeScript && fileExtensionIsOneOf(candidate, supportedTSImplementationExtensions) ||
18731884
extensions & Extensions.Declaration && fileExtensionIsOneOf(candidate, supportedDeclarationExtensions)
18741885
) {
18751886
const result = tryFile(candidate, onlyRecordFailures, state);
18761887
return result !== undefined ? { path: candidate, ext: tryExtractTSExtension(candidate) as Extension, resolvedUsingTsExtension: undefined } : undefined;
18771888
}
18781889

1890+
if (state.isConfigLookup && extensions === Extensions.Json && fileExtensionIs(candidate, Extension.Json)) {
1891+
const result = tryFile(candidate, onlyRecordFailures, state);
1892+
return result !== undefined ? { path: candidate, ext: Extension.Json, resolvedUsingTsExtension: undefined } : undefined;
1893+
}
1894+
18791895
return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state);
18801896
}
18811897

@@ -2058,7 +2074,7 @@ function loadEntrypointsFromExportMap(
20582074
}
20592075
const resolvedTarget = combinePaths(scope.packageDirectory, target);
20602076
const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.());
2061-
const result = loadJSOrExactTSFileName(extensions, finalPath, /*recordOnlyFailures*/ false, state);
2077+
const result = loadFileNameFromPackageJsonField(extensions, finalPath, /*recordOnlyFailures*/ false, state);
20622078
if (result) {
20632079
entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path);
20642080
return true;
@@ -2488,7 +2504,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
24882504
const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath);
24892505
const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports);
24902506
if (inputLink) return inputLink;
2491-
return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state)));
2507+
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state)));
24922508
}
24932509
else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null
24942510
if (!Array.isArray(target)) {
@@ -2632,7 +2648,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
26322648
if (!extensionIsOk(extensions, possibleExt)) continue;
26332649
const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames());
26342650
if (state.host.fileExists(possibleInputWithInputExtension)) {
2635-
return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state)));
2651+
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state)));
26362652
}
26372653
}
26382654
}

src/testRunner/unittests/config/configurationExtension.ts

+19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
77
cwd,
88
files: {
99
[root]: {
10+
"dev/node_modules/@foo/tsconfig/package.json": JSON.stringify({
11+
name: "@foo/tsconfig",
12+
version: "1.0.0",
13+
exports: {
14+
".": "./src/tsconfig.json"
15+
}
16+
}),
17+
"dev/node_modules/@foo/tsconfig/src/tsconfig.json": JSON.stringify({
18+
compilerOptions: {
19+
strict: true,
20+
}
21+
}),
22+
"dev/tsconfig.extendsFoo.json": JSON.stringify({
23+
extends: "@foo/tsconfig",
24+
files: [
25+
"main.ts",
26+
]
27+
}),
1028
"dev/node_modules/config-box/package.json": JSON.stringify({
1129
name: "config-box",
1230
version: "1.0.0",
@@ -376,6 +394,7 @@ describe("unittests:: config:: configurationExtension", () => {
376394
testSuccess("can lookup via an implicit tsconfig in a package-relative directory", "tsconfig.extendsBoxImpliedUnstrict.json", { strict: false }, [ts.combinePaths(basePath, "main.ts")]);
377395
testSuccess("can lookup via an implicit tsconfig in a package-relative directory with name", "tsconfig.extendsBoxImpliedUnstrictExtension.json", { strict: false }, [ts.combinePaths(basePath, "main.ts")]);
378396
testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]);
397+
testSuccess("can lookup via an package.json exports", "tsconfig.extendsFoo.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]);
379398
});
380399

381400
it("adds extendedSourceFiles only once", () => {

0 commit comments

Comments
 (0)