Fix(29118): tsconfig.extends as array (#50403)

* tsconfig.extends as array

* Updated baselines

* Changes for pr

* Changes for pr comments

* Fixed formatting and edited a test

* Resolved errors after a merge conflict

* Added "string | list" type implentation

* Removed string | list type implementation

* Fixed formatting

* Added compiler test

* Resolving programUpdate errors

* Fixing commandLineParser error
This commit is contained in:
navya9singh
2022-12-13 11:16:07 -08:00
committed by GitHub
parent a3802c1e7b
commit 7267fcaeb9
17 changed files with 767 additions and 104 deletions
+1
View File
@@ -1161,6 +1161,7 @@ function getBuildInfo(state: BuilderProgramState, bundle: BundleBuildInfo | unde
function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) {
if (option) {
Debug.assert(option.type !== "listOrElement");
if (option.type === "list") {
const values = value as readonly (string | number)[];
if (option.element.isFilePath && values.length) {
+158 -58
View File
@@ -103,6 +103,7 @@ import {
returnTrue,
ScriptTarget,
startsWith,
stringContains,
StringLiteral,
SyntaxKind,
sys,
@@ -1658,11 +1659,14 @@ export function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value:
}
/** @internal */
export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push<Diagnostic>): (string | number)[] | undefined {
export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push<Diagnostic>): string | (string | number)[] | undefined {
value = trimString(value);
if (startsWith(value, "-")) {
return undefined;
}
if (opt.type === "listOrElement" && !stringContains(value, ",")) {
return validateJsonOptionValue(opt, value, errors);
}
if (value === "") {
return [];
}
@@ -1672,6 +1676,9 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = ""
return mapDefined(values, v => validateJsonOptionValue(opt.element, parseInt(v), errors));
case "string":
return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors));
case "boolean":
case "object":
return Debug.fail(`List of ${opt.element.type} is not yet supported.`);
default:
return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors));
}
@@ -1850,6 +1857,9 @@ function parseOptionValue(
i++;
}
break;
case "listOrElement":
Debug.fail("listOrElement not supported here");
break;
// If not a primitive, the possible types are specified in what is effectively a map of options.
default:
options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors);
@@ -2090,6 +2100,15 @@ function getCommandLineTypeAcquisitionMap() {
return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations));
}
const extendsOptionDeclaration: CommandLineOptionOfListType = {
name: "extends",
type: "listOrElement",
element: {
name: "extends",
type: "string"
},
category: Diagnostics.File_Management,
};
let _tsconfigRootOptions: TsConfigOnlyOption;
function getTsconfigRootOptionsMap() {
if (_tsconfigRootOptions === undefined) {
@@ -2121,11 +2140,7 @@ function getTsconfigRootOptionsMap() {
elementOptions: getCommandLineTypeAcquisitionMap(),
extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics
},
{
name: "extends",
type: "string",
category: Diagnostics.File_Management,
},
extendsOptionDeclaration,
{
name: "references",
type: "list",
@@ -2339,11 +2354,11 @@ export function convertToObjectWorker(
let invalidReported: boolean | undefined;
switch (valueExpression.kind) {
case SyntaxKind.TrueKeyword:
reportInvalidOptionValue(option && option.type !== "boolean");
reportInvalidOptionValue(option && option.type !== "boolean" && (option.type !== "listOrElement" || option.element.type !== "boolean"));
return validateValue(/*value*/ true);
case SyntaxKind.FalseKeyword:
reportInvalidOptionValue(option && option.type !== "boolean");
reportInvalidOptionValue(option && option.type !== "boolean"&& (option.type !== "listOrElement" || option.element.type !== "boolean"));
return validateValue(/*value*/ false);
case SyntaxKind.NullKeyword:
@@ -2354,8 +2369,11 @@ export function convertToObjectWorker(
if (!isDoubleQuotedString(valueExpression)) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected));
}
reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string"));
reportInvalidOptionValue(option && isString(option.type) && option.type !== "string" && (option.type !== "listOrElement" || (isString(option.element.type) && option.element.type !== "string")));
const text = (valueExpression as StringLiteral).text;
if (option) {
Debug.assert(option.type !== "listOrElement" || option.element.type === "string", "Only string or array of string is handled for now");
}
if (option && !isString(option.type)) {
const customOption = option as CommandLineOptionOfCustomType;
// Validate custom option type
@@ -2372,18 +2390,18 @@ export function convertToObjectWorker(
return validateValue(text);
case SyntaxKind.NumericLiteral:
reportInvalidOptionValue(option && option.type !== "number");
reportInvalidOptionValue(option && option.type !== "number" && (option.type !== "listOrElement" || option.element.type !== "number"));
return validateValue(Number((valueExpression as NumericLiteral).text));
case SyntaxKind.PrefixUnaryExpression:
if ((valueExpression as PrefixUnaryExpression).operator !== SyntaxKind.MinusToken || (valueExpression as PrefixUnaryExpression).operand.kind !== SyntaxKind.NumericLiteral) {
break; // not valid JSON syntax
}
reportInvalidOptionValue(option && option.type !== "number");
reportInvalidOptionValue(option && option.type !== "number" && (option.type !== "listOrElement" || option.element.type !== "number"));
return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text));
case SyntaxKind.ObjectLiteralExpression:
reportInvalidOptionValue(option && option.type !== "object");
reportInvalidOptionValue(option && option.type !== "object" && (option.type !== "listOrElement" || option.element.type !== "object"));
const objectLiteralExpression = valueExpression as ObjectLiteralExpression;
// Currently having element option declaration in the tsconfig with type "object"
@@ -2404,7 +2422,7 @@ export function convertToObjectWorker(
}
case SyntaxKind.ArrayLiteralExpression:
reportInvalidOptionValue(option && option.type !== "list");
reportInvalidOptionValue(option && option.type !== "list" && option.type !== "listOrElement");
return validateValue(convertArrayLiteralExpressionToJson(
(valueExpression as ArrayLiteralExpression).elements,
option && (option as CommandLineOptionOfListType).element));
@@ -2444,8 +2462,10 @@ export function convertToObjectWorker(
}
}
function getCompilerOptionValueTypeString(option: CommandLineOption) {
return option.type === "list" ?
function getCompilerOptionValueTypeString(option: CommandLineOption): string {
return (option.type === "listOrElement") ?
`${getCompilerOptionValueTypeString(option.element)} or Array`:
option.type === "list" ?
"Array" :
isString(option.type) ? option.type : "string";
}
@@ -2456,6 +2476,9 @@ function isCompilerOptionsValue(option: CommandLineOption | undefined, value: an
if (option.type === "list") {
return isArray(value);
}
if (option.type === "listOrElement") {
return isArray(value) || isCompilerOptionsValue(option.element, value);
}
const expectedType = isString(option.type) ? option.type : "string";
return typeof value === expectedType;
}
@@ -2560,15 +2583,18 @@ function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined,
}
function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map<string, string | number> | undefined {
if (optionDefinition.type === "string" || optionDefinition.type === "number" || optionDefinition.type === "boolean" || optionDefinition.type === "object") {
// this is of a type CommandLineOptionOfPrimitiveType
return undefined;
}
else if (optionDefinition.type === "list") {
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
}
else {
return optionDefinition.type;
switch (optionDefinition.type) {
case "string":
case "number":
case "boolean":
case "object":
// this is of a type CommandLineOptionOfPrimitiveType
return undefined;
case "list":
case "listOrElement":
return getCustomTypeMapOfCommandLineOption(optionDefinition.element);
default:
return optionDefinition.type;
}
}
@@ -2612,6 +2638,7 @@ function serializeOptionBaseObject(
const value = options[name] as CompilerOptionsValue;
const optionDefinition = optionsNameMap.get(name.toLowerCase());
if (optionDefinition) {
Debug.assert(optionDefinition.type !== "listOrElement");
const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition);
if (!customTypeMap) {
// There is no map associated with this compiler option then use the value as-is
@@ -2805,6 +2832,7 @@ function convertToOptionValueWithAbsolutePaths(option: CommandLineOption | undef
else if (option.isFilePath) {
return toAbsolutePath(value as string);
}
Debug.assert(option.type !== "listOrElement");
}
return value;
}
@@ -3076,13 +3104,22 @@ export interface ParsedTsconfig {
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
*/
extendedConfigPath?: string;
extendedConfigPath?: string | string[];
}
function isSuccessfulParsedTsconfig(value: ParsedTsconfig) {
return !!value.options;
}
interface ExtendsResult {
options: CompilerOptions;
watchOptions?: WatchOptions;
include?: string[];
exclude?: string[];
files?: string[];
compileOnSave?: boolean;
extendedSourceFiles?: Set<string>
}
/**
* This *just* extracts options/include/exclude/files out of a config file.
* It does *not* resolve the included files.
@@ -3119,34 +3156,52 @@ function parseConfig(
if (ownConfig.extendedConfigPath) {
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
resolutionStack = resolutionStack.concat([resolvedPath]);
const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, resolutionStack, errors, extendedConfigCache);
const result: ExtendsResult = { options:{} };
if (isString(ownConfig.extendedConfigPath)) {
applyExtendedConfig(result, ownConfig.extendedConfigPath);
}
else {
ownConfig.extendedConfigPath.forEach(extendedConfigPath => applyExtendedConfig(result, extendedConfigPath));
}
if (!ownConfig.raw.include && result.include) ownConfig.raw.include = result.include;
if (!ownConfig.raw.exclude && result.exclude) ownConfig.raw.exclude = result.exclude;
if (!ownConfig.raw.files && result.files) ownConfig.raw.files = result.files;
if (ownConfig.raw.compileOnSave === undefined && result.compileOnSave) ownConfig.raw.compileOnSave = result.compileOnSave;
if (sourceFile && result.extendedSourceFiles) sourceFile.extendedSourceFiles = arrayFrom(result.extendedSourceFiles.keys());
ownConfig.options = assign(result.options, ownConfig.options);
ownConfig.watchOptions = ownConfig.watchOptions && result.watchOptions ?
assign(result.watchOptions, ownConfig.watchOptions) :
ownConfig.watchOptions || result.watchOptions;
}
return ownConfig;
function applyExtendedConfig(result: ExtendsResult, extendedConfigPath: string){
const extendedConfig = getExtendedConfig(sourceFile, extendedConfigPath, host, resolutionStack, errors, extendedConfigCache, result);
if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) {
const baseRaw = extendedConfig.raw;
const raw = ownConfig.raw;
let relativeDifference: string | undefined;
const setPropertyInRawIfNotUndefined = (propertyName: string) => {
if (!raw[propertyName] && baseRaw[propertyName]) {
raw[propertyName] = map(baseRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths(
relativeDifference ||= convertToRelativePath(getDirectoryPath(ownConfig.extendedConfigPath!), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)),
const extendsRaw = extendedConfig.raw;
let relativeDifference: string | undefined ;
const setPropertyInResultIfNotUndefined = (propertyName: "include" | "exclude" | "files") => {
if (extendsRaw[propertyName]) {
result[propertyName] = map(extendsRaw[propertyName], (path: string) => isRootedDiskPath(path) ? path : combinePaths(
relativeDifference ||= convertToRelativePath(getDirectoryPath(extendedConfigPath), basePath, createGetCanonicalFileName(host.useCaseSensitiveFileNames)),
path
));
}
};
setPropertyInRawIfNotUndefined("include");
setPropertyInRawIfNotUndefined("exclude");
setPropertyInRawIfNotUndefined("files");
if (raw.compileOnSave === undefined) {
raw.compileOnSave = baseRaw.compileOnSave;
setPropertyInResultIfNotUndefined("include");
setPropertyInResultIfNotUndefined("exclude");
setPropertyInResultIfNotUndefined("files");
if (extendsRaw.compileOnSave !== undefined) {
result.compileOnSave = extendsRaw.compileOnSave;
}
ownConfig.options = assign({}, extendedConfig.options, ownConfig.options);
ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ?
assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) :
ownConfig.watchOptions || extendedConfig.watchOptions;
assign(result.options, extendedConfig.options);
result.watchOptions = result.watchOptions && extendedConfig.watchOptions ?
assign({}, result.watchOptions, extendedConfig.watchOptions) :
result.watchOptions || extendedConfig.watchOptions;
// TODO extend type typeAcquisition
}
}
return ownConfig;
}
function parseOwnConfigOfJson(
@@ -3166,15 +3221,28 @@ function parseOwnConfigOfJson(
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName);
const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors);
json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
let extendedConfigPath: string | undefined;
let extendedConfigPath: string | string[] | undefined;
if (json.extends) {
if (!isString(json.extends)) {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"));
if (!isCompilerOptionsValue(extendsOptionDeclaration, json.extends)) {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", getCompilerOptionValueTypeString(extendsOptionDeclaration)));
}
else {
const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath;
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic);
if (isString(json.extends)) {
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic);
}
else {
extendedConfigPath = [];
for (const fileName of json.extends as unknown[]) {
if (isString(fileName)) {
extendedConfigPath = append(extendedConfigPath, getExtendsConfigPath(fileName, host, newBase, errors, createCompilerDiagnostic));
}
else {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", getCompilerOptionValueTypeString(extendsOptionDeclaration.element)));
}
}
}
}
}
return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath };
@@ -3190,7 +3258,7 @@ function parseOwnConfigOfJsonSourceFile(
const options = getDefaultCompilerOptions(configFileName);
let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined;
let watchOptions: WatchOptions | undefined;
let extendedConfigPath: string | undefined;
let extendedConfigPath: string | string[] | undefined;
let rootCompilerOptions: PropertyName[] | undefined;
const optionsIterator: JsonConversionNotifier = {
@@ -3219,15 +3287,33 @@ function parseOwnConfigOfJsonSourceFile(
switch (key) {
case "extends":
const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath;
if (isString(value)) {
extendedConfigPath = getExtendsConfigPath(
value as string,
value,
host,
newBase,
errors,
(message, arg0) =>
createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0)
);
return;
}
else {
extendedConfigPath = [];
for (let index = 0; index < (value as unknown[]).length; index++) {
const fileName = (value as unknown[])[index];
if (isString(fileName)) {
extendedConfigPath = append(extendedConfigPath, getExtendsConfigPath(
fileName,
host,
newBase,
errors,
(message, arg0) =>
createDiagnosticForNodeInSourceFile(sourceFile, (valueNode as ArrayLiteralExpression).elements[index], message, arg0)
));
}
}
}
return;
}
},
onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) {
@@ -3301,7 +3387,8 @@ function getExtendedConfig(
host: ParseConfigHost,
resolutionStack: string[],
errors: Push<Diagnostic>,
extendedConfigCache?: Map<string, ExtendedConfigCacheEntry>
extendedConfigCache: Map<string, ExtendedConfigCacheEntry> | undefined,
result: ExtendsResult
): ParsedTsconfig | undefined {
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath);
let value: ExtendedConfigCacheEntry | undefined;
@@ -3321,9 +3408,11 @@ function getExtendedConfig(
}
}
if (sourceFile) {
sourceFile.extendedSourceFiles = [extendedResult.fileName];
(result.extendedSourceFiles ??= new Set()).add(extendedResult.fileName);
if (extendedResult.extendedSourceFiles) {
sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles);
for (const extenedSourceFile of extendedResult.extendedSourceFiles) {
result.extendedSourceFiles.add(extenedSourceFile);
}
}
}
if (extendedResult.parseDiagnostics.length) {
@@ -3416,10 +3505,15 @@ function convertOptionsFromJson(optionsNameMap: Map<string, CommandLineOption>,
export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {
if (isCompilerOptionsValue(opt, value)) {
const optType = opt.type;
if (optType === "list" && isArray(value)) {
if ((optType === "list") && isArray(value)) {
return convertJsonOptionOfListType(opt, value, basePath, errors);
}
else if (!isString(optType)) {
else if (optType === "listOrElement") {
return isArray(value) ?
convertJsonOptionOfListType(opt, value, basePath, errors) :
convertJsonOption(opt.element, value, basePath, errors);
}
else if (!isString(opt.type)) {
return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors);
}
const validatedValue = validateJsonOptionValue(opt, value, errors);
@@ -3432,7 +3526,8 @@ export function convertJsonOption(opt: CommandLineOption, value: any, basePath:
function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue {
if (isNullOrUndefined(value)) return undefined;
if (option.type === "list") {
if (option.type === "listOrElement" && !isArray(value)) return normalizeOptionValue(option.element, basePath, value);
else if (option.type === "list" || option.type === "listOrElement") {
const listOption = option;
if (listOption.element.isFilePath || !isString(listOption.element.type)) {
return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => listOption.listPreserveFalsyValues ? true : !!v) as CompilerOptionsValue;
@@ -3860,6 +3955,9 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption):
return typeof value === "number" ? value : "";
case "boolean":
return typeof value === "boolean" ? value : "";
case "listOrElement":
if (!isArray(value)) return getOptionValueWithEmptyStrings(value, option.element);
// fall through to list
case "list":
const elementType = option.element;
return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : "";
@@ -3873,7 +3971,7 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption):
}
function getDefaultValueForOption(option: CommandLineOption) {
function getDefaultValueForOption(option: CommandLineOption): {} {
switch (option.type) {
case "number":
return 1;
@@ -3884,6 +3982,8 @@ function getDefaultValueForOption(option: CommandLineOption) {
return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : "";
case "list":
return [];
case "listOrElement":
return getDefaultValueForOption(option.element);
case "object":
return {};
default:
+2 -2
View File
@@ -6977,7 +6977,7 @@ export interface CreateProgramOptions {
/** @internal */
export interface CommandLineOptionBase {
name: string;
type: "string" | "number" | "boolean" | "object" | "list" | Map<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
isFilePath?: boolean; // True if option value is a path or fileName
shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help'
description?: DiagnosticMessage; // The message describing what the command line switch does.
@@ -7047,7 +7047,7 @@ export interface TsConfigOnlyOption extends CommandLineOptionBase {
/** @internal */
export interface CommandLineOptionOfListType extends CommandLineOptionBase {
type: "list";
type: "list" | "listOrElement";
element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption;
listPreserveFalsyValues?: boolean;
}
+5 -4
View File
@@ -253,9 +253,9 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign
typeof option.defaultValueDescription === "object"
? getDiagnosticText(option.defaultValueDescription)
: formatDefaultValue(
option.defaultValueDescription,
option.type === "list" ? option.element.type : option.type
);
option.defaultValueDescription,
option.type === "list" || option.type === "listOrElement" ? option.element.type : option.type
);
const terminalWidth = sys.getWidthOfTerminal?.() ?? 0;
// Note: child_process might return `terminalWidth` as undefined.
@@ -365,6 +365,7 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign
};
function getValueType(option: CommandLineOption) {
Debug.assert(option.type !== "listOrElement");
switch (option.type) {
case "string":
case "number":
@@ -386,7 +387,7 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign
possibleValues = option.type;
break;
case "list":
// TODO: check infinite loop
case "listOrElement":
possibleValues = getPossibleValues(option.element);
break;
case "object":
+1
View File
@@ -380,6 +380,7 @@ export namespace Compiler {
}
// If not a primitive, the possible types are specified in what is effectively a map of options.
case "list":
case "listOrElement":
return ts.parseListTypeOption(option, value, errors);
default:
return ts.parseCustomTypeOption(option as ts.CommandLineOptionOfCustomType, value, errors);
+2 -1
View File
@@ -120,7 +120,7 @@ function updateTsconfigFiles(program: Program, changeTracker: textChanges.Change
if (foundExactMatch || propertyName !== "include" || !isArrayLiteralExpression(property.initializer)) return;
const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined);
if (includes.length === 0) return;
const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory);
const matchers = getFileMatcherPatterns(configDir, /*excludes*/[], includes, useCaseSensitiveFileNames, currentDirectory);
// If there isn't some include for this, add a new one.
if (getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(oldFileOrDirPath) &&
!getRegexFromPattern(Debug.checkDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) {
@@ -131,6 +131,7 @@ function updateTsconfigFiles(program: Program, changeTracker: textChanges.Change
case "compilerOptions":
forEachProperty(property.initializer, (property, propertyName) => {
const option = getOptionFromName(propertyName);
Debug.assert(option?.type !== "listOrElement");
if (option && (option.isFilePath || option.type === "list" && option.element.isFilePath)) {
updatePaths(property);
}
@@ -186,7 +186,47 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
"dev/tests/unit/spec.ts": "",
"dev/tests/utils.ts": "",
"dev/tests/scenarios/first.json": "",
"dev/tests/baselines/first/output.ts": ""
"dev/tests/baselines/first/output.ts": "",
"dev/configs/extendsArrayFirst.json": JSON.stringify({
compilerOptions: {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true
}
}),
"dev/configs/extendsArraySecond.json": JSON.stringify({
compilerOptions: {
module: "amd"
},
include: ["../supplemental.*"]
}),
"dev/configs/extendsArrayThird.json": JSON.stringify({
compilerOptions: {
module: null, // eslint-disable-line no-null/no-null
noImplicitAny: false
},
extends: "./extendsArrayFirst",
include: ["../supplemental.*"]
}),
"dev/configs/extendsArrayFourth.json": JSON.stringify({
compilerOptions: {
module: "system",
strictNullChecks: false
},
include: null, // eslint-disable-line no-null/no-null
files: ["../main.ts"]
}),
"dev/configs/extendsArrayFifth.json": JSON.stringify({
extends: ["./extendsArrayFirst", "./extendsArraySecond", "./extendsArrayThird", "./extendsArrayFourth"],
files: [],
}),
"dev/extendsArrayFails.json": JSON.stringify({
extends: ["./missingFile"],
compilerOptions: {
types: []
}
}),
"dev/extendsArrayFails2.json": JSON.stringify({ extends: [42] }),
}
}
});
@@ -295,9 +335,9 @@ describe("unittests:: config:: configurationExtension", () => {
messageText: `Unknown option 'excludes'. Did you mean 'exclude'?`
}]);
testFailure("can error when 'extends' is not a string", "extends.json", [{
testFailure("can error when 'extends' is not a string or Array", "extends.json", [{
code: 5024,
messageText: `Compiler option 'extends' requires a value of type string.`
messageText: `Compiler option 'extends' requires a value of type string or Array.`
}]);
testSuccess("can overwrite compiler options using extended 'null'", "configs/third.json", {
@@ -352,5 +392,40 @@ describe("unittests:: config:: configurationExtension", () => {
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
});
});
describe(testName, () => {
it("adds extendedSourceFiles from an array only once", () => {
const sourceFile = ts.readJsonConfigFile("configs/extendsArrayFifth.json", (path) => host.readFile(path));
const dir = ts.combinePaths(basePath, "configs");
const expected = [
ts.combinePaths(dir, "extendsArrayFirst.json"),
ts.combinePaths(dir, "extendsArraySecond.json"),
ts.combinePaths(dir, "extendsArrayThird.json"),
ts.combinePaths(dir, "extendsArrayFourth.json"),
];
ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "extendsArrayFifth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "extendsArrayFifth.json");
assert.deepEqual(sourceFile.extendedSourceFiles, expected);
});
testSuccess("can overwrite top-level compilerOptions", "configs/extendsArrayFifth.json", {
allowJs: true,
noImplicitAny: false,
strictNullChecks: false,
module: ts.ModuleKind.System
}, []);
testFailure("can report missing configurations", "extendsArrayFails.json", [{
code: 6053,
messageText: `File './missingFile' not found.`
}]);
testFailure("can error when 'extends' is not a string or Array2", "extendsArrayFails2.json", [{
code: 5024,
messageText: `Compiler option 'extends' requires a value of type string.`
}]);
});
});
});
@@ -147,6 +147,10 @@ describe("unittests:: config:: showConfig", () => {
}
break;
}
case "listOrElement": {
ts.Debug.fail();
break;
}
case "string": {
if (option.isTSConfigOnly) {
args = ["-p", "tsconfig.json"];
@@ -597,7 +597,7 @@ export function someFn() { }`),
verifyTscWatch({
scenario: "programUpdates",
subScenario: "works with extended source files",
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json"],
commandLineArgs: ["-b", "-w", "-v", "project1.tsconfig.json", "project2.tsconfig.json", "project3.tsconfig.json"],
sys: () => {
const alphaExtendedConfigFile: File = {
path: "/a/b/alpha.tsconfig.json",
@@ -633,10 +633,49 @@ export function someFn() { }`),
files: [otherFile.path]
})
};
const otherFile2: File = {
path: "/a/b/other2.ts",
content: "let k = 0;",
};
const extendsConfigFile1: File = {
path: "/a/b/extendsConfig1.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
composite: true,
}
})
};
const extendsConfigFile2: File = {
path: "/a/b/extendsConfig2.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
strictNullChecks: false,
}
})
};
const extendsConfigFile3: File = {
path: "/a/b/extendsConfig3.tsconfig.json",
content: JSON.stringify({
compilerOptions: {
noImplicitAny: true,
}
})
};
const project3Config: File = {
path: "/a/b/project3.tsconfig.json",
content: JSON.stringify({
extends: ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json", "./extendsConfig3.tsconfig.json"],
compilerOptions: {
composite: false,
},
files: [otherFile2.path]
})
};
return createWatchedSystem([
libFile,
alphaExtendedConfigFile, project1Config, commonFile1, commonFile2,
bravoExtendedConfigFile, project2Config, otherFile
bravoExtendedConfigFile, project2Config, otherFile, otherFile2,
extendsConfigFile1, extendsConfigFile2, extendsConfigFile3, project3Config
], { currentDirectory: "/a/b" });
},
edits: [
@@ -689,7 +728,37 @@ export function someFn() { }`),
sys.checkTimeoutQueueLength(0);
},
},
]
{
caption: "Modify extendsConfigFile2",
edit: sys => sys.writeFile("/a/b/extendsConfig2.tsconfig.json", JSON.stringify({
compilerOptions: { strictNullChecks: true }
})),
timeouts: sys => { // Build project3
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "Modify project 3",
edit: sys => sys.writeFile("/a/b/project3.tsconfig.json", JSON.stringify({
extends: ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json"],
compilerOptions: { composite: false },
files: ["/a/b/other2.ts"]
})),
timeouts: sys => { // Build project3
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
},
{
caption: "Delete extendedConfigFile2 and report error",
edit: sys => sys.deleteFile("./extendsConfig2.tsconfig.json"),
timeouts: sys => { // Build project3
sys.checkTimeoutQueueLengthAndRun(1);
sys.checkTimeoutQueueLength(0);
},
}
],
});
verifyTscWatch({
+1 -1
View File
@@ -9170,7 +9170,7 @@ declare namespace ts {
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
*/
extendedConfigPath?: string;
extendedConfigPath?: string | string[];
}
interface ExtendedConfigCacheEntry {
extendedResult: TsConfigSourceFile;
+1 -1
View File
@@ -5236,7 +5236,7 @@ declare namespace ts {
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
*/
extendedConfigPath?: string;
extendedConfigPath?: string | string[];
}
interface ExtendedConfigCacheEntry {
extendedResult: TsConfigSourceFile;
@@ -0,0 +1,31 @@
/index.ts(1,12): error TS7006: Parameter 'x' implicitly has an 'any' type.
/index.ts(3,1): error TS2454: Variable 'y' is used before being assigned.
==== /tsconfig.json (0 errors) ====
{
"extends": ["./tsconfig1.json", "./tsconfig2.json"]
}
==== /tsconfig1.json (0 errors) ====
{
"compilerOptions": {
"strictNullChecks": true
}
}
==== /tsconfig2.json (0 errors) ====
{
"compilerOptions": {
"noImplicitAny": true
}
}
==== /index.ts (2 errors) ====
function f(x) { } // noImplicitAny error
~
!!! error TS7006: Parameter 'x' implicitly has an 'any' type.
let y: string;
y.toLowerCase(); // strictNullChecks error
~
!!! error TS2454: Variable 'y' is used before being assigned.
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/configFileExtendsAsList.ts] ////
//// [tsconfig1.json]
{
"compilerOptions": {
"strictNullChecks": true
}
}
//// [tsconfig2.json]
{
"compilerOptions": {
"noImplicitAny": true
}
}
//// [index.ts]
function f(x) { } // noImplicitAny error
let y: string;
y.toLowerCase(); // strictNullChecks error
//// [index.js]
function f(x) { } // noImplicitAny error
var y;
y.toLowerCase(); // strictNullChecks error
@@ -0,0 +1,13 @@
=== /index.ts ===
function f(x) { } // noImplicitAny error
>f : Symbol(f, Decl(index.ts, 0, 0))
>x : Symbol(x, Decl(index.ts, 0, 11))
let y: string;
>y : Symbol(y, Decl(index.ts, 1, 3))
y.toLowerCase(); // strictNullChecks error
>y.toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(index.ts, 1, 3))
>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --))
@@ -0,0 +1,14 @@
=== /index.ts ===
function f(x) { } // noImplicitAny error
>f : (x: any) => void
>x : any
let y: string;
>y : string
y.toLowerCase(); // strictNullChecks error
>y.toLowerCase() : string
>y.toLowerCase : () => string
>y : string
>toLowerCase : () => string
@@ -33,25 +33,45 @@ let y = 1
//// [/a/b/other.ts]
let z = 0;
//// [/a/b/other2.ts]
let k = 0;
/a/lib/tsc.js -b -w -v project1.tsconfig.json project2.tsconfig.json
//// [/a/b/extendsConfig1.tsconfig.json]
{"compilerOptions":{"composite":true}}
//// [/a/b/extendsConfig2.tsconfig.json]
{"compilerOptions":{"strictNullChecks":false}}
//// [/a/b/extendsConfig3.tsconfig.json]
{"compilerOptions":{"noImplicitAny":true}}
//// [/a/b/project3.tsconfig.json]
{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json","./extendsConfig3.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other2.ts"]}
/a/lib/tsc.js -b -w -v project1.tsconfig.json project2.tsconfig.json project3.tsconfig.json
Output::
>> Screen clear
[12:00:25 AM] Starting compilation in watch mode...
[12:00:35 AM] Starting compilation in watch mode...
[12:00:26 AM] Projects in this build:
[12:00:36 AM] Projects in this build:
* project1.tsconfig.json
* project2.tsconfig.json
* project2.tsconfig.json
* project3.tsconfig.json
[12:00:27 AM] Project 'project1.tsconfig.json' is out of date because output file 'project1.tsconfig.tsbuildinfo' does not exist
[12:00:37 AM] Project 'project1.tsconfig.json' is out of date because output file 'project1.tsconfig.tsbuildinfo' does not exist
[12:00:28 AM] Building project '/a/b/project1.tsconfig.json'...
[12:00:38 AM] Building project '/a/b/project1.tsconfig.json'...
[12:00:42 AM] Project 'project2.tsconfig.json' is out of date because output file 'project2.tsconfig.tsbuildinfo' does not exist
[12:00:52 AM] Project 'project2.tsconfig.json' is out of date because output file 'project2.tsconfig.tsbuildinfo' does not exist
[12:00:43 AM] Building project '/a/b/project2.tsconfig.json'...
[12:00:53 AM] Building project '/a/b/project2.tsconfig.json'...
[12:00:53 AM] Found 0 errors. Watching for file changes.
[12:01:03 AM] Project 'project3.tsconfig.json' is out of date because output file 'other2.js' does not exist
[12:01:04 AM] Building project '/a/b/project3.tsconfig.json'...
[12:01:08 AM] Found 0 errors. Watching for file changes.
@@ -88,6 +108,21 @@ Shape signatures in builder refreshed for::
/a/lib/lib.d.ts (used version)
/a/b/other.ts (computed .d.ts during emit)
Program root files: ["/a/b/other2.ts"]
Program options: {"composite":false,"strictNullChecks":false,"noImplicitAny":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"}
Program structureReused: Not
Program files::
/a/lib/lib.d.ts
/a/b/other2.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/a/b/other2.ts
Shape signatures in builder refreshed for::
/a/lib/lib.d.ts (used version)
/a/b/other2.ts (used version)
PolledWatches::
FsWatches::
@@ -105,6 +140,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
@@ -238,6 +283,10 @@ declare let z: number;
"size": 755
}
//// [/a/b/other2.js]
var k = 0;
Change:: Modify alpha config
@@ -248,11 +297,11 @@ Input::
Output::
>> Screen clear
[12:00:57 AM] File change detected. Starting incremental compilation...
[12:01:12 AM] File change detected. Starting incremental compilation...
[12:00:58 AM] Project 'project1.tsconfig.json' is out of date because output 'project1.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:01:13 AM] Project 'project1.tsconfig.json' is out of date because output 'project1.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:00:59 AM] Building project '/a/b/project1.tsconfig.json'...
[12:01:14 AM] Building project '/a/b/project1.tsconfig.json'...
@@ -288,6 +337,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
@@ -368,11 +427,11 @@ Change:: Build project 2
Input::
Output::
[12:01:13 AM] Project 'project2.tsconfig.json' is out of date because output 'project2.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:01:28 AM] Project 'project2.tsconfig.json' is out of date because output 'project2.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:01:14 AM] Building project '/a/b/project2.tsconfig.json'...
[12:01:29 AM] Building project '/a/b/project2.tsconfig.json'...
[12:01:25 AM] Found 0 errors. Watching for file changes.
[12:01:40 AM] Found 0 errors. Watching for file changes.
@@ -406,6 +465,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
@@ -473,13 +542,13 @@ Input::
Output::
>> Screen clear
[12:01:29 AM] File change detected. Starting incremental compilation...
[12:01:44 AM] File change detected. Starting incremental compilation...
[12:01:30 AM] Project 'project2.tsconfig.json' is out of date because output 'project2.tsconfig.tsbuildinfo' is older than input 'bravo.tsconfig.json'
[12:01:45 AM] Project 'project2.tsconfig.json' is out of date because output 'project2.tsconfig.tsbuildinfo' is older than input 'bravo.tsconfig.json'
[12:01:31 AM] Building project '/a/b/project2.tsconfig.json'...
[12:01:46 AM] Building project '/a/b/project2.tsconfig.json'...
[12:01:42 AM] Found 0 errors. Watching for file changes.
[12:01:57 AM] Found 0 errors. Watching for file changes.
@@ -513,6 +582,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
@@ -579,17 +658,17 @@ Input::
Output::
>> Screen clear
[12:01:46 AM] File change detected. Starting incremental compilation...
[12:02:01 AM] File change detected. Starting incremental compilation...
[12:01:47 AM] Project 'project2.tsconfig.json' is out of date because output 'commonFile1.js' is older than input 'project2.tsconfig.json'
[12:02:02 AM] Project 'project2.tsconfig.json' is out of date because output 'other2.js' is older than input 'project2.tsconfig.json'
[12:01:48 AM] Building project '/a/b/project2.tsconfig.json'...
[12:02:03 AM] Building project '/a/b/project2.tsconfig.json'...
[12:01:59 AM] Found 0 errors. Watching for file changes.
[12:02:17 AM] Found 0 errors. Watching for file changes.
Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts"]
Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts","/a/b/other2.ts"]
Program options: {"strict":true,"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"}
Program structureReused: Not
Program files::
@@ -597,17 +676,20 @@ Program files::
/a/b/commonFile1.ts
/a/b/commonFile2.ts
/a/b/other.ts
/a/b/other2.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/a/b/commonFile1.ts
/a/b/commonFile2.ts
/a/b/other.ts
/a/b/other2.ts
Shape signatures in builder refreshed for::
/a/b/commonfile1.ts (computed .d.ts)
/a/b/commonfile2.ts (computed .d.ts)
/a/b/other.ts (computed .d.ts)
/a/b/other2.ts (computed .d.ts)
PolledWatches::
@@ -624,6 +706,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
@@ -638,6 +730,11 @@ exitCode:: ExitStatus.undefined
var z = 0;
//// [/a/b/other2.js]
"use strict";
var k = 0;
Change:: update aplha config
@@ -648,11 +745,11 @@ Input::
Output::
>> Screen clear
[12:02:04 AM] File change detected. Starting incremental compilation...
[12:02:22 AM] File change detected. Starting incremental compilation...
[12:02:05 AM] Project 'project1.tsconfig.json' is out of date because output 'project1.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:02:23 AM] Project 'project1.tsconfig.json' is out of date because output 'project1.tsconfig.tsbuildinfo' is older than input 'alpha.tsconfig.json'
[12:02:06 AM] Building project '/a/b/project1.tsconfig.json'...
[12:02:24 AM] Building project '/a/b/project1.tsconfig.json'...
@@ -686,6 +783,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
@@ -765,15 +872,15 @@ Change:: Build project 2
Input::
Output::
[12:02:20 AM] Project 'project2.tsconfig.json' is out of date because output 'commonFile1.js' is older than input 'alpha.tsconfig.json'
[12:02:38 AM] Project 'project2.tsconfig.json' is out of date because output 'commonFile1.js' is older than input 'alpha.tsconfig.json'
[12:02:21 AM] Building project '/a/b/project2.tsconfig.json'...
[12:02:39 AM] Building project '/a/b/project2.tsconfig.json'...
[12:02:32 AM] Found 0 errors. Watching for file changes.
[12:02:53 AM] Found 0 errors. Watching for file changes.
Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts"]
Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts","/a/b/other2.ts"]
Program options: {"watch":true,"configFilePath":"/a/b/project2.tsconfig.json"}
Program structureReused: Not
Program files::
@@ -781,12 +888,14 @@ Program files::
/a/b/commonFile1.ts
/a/b/commonFile2.ts
/a/b/other.ts
/a/b/other2.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/a/b/commonFile1.ts
/a/b/commonFile2.ts
/a/b/other.ts
/a/b/other2.ts
No shapes updated in the builder::
@@ -805,6 +914,16 @@ FsWatches::
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
@@ -818,3 +937,189 @@ exitCode:: ExitStatus.undefined
var z = 0;
//// [/a/b/other2.js]
var k = 0;
Change:: Modify extendsConfigFile2
Input::
//// [/a/b/extendsConfig2.tsconfig.json]
{"compilerOptions":{"strictNullChecks":true}}
Output::
>> Screen clear
[12:02:57 AM] File change detected. Starting incremental compilation...
[12:02:58 AM] Project 'project3.tsconfig.json' is out of date because output 'other2.js' is older than input 'extendsConfig2.tsconfig.json'
[12:02:59 AM] Building project '/a/b/project3.tsconfig.json'...
[12:03:00 AM] Updating unchanged output timestamps of project '/a/b/project3.tsconfig.json'...
[12:03:02 AM] Found 0 errors. Watching for file changes.
Program root files: ["/a/b/other2.ts"]
Program options: {"composite":false,"strictNullChecks":true,"noImplicitAny":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"}
Program structureReused: Not
Program files::
/a/lib/lib.d.ts
/a/b/other2.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/a/b/other2.ts
No shapes updated in the builder::
PolledWatches::
FsWatches::
/a/b/project1.tsconfig.json:
{}
/a/b/alpha.tsconfig.json:
{}
/a/b/commonfile1.ts:
{}
/a/b/commonfile2.ts:
{}
/a/b/project2.tsconfig.json:
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/extendsconfig3.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
{}
exitCode:: ExitStatus.undefined
//// [/a/b/other2.js] file changed its modified time
Change:: Modify project 3
Input::
//// [/a/b/project3.tsconfig.json]
{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other2.ts"]}
Output::
>> Screen clear
[12:03:06 AM] File change detected. Starting incremental compilation...
[12:03:07 AM] Project 'project3.tsconfig.json' is out of date because output 'other2.js' is older than input 'project3.tsconfig.json'
[12:03:08 AM] Building project '/a/b/project3.tsconfig.json'...
[12:03:09 AM] Updating unchanged output timestamps of project '/a/b/project3.tsconfig.json'...
[12:03:11 AM] Found 0 errors. Watching for file changes.
Program root files: ["/a/b/other2.ts"]
Program options: {"composite":false,"strictNullChecks":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"}
Program structureReused: Not
Program files::
/a/lib/lib.d.ts
/a/b/other2.ts
Semantic diagnostics in builder refreshed for::
/a/lib/lib.d.ts
/a/b/other2.ts
No shapes updated in the builder::
PolledWatches::
FsWatches::
/a/b/project1.tsconfig.json:
{}
/a/b/alpha.tsconfig.json:
{}
/a/b/commonfile1.ts:
{}
/a/b/commonfile2.ts:
{}
/a/b/project2.tsconfig.json:
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
{}
exitCode:: ExitStatus.undefined
//// [/a/b/other2.js] file changed its modified time
Change:: Delete extendedConfigFile2 and report error
Input::
//// [/a/b/extendsConfig2.tsconfig.json] deleted
Output::
>> Screen clear
[12:03:13 AM] File change detected. Starting incremental compilation...
[12:03:14 AM] Project 'project3.tsconfig.json' is up to date because newest input 'other2.ts' is older than output 'other2.js'
error TS5083: Cannot read file '/a/b/extendsConfig2.tsconfig.json'.
[12:03:15 AM] Found 1 error. Watching for file changes.
PolledWatches::
FsWatches::
/a/b/project1.tsconfig.json:
{}
/a/b/alpha.tsconfig.json:
{}
/a/b/commonfile1.ts:
{}
/a/b/commonfile2.ts:
{}
/a/b/project2.tsconfig.json:
{}
/a/b/other.ts:
{}
/a/b/project3.tsconfig.json:
{}
/a/b/extendsconfig1.tsconfig.json:
{}
/a/b/extendsconfig2.tsconfig.json:
{}
/a/b/other2.ts:
{}
FsWatchesRecursive::
/a/b:
{}
exitCode:: ExitStatus.undefined
@@ -0,0 +1,23 @@
// @Filename: /tsconfig1.json
{
"compilerOptions": {
"strictNullChecks": true
}
}
// @Filename: /tsconfig2.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
// @Filename: /tsconfig.json
{
"extends": ["./tsconfig1.json", "./tsconfig2.json"]
}
// @Filename: /index.ts
function f(x) { } // noImplicitAny error
let y: string;
y.toLowerCase(); // strictNullChecks error