Skip to content

Commit 05716a7

Browse files
authored
Add support for configuration inheritance via packages (#27348)
* Add support for configuration inheritance via packages * Fix lint * Propagate trace into config parse hosts
1 parent 0aac87f commit 05716a7

File tree

8 files changed

+156
-39
lines changed

8 files changed

+156
-39
lines changed

src/compiler/commandLineParser.ts

+16-12
Original file line numberDiff line numberDiff line change
@@ -2191,20 +2191,24 @@ namespace ts {
21912191
errors: Push<Diagnostic>,
21922192
createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) {
21932193
extendedConfig = normalizeSlashes(extendedConfig);
2194-
// If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future)
2195-
if (!(isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../"))) {
2196-
errors.push(createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig));
2197-
return undefined;
2198-
}
2199-
let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath);
2200-
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) {
2201-
extendedConfigPath = `${extendedConfigPath}.json`;
2202-
if (!host.fileExists(extendedConfigPath)) {
2203-
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
2204-
return undefined;
2194+
if (isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../")) {
2195+
let extendedConfigPath = getNormalizedAbsolutePath(extendedConfig, basePath);
2196+
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) {
2197+
extendedConfigPath = `${extendedConfigPath}.json`;
2198+
if (!host.fileExists(extendedConfigPath)) {
2199+
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
2200+
return undefined;
2201+
}
22052202
}
2203+
return extendedConfigPath;
2204+
}
2205+
// If the path isn't a rooted or relative path, resolve like a module
2206+
const resolved = nodeModuleNameResolver(extendedConfig, combinePaths(basePath, "tsconfig.json"), { moduleResolution: ModuleResolutionKind.NodeJs }, host, /*cache*/ undefined, /*projectRefs*/ undefined, /*lookupConfig*/ true);
2207+
if (resolved.resolvedModule) {
2208+
return resolved.resolvedModule.resolvedFileName;
22062209
}
2207-
return extendedConfigPath;
2210+
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
2211+
return undefined;
22082212
}
22092213

22102214
function getExtendedConfig(

src/compiler/moduleNameResolver.ts

+45-19
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ namespace ts {
6262
TypeScript, /** '.ts', '.tsx', or '.d.ts' */
6363
JavaScript, /** '.js' or '.jsx' */
6464
Json, /** '.json' */
65+
TSConfig, /** '.json' with `tsconfig` used instead of `index` */
6566
DtsOnly /** Only '.d.ts' */
6667
}
6768

@@ -98,6 +99,7 @@ namespace ts {
9899
types?: string;
99100
typesVersions?: MapLike<MapLike<string[]>>;
100101
main?: string;
102+
tsconfig?: string;
101103
}
102104

103105
interface PackageJson extends PackageJsonPathFields {
@@ -126,7 +128,7 @@ namespace ts {
126128
return value;
127129
}
128130

129-
function readPackageJsonPathField<K extends "typings" | "types" | "main">(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined {
131+
function readPackageJsonPathField<K extends "typings" | "types" | "main" | "tsconfig">(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined {
130132
const fileName = readPackageJsonField(jsonContent, fieldName, "string", state);
131133
if (fileName === undefined) return;
132134
const path = normalizePath(combinePaths(baseDirectory, fileName));
@@ -141,6 +143,10 @@ namespace ts {
141143
|| readPackageJsonPathField(jsonContent, "types", baseDirectory, state);
142144
}
143145

146+
function readPackageJsonTSConfigField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
147+
return readPackageJsonPathField(jsonContent, "tsconfig", baseDirectory, state);
148+
}
149+
144150
function readPackageJsonMainField(jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState) {
145151
return readPackageJsonPathField(jsonContent, "main", baseDirectory, state);
146152
}
@@ -853,25 +859,27 @@ namespace ts {
853859
return resolvedModule && resolvedModule.resolvedFileName;
854860
}
855861

862+
const jsOnlyExtensions = [Extensions.JavaScript];
863+
const tsExtensions = [Extensions.TypeScript, Extensions.JavaScript];
864+
const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json];
865+
const tsconfigExtensions = [Extensions.TSConfig];
856866
function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
857-
return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*redirectedReference*/ undefined, /*jsOnly*/ true);
867+
return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined);
858868
}
859869

860-
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations {
861-
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, redirectedReference, /*jsOnly*/ false);
870+
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
871+
/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // tslint:disable-line unified-signatures
872+
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations {
873+
return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference);
862874
}
863875

