Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a4b9110

Browse filesBrowse files
author
Andy
authored
Merge pull request microsoft#15139 from Microsoft/config-extension
Tsconfig inheritance: Do not resolve included files in an inherited tsconfig
2 parents 1b520fc + a745787 commit a4b9110
Copy full SHA for a4b9110

1 file changed

+125-82Lines changed: 125 additions & 82 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/commandLineParser.ts‎

Copy file name to clipboardExpand all lines: src/compiler/commandLineParser.ts
+125-82Lines changed: 125 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,58 +1088,38 @@ namespace ts {
10881088
* @param host Instance of ParseConfigHost used to enumerate files in folder.
10891089
* @param basePath A root directory to resolve relative path entries in the config
10901090
* file to. e.g. outDir
1091+
* @param resolutionStack Only present for backwards-compatibility. Should be empty.
10911092
*/
1092-
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = [], extraFileExtensions: JsFileExtensionInfo[] = []): ParsedCommandLine {
1093+
export function parseJsonConfigFileContent(
1094+
json: any,
1095+
host: ParseConfigHost,
1096+
basePath: string,
1097+
existingOptions: CompilerOptions = {},
1098+
configFileName?: string,
1099+
resolutionStack: Path[] = [],
1100+
extraFileExtensions: JsFileExtensionInfo[] = [],
1101+
): ParsedCommandLine {
10931102
const errors: Diagnostic[] = [];
1094-
basePath = normalizeSlashes(basePath);
1095-
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
1096-
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
1097-
if (resolutionStack.indexOf(resolvedPath) >= 0) {
1098-
return {
1099-
options: {},
1100-
fileNames: [],
1101-
typeAcquisition: {},
1102-
raw: json,
1103-
errors: [createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))],
1104-
wildcardDirectories: {}
1105-
};
1106-
}
11071103

1108-
let options: CompilerOptions = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName);
1104+
let options = (() => {
1105+
const { include, exclude, files, options, compileOnSave } = parseConfig(json, host, basePath, configFileName, resolutionStack, errors);
1106+
if (include) { json.include = include; }
1107+
if (exclude) { json.exclude = exclude; }
1108+
if (files) { json.files = files; }
1109+
if (compileOnSave !== undefined) { json.compileOnSave = compileOnSave; }
1110+
return options;
1111+
})();
1112+
1113+
options = extend(existingOptions, options);
1114+
options.configFilePath = configFileName;
1115+
11091116
// typingOptions has been deprecated and is only supported for backward compatibility purposes.
11101117
// It should be removed in future releases - use typeAcquisition instead.
11111118
const jsonOptions = json["typeAcquisition"] || json["typingOptions"];
11121119
const typeAcquisition: TypeAcquisition = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName);
11131120

1114-
let baseCompileOnSave: boolean;
1115-
if (json["extends"]) {
1116-
let [include, exclude, files, baseOptions]: [string[], string[], string[], CompilerOptions] = [undefined, undefined, undefined, {}];
1117-
if (typeof json["extends"] === "string") {
1118-
[include, exclude, files, baseCompileOnSave, baseOptions] = (tryExtendsName(json["extends"]) || [include, exclude, files, baseCompileOnSave, baseOptions]);
1119-
}
1120-
else {
1121-
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
1122-
}
1123-
if (include && !json["include"]) {
1124-
json["include"] = include;
1125-
}
1126-
if (exclude && !json["exclude"]) {
1127-
json["exclude"] = exclude;
1128-
}
1129-
if (files && !json["files"]) {
1130-
json["files"] = files;
1131-
}
1132-
options = assign({}, baseOptions, options);
1133-
}
1134-
1135-
options = extend(existingOptions, options);
1136-
options.configFilePath = configFileName;
1137-
1138-
const { fileNames, wildcardDirectories } = getFileNames(errors);
1139-
let compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
1140-
if (baseCompileOnSave && json[compileOnSaveCommandLineOption.name] === undefined) {
1141-
compileOnSave = baseCompileOnSave;
1142-
}
1121+
const { fileNames, wildcardDirectories } = getFileNames();
1122+
const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
11431123