864-
function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations {
876+
function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations {
865877
const traceEnabled = isTraceEnabled(compilerOptions, host);
866878

867879
const failedLookupLocations: string[] = [];
868880
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
869881

870-
const result = jsOnly ?
871-
tryResolve(Extensions.JavaScript) :
872-
(tryResolve(Extensions.TypeScript) ||
873-
tryResolve(Extensions.JavaScript) ||
874-
(compilerOptions.resolveJsonModule ? tryResolve(Extensions.Json) : undefined));
882+
const result = forEach(extensions, ext => tryResolve(ext));
875883
if (result && result.value) {
876884
const { resolved, isExternalLibraryImport } = result.value;
877885
return createResolvedModuleWithFailedLookupLocations(resolved, isExternalLibraryImport, failedLookupLocations);
@@ -1019,9 +1027,9 @@ namespace ts {
10191027
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
10201028
*/
10211029
function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
1022-
if (extensions === Extensions.Json) {
1030+
if (extensions === Extensions.Json || extensions === Extensions.TSConfig) {
10231031
const extensionLess = tryRemoveExtension(candidate, Extension.Json);
1024-
return extensionLess === undefined ? undefined : tryAddingExtensions(extensionLess, extensions, onlyRecordFailures, state);
1032+
return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state);
10251033
}
10261034

10271035
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
@@ -1059,6 +1067,7 @@ namespace ts {
10591067
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
10601068
case Extensions.JavaScript:
10611069
return tryExtension(Extension.Js) || tryExtension(Extension.Jsx);
1070+
case Extensions.TSConfig:
10621071
case Extensions.Json:
10631072
return tryExtension(Extension.Json);
10641073
}
@@ -1156,11 +1165,27 @@ namespace ts {
11561165
}
11571166

11581167
function loadNodeModuleFromDirectoryWorker(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, jsonContent: PackageJsonPathFields | undefined, versionPaths: VersionPaths | undefined): PathAndExtension | undefined {
1159-
const packageFile = jsonContent && (extensions !== Extensions.JavaScript && extensions !== Extensions.Json
1160-
? readPackageJsonTypesFields(jsonContent, candidate, state) ||
1161-
// When resolving typescript modules, try resolving using main field as well
1162-
(extensions === Extensions.TypeScript ? readPackageJsonMainField(jsonContent, candidate, state) : undefined)
1163-
: readPackageJsonMainField(jsonContent, candidate, state));
1168+
let packageFile: string | undefined;
1169+
if (jsonContent) {
1170+
switch (extensions) {
1171+
case Extensions.JavaScript:
1172+
case Extensions.Json:
1173+
packageFile = readPackageJsonMainField(jsonContent, candidate, state);
1174+
break;
1175+
case Extensions.TypeScript:
1176+
// When resolving typescript modules, try resolving using main field as well
1177+
packageFile = readPackageJsonTypesFields(jsonContent, candidate, state) || readPackageJsonMainField(jsonContent, candidate, state);
1178+
break;
1179+
case Extensions.DtsOnly:
1180+
packageFile = readPackageJsonTypesFields(jsonContent, candidate, state);
1181+
break;
1182+
case Extensions.TSConfig:
1183+
packageFile = readPackageJsonTSConfigField(jsonContent, candidate, state);
1184+
break;
1185+
default:
1186+
return Debug.assertNever(extensions);
1187+
}
1188+
}
11641189

11651190
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
11661191
const fromFile = tryFile(candidate, onlyRecordFailures, state);
@@ -1182,7 +1207,7 @@ namespace ts {
11821207

11831208
const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined;
11841209
const onlyRecordFailuresForIndex = onlyRecordFailures || !directoryProbablyExists(candidate, state.host);
1185-
const indexPath = combinePaths(candidate, "index");
1210+
const indexPath = combinePaths(candidate, extensions === Extensions.TSConfig ? "tsconfig" : "index");
11861211

11871212
if (versionPaths && (!packageFile || containsPath(candidate, packageFile))) {
11881213
const moduleName = getRelativePathFromDirectory(candidate, packageFile || indexPath, /*ignoreCase*/ false);
@@ -1213,6 +1238,7 @@ namespace ts {
12131238
switch (extensions) {
12141239
case Extensions.JavaScript:
12151240
return extension === Extension.Js || extension === Extension.Jsx;
1241+
case Extensions.TSConfig:
12161242
case Extensions.Json:
12171243
return extension === Extension.Json;
12181244
case Extensions.TypeScript:
@@ -1264,7 +1290,7 @@ namespace ts {
12641290
if (packageResult) {
12651291
return packageResult;
12661292
}
1267-
if (extensions !== Extensions.JavaScript && extensions !== Extensions.Json) {
1293+
if (extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) {
12681294
const nodeModulesAtTypes = combinePaths(nodeModulesFolder, "@types");
12691295
let nodeModulesAtTypesExists = nodeModulesFolderExists;
12701296
if (nodeModulesFolderExists && !directoryProbablyExists(nodeModulesAtTypes, state.host)) {

src/compiler/program.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2987,7 +2987,8 @@ namespace ts {
29872987
readFile: f => host.readFile(f),
29882988
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
29892989
getCurrentDirectory: () => host.getCurrentDirectory(),
2990-
onUnRecoverableConfigFileDiagnostic: () => undefined
2990+
onUnRecoverableConfigFileDiagnostic: () => undefined,
2991+
trace: host.trace ? (s) => host.trace!(s) : undefined
29912992
};
29922993
}
29932994

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,7 @@ namespace ts {
28022802
fileExists(path: string): boolean;
28032803

28042804
readFile(path: string): string | undefined;
2805+
trace?(s: string): void;
28052806
}
28062807

28072808
/**

src/compiler/watch.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ namespace ts {
484484
fileExists: path => host.fileExists(path),
485485
readFile,
486486
getCurrentDirectory,
487-
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic
487+
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic,
488+
trace: host.trace ? s => host.trace!(s) : undefined
488489
};
489490

490491
// From tsc we want to get already parsed result and hence check for rootFileNames

0 commit comments

Comments
 (0)