11441124
return {
11451125
options,
@@ -1151,40 +1131,7 @@ namespace ts {
11511131
compileOnSave
11521132
};
11531133

1154-
function tryExtendsName(extendedConfig: string): [string[], string[], string[], boolean, CompilerOptions] {
1155-
// 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)
1156-
if (!(isRootedDiskPath(extendedConfig) || startsWith(normalizeSlashes(extendedConfig), "./") || startsWith(normalizeSlashes(extendedConfig), "../"))) {
1157-
errors.push(createCompilerDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig));
1158-
return;
1159-
}
1160-
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
1161-
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
1162-
extendedConfigPath = `${extendedConfigPath}.json` as Path;
1163-
if (!host.fileExists(extendedConfigPath)) {
1164-
errors.push(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
1165-
return;
1166-
}
1167-
}
1168-
const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path));
1169-
if (extendedResult.error) {
1170-
errors.push(extendedResult.error);
1171-
return;
1172-
}
1173-
const extendedDirname = getDirectoryPath(extendedConfigPath);
1174-
const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName);
1175-
const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
1176-
// Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios)
1177-
const result = parseJsonConfigFileContent(extendedResult.config, host, extendedDirname, /*existingOptions*/ undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath]));
1178-
errors.push(...result.errors);
1179-
const [include, exclude, files] = map(["include", "exclude", "files"], key => {
1180-
if (!json[key] && extendedResult.config[key]) {
1181-
return map(extendedResult.config[key], updatePath);
1182-
}
1183-
});
1184-
return [include, exclude, files, result.compileOnSave, result.options];
1185-
}
1186-
1187-
function getFileNames(errors: Diagnostic[]): ExpandResult {
1134+
function getFileNames(): ExpandResult {
11881135
let fileNames: string[];
11891136
if (hasProperty(json, "files")) {
11901137
if (isArray(json["files"])) {
@@ -1217,9 +1164,6 @@ namespace ts {
12171164
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"));
12181165
}
12191166
}
1220-
else if (hasProperty(json, "excludes")) {
1221-
errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude));
1222-
}
12231167
else {
12241168
// If no includes were specified, exclude common package folders and the outDir
12251169
excludeSpecs = includeSpecs ? [] : ["node_modules", "bower_components", "jspm_packages"];
@@ -1249,7 +1193,106 @@ namespace ts {
12491193
}
12501194
}
12511195

1252-
export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean | undefined {
1196+
interface ParsedTsconfig {
1197+
include: string[] | undefined;
1198+
exclude: string[] | undefined;
1199+
files: string[] | undefined;
1200+
options: CompilerOptions;
1201+
compileOnSave: boolean | undefined;
1202+
}
1203+
1204+
/**
1205+
* This *just* extracts options/include/exclude/files out of a config file.
1206+
* It does *not* resolve the included files.
1207+
*/
1208+
function parseConfig(
1209+
json: any,
1210+
host: ParseConfigHost,
1211+
basePath: string,
1212+
configFileName: string,
1213+
resolutionStack: Path[],
1214+
errors: Diagnostic[],
1215+
): ParsedTsconfig {
1216+
1217+
basePath = normalizeSlashes(basePath);
1218+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
1219+
const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName);
1220+
1221+
if (resolutionStack.indexOf(resolvedPath) >= 0) {
1222+
errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> ")));
1223+
return { include: undefined, exclude: undefined, files: undefined, options: {}, compileOnSave: undefined };
1224+
}
1225+
1226+
if (hasProperty(json, "excludes")) {
1227+
errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude));
1228+
}
1229+
1230+
let options: CompilerOptions = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName);
1231+
let include: string[] | undefined = json.include, exclude: string[] | undefined = json.exclude, files: string[] | undefined = json.files;
1232+
let compileOnSave: boolean | undefined = json.compileOnSave;
1233+
1234+
if (json.extends) {
1235+
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
1236+
resolutionStack = resolutionStack.concat([resolvedPath]);
1237+
const base = getExtendedConfig(json.extends, host, basePath, getCanonicalFileName, resolutionStack, errors);
1238+
if (base) {
1239+
include = include || base.include;
1240+
exclude = exclude || base.exclude;
1241+
files = files || base.files;
1242+
if (compileOnSave === undefined) {
1243+
compileOnSave = base.compileOnSave;
1244+
}
1245+
options = assign({}, base.options, options);
1246+
}
1247+
}
1248+
1249+
return { include, exclude, files, options, compileOnSave };
1250+
}
1251+
1252+
function getExtendedConfig(
1253+
extended: any, // Usually a string.
1254+
host: ts.ParseConfigHost,
1255+
basePath: string,
1256+
getCanonicalFileName: (fileName: string) => string,
1257+
resolutionStack: Path[],
1258+
errors: Diagnostic[],
1259+
): ParsedTsconfig | undefined {
1260+
if (typeof extended !== "string") {
1261+
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
1262+
return undefined;
1263+
}
1264+
1265+
extended = normalizeSlashes(extended);
1266+
1267+
// 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)
1268+
if (!(isRootedDiskPath(extended) || startsWith(extended, "./") || startsWith(extended, "../"))) {
1269+
errors.push(createCompilerDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extended));
1270+
return undefined;
1271+
}
1272+
1273+
let extendedConfigPath = toPath(extended, basePath, getCanonicalFileName);
1274+
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
1275+
extendedConfigPath = extendedConfigPath + ".json" as Path;
1276+
if (!host.fileExists(extendedConfigPath)) {
1277+
errors.push(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, extended));
1278+
return undefined;
1279+
}
1280+
}
1281+
1282+
const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path));
1283+
if (extendedResult.error) {
1284+
errors.push(extendedResult.error);
1285+
return undefined;
1286+
}
1287+
1288+
const extendedDirname = getDirectoryPath(extendedConfigPath);
1289+
const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName);
1290+
const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path);
1291+
const { include, exclude, files, options, compileOnSave } = parseConfig(extendedResult.config, host, extendedDirname, getBaseFileName(extendedConfigPath), resolutionStack, errors);
1292+
return { include: map(include, updatePath), exclude: map(exclude, updatePath), files: map(files, updatePath), compileOnSave, options };
1293+
}
1294+
1295+
export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean {
12531296
if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) {
12541297
return false;
12551298
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.