From 3b6917ca4d75bbb0fe2142496606328f7b94d29a Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 22 Aug 2022 09:08:34 -0700 Subject: [PATCH 01/12] tsconfig.extends as array --- src/compiler/commandLineParser.ts | 227 ++++++++--- src/compiler/types.ts | 4 +- src/executeCommandLine/executeCommandLine.ts | 8 + .../config/configurationExtension.ts | 79 +++- src/testRunner/unittests/config/showConfig.ts | 20 + .../unittests/tsbuildWatch/programUpdates.ts | 57 ++- .../works-with-extended-source-files.js | 368 ++++++++++++++++-- 7 files changed, 670 insertions(+), 93 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 370461abd8559..1d17d1b8a742c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1731,6 +1731,19 @@ namespace ts { i++; } break; + case "listOrElement": + if(opt.element.type === "string"){ + options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); + i++; + } + else{ + const result = parseListTypeOption(opt, args[i], errors); + options[opt.name] = result || []; + if (result) { + i++; + } + } + 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); @@ -1971,6 +1984,15 @@ namespace ts { 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) { @@ -2002,11 +2024,7 @@ namespace ts { elementOptions: getCommandLineTypeAcquisitionMap(), extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics }, - { - name: "extends", - type: "string", - category: Diagnostics.File_Management, - }, + extendsOptionDeclaration, { name: "references", type: "list", @@ -2219,11 +2237,11 @@ namespace ts { 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: @@ -2234,7 +2252,7 @@ namespace ts { 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" || option.element.type !== "string")); const text = (valueExpression as StringLiteral).text; if (option && !isString(option.type)) { const customOption = option as CommandLineOptionOfCustomType; @@ -2252,18 +2270,18 @@ namespace ts { 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" @@ -2284,7 +2302,7 @@ namespace ts { } 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)); @@ -2324,8 +2342,10 @@ namespace ts { } } - 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"; } @@ -2336,6 +2356,9 @@ namespace ts { 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; } @@ -2445,6 +2468,9 @@ namespace ts { else if (optionDefinition.type === "list") { return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } + else if(optionDefinition.type === "listOrElement"){ + return (optionDefinition.element.type === "string") ? undefined : getCustomTypeMapOfCommandLineOption(optionDefinition.element); + } else { return optionDefinition.type; } @@ -2669,6 +2695,17 @@ namespace ts { return values.map(toAbsolutePath); } } + else if(option.type === "listOrElement"){ + if(option.element.type === "string"){ + return toAbsolutePath(value as string); + } + else{ + const values = value as readonly (string | number)[]; + if (option.element.isFilePath && values.length) { + return values.map(toAbsolutePath); + } + } + } else if (option.isFilePath) { return toAbsolutePath(value as string); } @@ -2943,17 +2980,19 @@ 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[]; } function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { return !!value.options; } + interface ExtendsResult { options: CompilerOptions, watchOptions?: WatchOptions, include?: string[], exclude?: string[], files?: string[], compileOnSave?: boolean, extendedSourceFiles?: Set} /** * This *just* extracts options/include/exclude/files out of a config file. * It does *not* resolve the included files. */ + function parseConfig( json: any, sourceFile: TsConfigSourceFile | undefined, @@ -2986,34 +3025,52 @@ namespace ts { 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; + const extendsRaw = extendedConfig.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 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( @@ -3033,15 +3090,27 @@ namespace ts { 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[] | 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 = append (extendedConfigPath, getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic)); + } + else { + 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 }; @@ -3057,7 +3126,7 @@ namespace ts { const options = getDefaultCompilerOptions(configFileName); let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined; let watchOptions: WatchOptions | undefined; - let extendedConfigPath: string | undefined; + let extendedConfigPath: string[] | undefined; let rootCompilerOptions: PropertyName[] | undefined; const optionsIterator: JsonConversionNotifier = { @@ -3083,19 +3152,35 @@ namespace ts { currentOption[option.name] = normalizeOptionValue(option, basePath, value); }, onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { - switch (key) { - case "extends": - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - extendedConfigPath = getExtendsConfigPath( - value as string, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - return; - } + switch (key) { + case "extends": + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + if (isString(value)) { + extendedConfigPath = append (extendedConfigPath, getExtendsConfigPath( + value, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + )); + } + else { + for (const fileName of value as unknown[]) { + if (isString(fileName)) { + extendedConfigPath = append(extendedConfigPath, getExtendsConfigPath( + fileName, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, (valueNode as ArrayLiteralExpression).elements[0], message, arg0) + )); + } + }; + }; + return; + } }, onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { if (key === "excludes") { @@ -3168,7 +3253,8 @@ namespace ts { host: ParseConfigHost, resolutionStack: string[], errors: Push, - extendedConfigCache?: ESMap + extendedConfigCache: ESMap | undefined, + result: ExtendsResult ): ParsedTsconfig | undefined { const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); let value: ExtendedConfigCacheEntry | undefined; @@ -3188,9 +3274,11 @@ namespace ts { } } if (sourceFile) { - sourceFile.extendedSourceFiles = [extendedResult.fileName]; - if (extendedResult.extendedSourceFiles) { - sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + (result.extendedSourceFiles ??= new Set()).add(extendedResult.fileName); + if (isArray(extendedResult.extendedSourceFiles)) { + for (const extenedSourceFile of extendedResult.extendedSourceFiles) { + result.extendedSourceFiles.add(extenedSourceFile); + } } } if (extendedResult.parseDiagnostics.length) { @@ -3286,6 +3374,11 @@ namespace ts { if (optType === "list" && isArray(value)) { return convertJsonOptionOfListType(opt , value, basePath, errors); } + else if (optType === "listOrElement") { + return isArray(value) ? + convertJsonOptionOfListType(opt, value, basePath, errors) : + convertJsonOption(opt.element, value, basePath, errors); + } else if (!isString(optType)) { return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); } @@ -3306,6 +3399,18 @@ namespace ts { } return value; } + if(option.type === "listOrElement"){ + if (option.element.type === "string"){ + return normalizeNonListOptionValue(option, basePath, value); + } + else{ + 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; + } + return value; + } + } else if (!isString(option.type)) { return option.type.get(isString(value) ? value.toLowerCase() : value); } @@ -3728,6 +3833,12 @@ namespace ts { case "list": const elementType = option.element; return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; + case "listOrElement": + if(option.element.type === "string"){ + return ""; + } + const typeOfElement = option.element; + return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, typeOfElement)) : ""; default: return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { if (optionEnumValue === value) { @@ -3749,6 +3860,12 @@ namespace ts { return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; case "list": return []; + case "listOrElement": + if (option.element.type === "string"){ + const defaultValue = option.defaultValueDescription; + return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; + } + return []; case "object": return {}; default: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f7567053c89cf..1671b909531b7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6598,7 +6598,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | ESMap; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | ESMap ; // 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. @@ -6669,7 +6669,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfListType extends CommandLineOptionBase { - type: "list"; + type: "list" | "listOrElement"; element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption; listPreserveFalsyValues?: boolean; } diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 68d2e51e9d4eb..13d1292442169 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -300,6 +300,14 @@ namespace ts { // TODO: check infinite loop possibleValues = getPossibleValues(option.element); break; + case "listOrElement": + if (option.element.type === "string"){ + possibleValues = option.type; + } + else { + possibleValues = getPossibleValues(option.element); + } + break; case "object": possibleValues = ""; break; diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index ad906411ffbca..058c21d907140 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -183,7 +183,46 @@ namespace ts { "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 + }, + 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: [""], + compilerOptions: { + types: [] + } + }), + "dev/extendsArrayFails2.json": JSON.stringify({ extends: [42] }), } } }); @@ -292,9 +331,9 @@ namespace ts { 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", { @@ -349,6 +388,40 @@ namespace ts { assert.deepEqual(sourceFile.extendedSourceFiles, expected); }); }); + + describe(testName, () => { + it("adds extendedSourceFiles from an array only once", () => { + const sourceFile = readJsonConfigFile("configs/extendsArrayFifth.json", (path) => host.readFile(path)); + const dir = combinePaths(basePath, "configs"); + const expected = [ + combinePaths(dir, "extendsArrayFirst.json"), + combinePaths(dir, "extendsArraySecond.json"), + combinePaths(dir, "extendsArrayThird.json"), + combinePaths(dir, "extendsArrayFourth.json"), + ]; + parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "extendsArrayFifth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); + 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: ModuleKind.System + }, []); + + testFailure("can report missing configurations", "extendsArrayFails.json", [{ + code: 6053, + messageText: `File '' 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.` + }]); + }); }); }); } diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index f8905e3412677..8a18303a71764 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -145,6 +145,26 @@ namespace ts { } break; } + case "listOrElement": { + if (option.isTSConfigOnly) { + args = ["-p", "tsconfig.json"]; + if(option.element.type === "string"){ + optionValue = { [option.name]: "someString" }; + } + else{ + optionValue = { [option.name]: [] }; + } + } + else { + if(option.element.type === "string"){ + args = [`--${option.name}`, "someString"]; + } + else{ + args = [`--${option.name}`]; + } + } + break; + } case "string": { if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 58d912b30c5a0..2699cc20660c0 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -566,7 +566,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", @@ -602,10 +602,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: ["./extendsCnfig1.tsconfig.json", "./extendsConfig2.tsconfig.json", "./extendsConfig3.tsconfig.json"], + compilerOptions: { + composite: false, + }, + files: [otherFile.path] + }) + }; return createWatchedSystem([ libFile, alphaExtendedConfigFile, project1Config, commonFile1, commonFile2, - bravoExtendedConfigFile, project2Config, otherFile + bravoExtendedConfigFile, project2Config, otherFile, otherFile2, + extendsConfigFile1, extendsConfigFile2, extendsConfigFile3, project3Config ], { currentDirectory: "/a/b" }); }, changes: [ @@ -646,6 +685,20 @@ export function someFn() { }`), change: noop, timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project2 }, + { + caption: "Modify extendsConfigFile2", + change: sys => sys.writeFile("/a/b/extendsConfig2.tsconfig.json", JSON.stringify({ + compilerOptions: { strictNullChecks: true } + })), + timeouts: checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout // Build project1 + }, + { + caption: "Modify project 3", + change: sys => sys.writeFile("/a/b/project3.tsconfig.json", JSON.stringify({ + extends: ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json"], + })), + timeouts: checkSingleTimeoutQueueLengthAndRun // Build project1 + }, ] }); diff --git a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js index 84c9d4532a8be..3cf8f534f9ac4 100644 --- a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js +++ b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js @@ -33,25 +33,43 @@ 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/other.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:37 AM] Project 'project1.tsconfig.json' is out of date because output file 'project1.tsconfig.tsbuildinfo' does not exist -[12:00:27 AM] Project 'project1.tsconfig.json' is out of date because output file 'project1.tsconfig.tsbuildinfo' does not exist +[12:00:38 AM] Building project '/a/b/project1.tsconfig.json'... -[12:00:28 AM] Building project '/a/b/project1.tsconfig.json'... +[12:00:52 AM] Project 'project2.tsconfig.json' is out of date because output file 'project2.tsconfig.tsbuildinfo' does not exist -[12:00:42 AM] Project 'project2.tsconfig.json' is out of date because output file 'project2.tsconfig.tsbuildinfo' does not exist +[12:00:53 AM] Building project '/a/b/project2.tsconfig.json'... -[12:00:43 AM] Building project '/a/b/project2.tsconfig.json'... +[12:01:03 AM] Project 'project3.tsconfig.json' is up to date because newest input 'other.ts' is older than output 'other.js' -[12:00:53 AM] Found 0 errors. Watching for file changes. +[12:01:04 AM] Found 0 errors. Watching for file changes. @@ -103,6 +121,14 @@ WatchedFiles:: {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} FsWatches:: @@ -225,11 +251,11 @@ Input:: Output:: >> Screen clear -[12:00:57 AM] File change detected. Starting incremental compilation... +[12:01:08 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:09 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:10 AM] Building project '/a/b/project1.tsconfig.json'... @@ -263,6 +289,14 @@ WatchedFiles:: {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} FsWatches:: @@ -331,11 +365,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:24 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:25 AM] Building project '/a/b/project2.tsconfig.json'... -[12:01:25 AM] Found 0 errors. Watching for file changes. +[12:01:36 AM] Found 0 errors. Watching for file changes. @@ -367,6 +401,14 @@ WatchedFiles:: {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} FsWatches:: @@ -427,13 +469,13 @@ Input:: Output:: >> Screen clear -[12:01:29 AM] File change detected. Starting incremental compilation... +[12:01:40 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:41 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:42 AM] Building project '/a/b/project2.tsconfig.json'... -[12:01:42 AM] Found 0 errors. Watching for file changes. +[12:01:53 AM] Found 0 errors. Watching for file changes. @@ -465,6 +507,14 @@ WatchedFiles:: {"fileName":"/a/b/bravo.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} FsWatches:: @@ -524,17 +574,17 @@ Input:: Output:: >> Screen clear -[12:01:46 AM] File change detected. Starting incremental compilation... +[12:01:57 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:01:58 AM] Project 'project2.tsconfig.json' is out of date because output file 'other2.js' does not exist -[12:01:48 AM] Building project '/a/b/project2.tsconfig.json'... +[12:01:59 AM] Building project '/a/b/project2.tsconfig.json'... -[12:01:59 AM] Found 0 errors. Watching for file changes. +[12:02:12 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:: @@ -542,17 +592,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) WatchedFiles:: /a/b/project1.tsconfig.json: @@ -567,6 +620,16 @@ WatchedFiles:: {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} +/a/b/other2.ts: + {"fileName":"/a/b/other2.ts","pollingInterval":250} FsWatches:: @@ -583,6 +646,11 @@ exitCode:: ExitStatus.undefined var z = 0; +//// [/a/b/other2.js] +"use strict"; +var k = 0; + + Change:: update aplha config @@ -593,11 +661,11 @@ Input:: Output:: >> Screen clear -[12:02:04 AM] File change detected. Starting incremental compilation... +[12:02:17 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:18 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:19 AM] Building project '/a/b/project1.tsconfig.json'... @@ -629,6 +697,16 @@ WatchedFiles:: {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} +/a/b/other2.ts: + {"fileName":"/a/b/other2.ts","pollingInterval":250} FsWatches:: @@ -696,15 +774,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:33 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:34 AM] Building project '/a/b/project2.tsconfig.json'... -[12:02:32 AM] Found 0 errors. Watching for file changes. +[12:02:48 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:: @@ -712,12 +790,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:: @@ -734,6 +814,16 @@ WatchedFiles:: {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} /a/b/other.ts: {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} +/a/b/other2.ts: + {"fileName":"/a/b/other2.ts","pollingInterval":250} FsWatches:: @@ -749,3 +839,219 @@ 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:52 AM] File change detected. Starting incremental compilation... + +[12:02:53 AM] Project 'project3.tsconfig.json' is out of date because output 'other.js' is older than input 'extendsConfig2.tsconfig.json' + +[12:02:54 AM] Building project '/a/b/project3.tsconfig.json'... + +[12:02:59 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/other.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/other.ts + +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts + +Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) +/a/b/other.ts (used version) + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig3.tsconfig.json: + {"fileName":"/a/b/extendsConfig3.tsconfig.json","pollingInterval":250} +/a/b/other2.ts: + {"fileName":"/a/b/other2.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b: + {"directoryName":"/a/b"} + +exitCode:: ExitStatus.undefined + +//// [/a/b/other.js] file written with same contents + +Change:: Modify project 3 + +Input:: +//// [/a/b/project3.tsconfig.json] +{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json"]} + + +Output:: +>> Screen clear +[12:03:03 AM] File change detected. Starting incremental compilation... + +[12:03:04 AM] Project 'project3.tsconfig.json' is out of date because output file 'project3.tsconfig.tsbuildinfo' does not exist + +[12:03:05 AM] Building project '/a/b/project3.tsconfig.json'... + +[12:03:34 AM] Found 0 errors. Watching for file changes. + + + +Program root files: ["/a/b/commonFile1.ts","/a/b/commonFile2.ts","/a/b/other.ts","/a/b/other2.ts"] +Program options: {"composite":true,"strictNullChecks":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} +Program structureReused: Not +Program files:: +/a/lib/lib.d.ts +/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) + +WatchedFiles:: +/a/b/project1.tsconfig.json: + {"fileName":"/a/b/project1.tsconfig.json","pollingInterval":250} +/a/b/alpha.tsconfig.json: + {"fileName":"/a/b/alpha.tsconfig.json","pollingInterval":250} +/a/b/commonfile1.ts: + {"fileName":"/a/b/commonFile1.ts","pollingInterval":250} +/a/b/commonfile2.ts: + {"fileName":"/a/b/commonFile2.ts","pollingInterval":250} +/a/b/project2.tsconfig.json: + {"fileName":"/a/b/project2.tsconfig.json","pollingInterval":250} +/a/b/other.ts: + {"fileName":"/a/b/other.ts","pollingInterval":250} +/a/b/project3.tsconfig.json: + {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig2.tsconfig.json: + {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} +/a/b/other2.ts: + {"fileName":"/a/b/other2.ts","pollingInterval":250} + +FsWatches:: + +FsWatchesRecursive:: +/a/b: + {"directoryName":"/a/b"} + {"directoryName":"/a/b"} + +exitCode:: ExitStatus.undefined + +//// [/a/b/commonFile1.js] file written with same contents +//// [/a/b/commonFile1.d.ts] file written with same contents +//// [/a/b/commonFile2.js] file written with same contents +//// [/a/b/commonFile2.d.ts] file written with same contents +//// [/a/b/other.js] file written with same contents +//// [/a/b/other.d.ts] file written with same contents +//// [/a/b/other2.js] file written with same contents +//// [/a/b/other2.d.ts] +declare let k: number; + + +//// [/a/b/project3.tsconfig.tsbuildinfo] +{"program":{"fileNames":["../lib/lib.d.ts","./commonfile1.ts","./commonfile2.ts","./other.ts","./other2.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2167136208-let x = 1","signature":"2842409786-declare let x: number;\n","affectsGlobalScope":true},{"version":"2168322129-let y = 1","signature":"784887931-declare let y: number;\n","affectsGlobalScope":true},{"version":"2874288940-let z = 0;","signature":"-1272633924-declare let z: number;\n","affectsGlobalScope":true},{"version":"2287258045-let k = 0;","signature":"3820390125-declare let k: number;\n","affectsGlobalScope":true}],"options":{"composite":true,"strictNullChecks":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,3,4,5,1],"latestChangedDtsFile":"./other2.d.ts"},"version":"FakeTSVersion"} + +//// [/a/b/project3.tsconfig.tsbuildinfo.readable.baseline.txt] +{ + "program": { + "fileNames": [ + "../lib/lib.d.ts", + "./commonfile1.ts", + "./commonfile2.ts", + "./other.ts", + "./other2.ts" + ], + "fileInfos": { + "../lib/lib.d.ts": { + "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", + "affectsGlobalScope": true + }, + "./commonfile1.ts": { + "version": "2167136208-let x = 1", + "signature": "2842409786-declare let x: number;\n", + "affectsGlobalScope": true + }, + "./commonfile2.ts": { + "version": "2168322129-let y = 1", + "signature": "784887931-declare let y: number;\n", + "affectsGlobalScope": true + }, + "./other.ts": { + "version": "2874288940-let z = 0;", + "signature": "-1272633924-declare let z: number;\n", + "affectsGlobalScope": true + }, + "./other2.ts": { + "version": "2287258045-let k = 0;", + "signature": "3820390125-declare let k: number;\n", + "affectsGlobalScope": true + } + }, + "options": { + "composite": true, + "strictNullChecks": true + }, + "referencedMap": {}, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "./commonfile1.ts", + "./commonfile2.ts", + "./other.ts", + "./other2.ts", + "../lib/lib.d.ts" + ], + "latestChangedDtsFile": "./other2.d.ts" + }, + "version": "FakeTSVersion", + "size": 1171 +} + From a50904ebebb3156a2325118dc374cdc19e8d20fd Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 22 Aug 2022 13:41:34 -0700 Subject: [PATCH 02/12] Updated baselines --- .../reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- .../works-with-extended-source-files.js | 82 ++++++++++--------- 3 files changed, 47 insertions(+), 39 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 103b3664e8b14..03f6bd87bd673 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4949,7 +4949,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[]; } export interface ExtendedConfigCacheEntry { extendedResult: TsConfigSourceFile; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3ea4fe3130e4b..081139da3aa8e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4949,7 +4949,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[]; } export interface ExtendedConfigCacheEntry { extendedResult: TsConfigSourceFile; diff --git a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js index 3cf8f534f9ac4..7db2e2c1c0fb8 100644 --- a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js +++ b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js @@ -46,7 +46,7 @@ let k = 0; {"compilerOptions":{"noImplicitAny":true}} //// [/a/b/project3.tsconfig.json] -{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json","./extendsConfig3.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other.ts"]} +{"extends":["./extendsCnfig1.tsconfig.json","./extendsConfig2.tsconfig.json","./extendsConfig3.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other.ts"]} /a/lib/tsc.js -b -w -v project1.tsconfig.json project2.tsconfig.json project3.tsconfig.json @@ -69,7 +69,9 @@ Output:: [12:01:03 AM] Project 'project3.tsconfig.json' is up to date because newest input 'other.ts' is older than output 'other.js' -[12:01:04 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:01:04 AM] Found 1 error. Watching for file changes. @@ -123,8 +125,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -291,8 +293,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -369,7 +371,9 @@ Output:: [12:01:25 AM] Building project '/a/b/project2.tsconfig.json'... -[12:01:36 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:01:36 AM] Found 1 error. Watching for file changes. @@ -403,8 +407,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -475,7 +479,9 @@ Output:: [12:01:42 AM] Building project '/a/b/project2.tsconfig.json'... -[12:01:53 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:01:53 AM] Found 1 error. Watching for file changes. @@ -509,8 +515,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -580,7 +586,9 @@ Output:: [12:01:59 AM] Building project '/a/b/project2.tsconfig.json'... -[12:02:12 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:02:12 AM] Found 1 error. Watching for file changes. @@ -622,8 +630,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -699,8 +707,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -778,7 +786,9 @@ Output:: [12:02:34 AM] Building project '/a/b/project2.tsconfig.json'... -[12:02:48 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:02:48 AM] Found 1 error. Watching for file changes. @@ -816,8 +826,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -859,24 +869,22 @@ Output:: [12:02:54 AM] Building project '/a/b/project3.tsconfig.json'... -[12:02:59 AM] Found 0 errors. Watching for file changes. +error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. + +[12:02:55 AM] Found 1 error. Watching for file changes. Program root files: ["/a/b/other.ts"] -Program options: {"composite":false,"strictNullChecks":true,"noImplicitAny":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} +Program options: {"strictNullChecks":true,"noImplicitAny":true,"composite":false,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts /a/b/other.ts -Semantic diagnostics in builder refreshed for:: -/a/lib/lib.d.ts -/a/b/other.ts +No cached semantic diagnostics in the builder:: -Shape signatures in builder refreshed for:: -/a/lib/lib.d.ts (used version) -/a/b/other.ts (used version) +No shapes updated in the builder:: WatchedFiles:: /a/b/project1.tsconfig.json: @@ -893,8 +901,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} +/a/b/extendscnfig1.tsconfig.json: + {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -910,7 +918,6 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined -//// [/a/b/other.js] file written with same contents Change:: Modify project 3 @@ -921,13 +928,13 @@ Input:: Output:: >> Screen clear -[12:03:03 AM] File change detected. Starting incremental compilation... +[12:02:59 AM] File change detected. Starting incremental compilation... -[12:03:04 AM] Project 'project3.tsconfig.json' is out of date because output file 'project3.tsconfig.tsbuildinfo' does not exist +[12:03:00 AM] Project 'project3.tsconfig.json' is out of date because output file 'project3.tsconfig.tsbuildinfo' does not exist -[12:03:05 AM] Building project '/a/b/project3.tsconfig.json'... +[12:03:01 AM] Building project '/a/b/project3.tsconfig.json'... -[12:03:34 AM] Found 0 errors. Watching for file changes. +[12:03:30 AM] Found 0 errors. Watching for file changes. @@ -949,6 +956,7 @@ Semantic diagnostics in builder refreshed for:: /a/b/other2.ts Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) /a/b/commonfile1.ts (computed .d.ts) /a/b/commonfile2.ts (computed .d.ts) /a/b/other.ts (computed .d.ts) @@ -969,12 +977,12 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/other2.ts: {"fileName":"/a/b/other2.ts","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} FsWatches:: From 1717b5338d913b494627d4309d9f332d07132321 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 23 Aug 2022 14:11:28 -0700 Subject: [PATCH 03/12] Changes for pr --- src/compiler/commandLineParser.ts | 58 +++++-------- .../config/configurationExtension.ts | 5 +- .../unittests/tsbuildWatch/programUpdates.ts | 2 +- .../works-with-extended-source-files.js | 82 +++++++++---------- 4 files changed, 63 insertions(+), 84 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 1d17d1b8a742c..811d760e7c07f 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1731,7 +1731,7 @@ namespace ts { i++; } break; - case "listOrElement": + case "listOrElement": if(opt.element.type === "string"){ options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); i++; @@ -2465,12 +2465,9 @@ namespace ts { // this is of a type CommandLineOptionOfPrimitiveType return undefined; } - else if (optionDefinition.type === "list") { + else if (optionDefinition.type === "list" || optionDefinition.type === "listOrElement") { return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } - else if(optionDefinition.type === "listOrElement"){ - return (optionDefinition.element.type === "string") ? undefined : getCustomTypeMapOfCommandLineOption(optionDefinition.element); - } else { return optionDefinition.type; } @@ -2987,12 +2984,19 @@ namespace ts { return !!value.options; } - interface ExtendsResult { options: CompilerOptions, watchOptions?: WatchOptions, include?: string[], exclude?: string[], files?: string[], compileOnSave?: boolean, extendedSourceFiles?: Set} + interface ExtendsResult { + options: CompilerOptions, + watchOptions?: WatchOptions, + include?: string[], + exclude?: string[], + files?: string[], + compileOnSave?: boolean, + extendedSourceFiles?: Set + } /** * This *just* extracts options/include/exclude/files out of a config file. * It does *not* resolve the included files. */ - function parseConfig( json: any, sourceFile: TsConfigSourceFile | undefined, @@ -3025,7 +3029,7 @@ namespace ts { if (ownConfig.extendedConfigPath) { // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. resolutionStack = resolutionStack.concat([resolvedPath]); - const result: ExtendsResult = {options: {}}; + const result: ExtendsResult = { options:{} }; if(isString(ownConfig.extendedConfigPath)){ applyExtendedConfig(result, ownConfig.extendedConfigPath); } @@ -3035,7 +3039,7 @@ namespace ts { 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 (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); @@ -3090,7 +3094,7 @@ namespace ts { 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[] | undefined; if (json.extends) { if (!isCompilerOptionsValue(extendsOptionDeclaration, json.extends)) { @@ -3275,7 +3279,7 @@ namespace ts { } if (sourceFile) { (result.extendedSourceFiles ??= new Set()).add(extendedResult.fileName); - if (isArray(extendedResult.extendedSourceFiles)) { + if (extendedResult.extendedSourceFiles) { for (const extenedSourceFile of extendedResult.extendedSourceFiles) { result.extendedSourceFiles.add(extenedSourceFile); } @@ -3392,25 +3396,14 @@ namespace ts { 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; } return value; } - if(option.type === "listOrElement"){ - if (option.element.type === "string"){ - return normalizeNonListOptionValue(option, basePath, value); - } - else{ - 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; - } - return value; - } - } else if (!isString(option.type)) { return option.type.get(isString(value) ? value.toLowerCase() : value); } @@ -3830,15 +3823,12 @@ namespace ts { 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)) : ""; - case "listOrElement": - if(option.element.type === "string"){ - return ""; - } - const typeOfElement = option.element; - return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, typeOfElement)) : ""; default: return forEachEntry(option.type, (optionEnumValue, optionStringValue) => { if (optionEnumValue === value) { @@ -3849,7 +3839,7 @@ namespace ts { } - function getDefaultValueForOption(option: CommandLineOption) { + function getDefaultValueForOption(option: CommandLineOption): {} { switch (option.type) { case "number": return 1; @@ -3861,11 +3851,7 @@ namespace ts { case "list": return []; case "listOrElement": - if (option.element.type === "string"){ - const defaultValue = option.defaultValueDescription; - return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; - } - return []; + return getDefaultValueForOption(option.element); case "object": return {}; default: diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index 058c21d907140..230c70f95887e 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -202,6 +202,7 @@ namespace ts { module: null, // eslint-disable-line no-null/no-null noImplicitAny: false }, + extends: "./extendsArrayFirst", include: ["../supplemental.*"] }), "dev/configs/extendsArrayFourth.json": JSON.stringify({ @@ -217,7 +218,7 @@ namespace ts { files: [], }), "dev/extendsArrayFails.json": JSON.stringify({ - extends: [""], + extends: ["./missingFile"], compilerOptions: { types: [] } @@ -414,7 +415,7 @@ namespace ts { testFailure("can report missing configurations", "extendsArrayFails.json", [{ code: 6053, - messageText: `File '' not found.` + messageText: `File './missingFile' not found.` }]); testFailure("can error when 'extends' is not a string or Array2", "extendsArrayFails2.json", [{ diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 2699cc20660c0..4c3dafa862b84 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -633,7 +633,7 @@ export function someFn() { }`), const project3Config: File = { path: "/a/b/project3.tsconfig.json", content: JSON.stringify({ - extends: ["./extendsCnfig1.tsconfig.json", "./extendsConfig2.tsconfig.json", "./extendsConfig3.tsconfig.json"], + extends: ["./extendsConfig1.tsconfig.json", "./extendsConfig2.tsconfig.json", "./extendsConfig3.tsconfig.json"], compilerOptions: { composite: false, }, diff --git a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js index 7db2e2c1c0fb8..3cf8f534f9ac4 100644 --- a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js +++ b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js @@ -46,7 +46,7 @@ let k = 0; {"compilerOptions":{"noImplicitAny":true}} //// [/a/b/project3.tsconfig.json] -{"extends":["./extendsCnfig1.tsconfig.json","./extendsConfig2.tsconfig.json","./extendsConfig3.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other.ts"]} +{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json","./extendsConfig3.tsconfig.json"],"compilerOptions":{"composite":false},"files":["/a/b/other.ts"]} /a/lib/tsc.js -b -w -v project1.tsconfig.json project2.tsconfig.json project3.tsconfig.json @@ -69,9 +69,7 @@ Output:: [12:01:03 AM] Project 'project3.tsconfig.json' is up to date because newest input 'other.ts' is older than output 'other.js' -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:01:04 AM] Found 1 error. Watching for file changes. +[12:01:04 AM] Found 0 errors. Watching for file changes. @@ -125,8 +123,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -293,8 +291,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -371,9 +369,7 @@ Output:: [12:01:25 AM] Building project '/a/b/project2.tsconfig.json'... -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:01:36 AM] Found 1 error. Watching for file changes. +[12:01:36 AM] Found 0 errors. Watching for file changes. @@ -407,8 +403,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -479,9 +475,7 @@ Output:: [12:01:42 AM] Building project '/a/b/project2.tsconfig.json'... -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:01:53 AM] Found 1 error. Watching for file changes. +[12:01:53 AM] Found 0 errors. Watching for file changes. @@ -515,8 +509,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -586,9 +580,7 @@ Output:: [12:01:59 AM] Building project '/a/b/project2.tsconfig.json'... -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:02:12 AM] Found 1 error. Watching for file changes. +[12:02:12 AM] Found 0 errors. Watching for file changes. @@ -630,8 +622,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -707,8 +699,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -786,9 +778,7 @@ Output:: [12:02:34 AM] Building project '/a/b/project2.tsconfig.json'... -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:02:48 AM] Found 1 error. Watching for file changes. +[12:02:48 AM] Found 0 errors. Watching for file changes. @@ -826,8 +816,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -869,22 +859,24 @@ Output:: [12:02:54 AM] Building project '/a/b/project3.tsconfig.json'... -error TS5083: Cannot read file '/a/b/extendsCnfig1.tsconfig.json'. - -[12:02:55 AM] Found 1 error. Watching for file changes. +[12:02:59 AM] Found 0 errors. Watching for file changes. Program root files: ["/a/b/other.ts"] -Program options: {"strictNullChecks":true,"noImplicitAny":true,"composite":false,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} +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/other.ts -No cached semantic diagnostics in the builder:: +Semantic diagnostics in builder refreshed for:: +/a/lib/lib.d.ts +/a/b/other.ts -No shapes updated in the builder:: +Shape signatures in builder refreshed for:: +/a/lib/lib.d.ts (used version) +/a/b/other.ts (used version) WatchedFiles:: /a/b/project1.tsconfig.json: @@ -901,8 +893,8 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} -/a/b/extendscnfig1.tsconfig.json: - {"fileName":"/a/b/extendsCnfig1.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/extendsconfig3.tsconfig.json: @@ -918,6 +910,7 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined +//// [/a/b/other.js] file written with same contents Change:: Modify project 3 @@ -928,13 +921,13 @@ Input:: Output:: >> Screen clear -[12:02:59 AM] File change detected. Starting incremental compilation... +[12:03:03 AM] File change detected. Starting incremental compilation... -[12:03:00 AM] Project 'project3.tsconfig.json' is out of date because output file 'project3.tsconfig.tsbuildinfo' does not exist +[12:03:04 AM] Project 'project3.tsconfig.json' is out of date because output file 'project3.tsconfig.tsbuildinfo' does not exist -[12:03:01 AM] Building project '/a/b/project3.tsconfig.json'... +[12:03:05 AM] Building project '/a/b/project3.tsconfig.json'... -[12:03:30 AM] Found 0 errors. Watching for file changes. +[12:03:34 AM] Found 0 errors. Watching for file changes. @@ -956,7 +949,6 @@ Semantic diagnostics in builder refreshed for:: /a/b/other2.ts Shape signatures in builder refreshed for:: -/a/lib/lib.d.ts (used version) /a/b/commonfile1.ts (computed .d.ts) /a/b/commonfile2.ts (computed .d.ts) /a/b/other.ts (computed .d.ts) @@ -977,12 +969,12 @@ WatchedFiles:: {"fileName":"/a/b/other.ts","pollingInterval":250} /a/b/project3.tsconfig.json: {"fileName":"/a/b/project3.tsconfig.json","pollingInterval":250} +/a/b/extendsconfig1.tsconfig.json: + {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} /a/b/extendsconfig2.tsconfig.json: {"fileName":"/a/b/extendsConfig2.tsconfig.json","pollingInterval":250} /a/b/other2.ts: {"fileName":"/a/b/other2.ts","pollingInterval":250} -/a/b/extendsconfig1.tsconfig.json: - {"fileName":"/a/b/extendsConfig1.tsconfig.json","pollingInterval":250} FsWatches:: From 77f9b508b9d9ff8360a2fe04a5f558ee7816ef60 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 29 Aug 2022 11:25:40 -0700 Subject: [PATCH 04/12] Changes for pr comments --- src/compiler/builder.ts | 1 + src/compiler/commandLineParser.ts | 55 +++++++++---------- src/executeCommandLine/executeCommandLine.ts | 14 ++--- src/services/getEditsForFileRename.ts | 1 + src/testRunner/unittests/config/showConfig.ts | 18 +----- 5 files changed, 32 insertions(+), 57 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 6177713582859..33744091866d3 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -994,6 +994,7 @@ namespace ts { 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) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 811d760e7c07f..04986e4e2aeb1 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1732,17 +1732,7 @@ namespace ts { } break; case "listOrElement": - if(opt.element.type === "string"){ - options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); - i++; - } - else{ - const result = parseListTypeOption(opt, args[i], errors); - options[opt.name] = result || []; - if (result) { - i++; - } - } + Debug.fail(); break; // If not a primitive, the possible types are specified in what is effectively a map of options. default: @@ -2252,8 +2242,11 @@ namespace ts { if (!isDoubleQuotedString(valueExpression)) { errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); } - reportInvalidOptionValue(option && (isString(option.type) && option.type !== "string")&& (option.type !== "listOrElement" || option.element.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 @@ -2512,6 +2505,7 @@ namespace ts { 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 @@ -2696,7 +2690,7 @@ namespace ts { if(option.element.type === "string"){ return toAbsolutePath(value as string); } - else{ + else { const values = value as readonly (string | number)[]; if (option.element.isFilePath && values.length) { return values.map(toAbsolutePath); @@ -2985,12 +2979,12 @@ namespace ts { } interface ExtendsResult { - options: CompilerOptions, - watchOptions?: WatchOptions, - include?: string[], - exclude?: string[], - files?: string[], - compileOnSave?: boolean, + options: CompilerOptions; + watchOptions?: WatchOptions; + include?: string[]; + exclude?: string[]; + files?: string[]; + compileOnSave?: boolean; extendedSourceFiles?: Set } /** @@ -3030,22 +3024,22 @@ namespace ts { // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. resolutionStack = resolutionStack.concat([resolvedPath]); const result: ExtendsResult = { options:{} }; - if(isString(ownConfig.extendedConfigPath)){ + if (isString(ownConfig.extendedConfigPath)) { applyExtendedConfig(result, ownConfig.extendedConfigPath); } - else{ + 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.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; + ownConfig.watchOptions = ownConfig.watchOptions && result.watchOptions ? + assign(result.watchOptions, ownConfig.watchOptions) : + ownConfig.watchOptions || result.watchOptions; } return ownConfig; @@ -3130,7 +3124,7 @@ namespace ts { 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 = { @@ -3160,16 +3154,17 @@ namespace ts { case "extends": const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; if (isString(value)) { - extendedConfigPath = append (extendedConfigPath, getExtendsConfigPath( + extendedConfigPath = getExtendsConfigPath( value, host, newBase, errors, (message, arg0) => createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - )); + ); } else { + extendedConfigPath = []; for (const fileName of value as unknown[]) { if (isString(fileName)) { extendedConfigPath = append(extendedConfigPath, getExtendsConfigPath( diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 13d1292442169..7c5277a64e3db 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -163,7 +163,8 @@ namespace ts { const defaultValueDescription = typeof option.defaultValueDescription === "object" ? getDiagnosticText(option.defaultValueDescription) - : formatDefaultValue( + : Debug.assert(option.type !== "listOrElement"); + formatDefaultValue( option.defaultValueDescription, option.type === "list" ? option.element.type : option.type ); @@ -276,6 +277,7 @@ namespace ts { }; function getValueType(option: CommandLineOption) { + Debug.assert(option.type !== "listOrElement"); switch (option.type) { case "string": case "number": @@ -297,16 +299,8 @@ namespace ts { possibleValues = option.type; break; case "list": - // TODO: check infinite loop - possibleValues = getPossibleValues(option.element); - break; case "listOrElement": - if (option.element.type === "string"){ - possibleValues = option.type; - } - else { - possibleValues = getPossibleValues(option.element); - } + possibleValues = getPossibleValues(option.element); break; case "object": possibleValues = ""; diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index 5120560da4cee..8cb4872dbfd35 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -73,6 +73,7 @@ namespace ts { 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); } diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 8a18303a71764..28830c558bd79 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -146,23 +146,7 @@ namespace ts { break; } case "listOrElement": { - if (option.isTSConfigOnly) { - args = ["-p", "tsconfig.json"]; - if(option.element.type === "string"){ - optionValue = { [option.name]: "someString" }; - } - else{ - optionValue = { [option.name]: [] }; - } - } - else { - if(option.element.type === "string"){ - args = [`--${option.name}`, "someString"]; - } - else{ - args = [`--${option.name}`]; - } - } + Debug.fail(); break; } case "string": { From f85c7720fd2a504818d1cf03944d0474754c2c22 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 28 Nov 2022 10:50:22 -0800 Subject: [PATCH 05/12] Fixed formatting and edited a test --- src/compiler/builder.ts | 2 +- src/compiler/commandLineParser.ts | 372 +++++++++--------- src/executeCommandLine/executeCommandLine.ts | 76 ++-- src/services/getEditsForFileRename.ts | 38 +- .../config/configurationExtension.ts | 86 ++-- src/testRunner/unittests/config/showConfig.ts | 8 +- .../unittests/tsbuildWatch/programUpdates.ts | 77 ++-- .../works-with-extended-source-files.js | 68 +--- 8 files changed, 340 insertions(+), 387 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 5dc915d152e7e..5f06711031448 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1116,7 +1116,7 @@ function getBuildInfo(state: BuilderProgramState, getCanonicalFileName: GetCanon function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { if (option) { - Debug.assert(option.type !== "listOrElement"); + Debug.assert(option.type !== "listOrElement"); if (option.type === "list") { const values = value as readonly (string | number)[]; if (option.element.isFilePath && values.length) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index e72ad522cedd3..4853e3b876bbf 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1751,9 +1751,9 @@ function parseOptionValue( i++; } break; - case "listOrElement": - Debug.fail("listOrElement not supported here"); - 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); @@ -1994,91 +1994,91 @@ function getCommandLineTypeAcquisitionMap() { return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations)); } - const extendsOptionDeclaration: CommandLineOptionOfListType = { +const extendsOptionDeclaration: CommandLineOptionOfListType = { + name: "extends", + type: "listOrElement", + element: { name: "extends", - type: "listOrElement", - element: { - name: "extends", - type: "string" - }, - category: Diagnostics.File_Management, - }; - let _tsconfigRootOptions: TsConfigOnlyOption; - function getTsconfigRootOptionsMap() { - if (_tsconfigRootOptions === undefined) { - _tsconfigRootOptions = { - name: undefined!, // should never be needed since this is root - type: "object", - elementOptions: commandLineOptionsToMap([ - { - name: "compilerOptions", - type: "object", - elementOptions: getCommandLineCompilerOptionsMap(), - extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, - }, - { - name: "watchOptions", - type: "object", - elementOptions: getCommandLineWatchOptionsMap(), - extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, - }, - { - name: "typingOptions", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, - }, - { - name: "typeAcquisition", - type: "object", - elementOptions: getCommandLineTypeAcquisitionMap(), - extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics - }, - extendsOptionDeclaration, - { + type: "string" + }, + category: Diagnostics.File_Management, +}; +let _tsconfigRootOptions: TsConfigOnlyOption; +function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = { + name: undefined!, // should never be needed since this is root + type: "object", + elementOptions: commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + elementOptions: getCommandLineCompilerOptionsMap(), + extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics, + }, + { + name: "watchOptions", + type: "object", + elementOptions: getCommandLineWatchOptionsMap(), + extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics, + }, + { + name: "typingOptions", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics, + }, + { + name: "typeAcquisition", + type: "object", + elementOptions: getCommandLineTypeAcquisitionMap(), + extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics + }, + extendsOptionDeclaration, + { + name: "references", + type: "list", + element: { name: "references", - type: "list", - element: { - name: "references", - type: "object" - }, - category: Diagnostics.Projects, + type: "object" }, - { + category: Diagnostics.Projects, + }, + { + name: "files", + type: "list", + element: { name: "files", - type: "list", - element: { - name: "files", - type: "string" - }, - category: Diagnostics.File_Management, + type: "string" }, - { + category: Diagnostics.File_Management, + }, + { + name: "include", + type: "list", + element: { name: "include", - type: "list", - element: { - name: "include", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + type: "string" }, - { + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.if_files_is_specified_otherwise_Asterisk_Asterisk_Slash_Asterisk + }, + { + name: "exclude", + type: "list", + element: { name: "exclude", - type: "list", - element: { - name: "exclude", - type: "string" - }, - category: Diagnostics.File_Management, - defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + type: "string" }, - compileOnSaveCommandLineOption - ]) - }; - } - return _tsconfigRootOptions; + category: Diagnostics.File_Management, + defaultValueDescription: Diagnostics.node_modules_bower_components_jspm_packages_plus_the_value_of_outDir_if_one_is_specified + }, + compileOnSaveCommandLineOption + ]) + }; } + return _tsconfigRootOptions; +} /** @internal */ export interface JsonConversionNotifier { @@ -2529,7 +2529,7 @@ function serializeOptionBaseObject( const value = options[name] as CompilerOptionsValue; const optionDefinition = optionsNameMap.get(name.toLowerCase()); if (optionDefinition) { - Debug.assert(optionDefinition.type !== "listOrElement"); + 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 @@ -3002,15 +3002,15 @@ function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { return !!value.options; } - interface ExtendsResult { - options: CompilerOptions; - watchOptions?: WatchOptions; - include?: string[]; - exclude?: string[]; - files?: string[]; - compileOnSave?: boolean; - extendedSourceFiles?: Set - } +interface ExtendsResult { + options: CompilerOptions; + watchOptions?: WatchOptions; + include?: string[]; + exclude?: string[]; + files?: string[]; + compileOnSave?: boolean; + extendedSourceFiles?: Set +} /** * This *just* extracts options/include/exclude/files out of a config file. * It does *not* resolve the included files. @@ -3037,63 +3037,63 @@ function parseConfig( parseOwnConfigOfJson(json, host, basePath, configFileName, errors) : parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors); - if (ownConfig.options?.paths) { - // If we end up needing to resolve relative paths from 'paths' relative to - // the config file location, we'll need to know where that config file was. - // Since 'paths' can be inherited from an extended config in another directory, - // we wouldn't know which directory to use unless we store it here. - ownConfig.options.pathsBasePath = basePath; - } - if (ownConfig.extendedConfigPath) { - // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. - resolutionStack = resolutionStack.concat([resolvedPath]); - 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 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 - )); - } - }; - setPropertyInResultIfNotUndefined("include"); - setPropertyInResultIfNotUndefined("exclude"); - setPropertyInResultIfNotUndefined("files"); - if (extendsRaw.compileOnSave !== undefined) { - result.compileOnSave = extendsRaw.compileOnSave; + if (ownConfig.options?.paths) { + // If we end up needing to resolve relative paths from 'paths' relative to + // the config file location, we'll need to know where that config file was. + // Since 'paths' can be inherited from an extended config in another directory, + // we wouldn't know which directory to use unless we store it here. + ownConfig.options.pathsBasePath = basePath; + } + if (ownConfig.extendedConfigPath) { + // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. + resolutionStack = resolutionStack.concat([resolvedPath]); + 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 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 + )); } - assign(result.options, extendedConfig.options); - result.watchOptions = result.watchOptions && extendedConfig.watchOptions ? - assign({}, result.watchOptions, extendedConfig.watchOptions) : - result.watchOptions || extendedConfig.watchOptions; - // TODO extend type typeAcquisition + }; + setPropertyInResultIfNotUndefined("include"); + setPropertyInResultIfNotUndefined("exclude"); + setPropertyInResultIfNotUndefined("files"); + if (extendsRaw.compileOnSave !== undefined) { + result.compileOnSave = extendsRaw.compileOnSave; } + assign(result.options, extendedConfig.options); + result.watchOptions = result.watchOptions && extendedConfig.watchOptions ? + assign({}, result.watchOptions, extendedConfig.watchOptions) : + result.watchOptions || extendedConfig.watchOptions; + // TODO extend type typeAcquisition } } +} function parseOwnConfigOfJson( json: any, @@ -3120,22 +3120,22 @@ function parseOwnConfigOfJson( } else { const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - if (isString(json.extends)) { - 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 { - 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))); - } + 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 }; } @@ -3175,37 +3175,37 @@ function parseOwnConfigOfJsonSourceFile( currentOption[option.name] = normalizeOptionValue(option, basePath, value); }, onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { - switch (key) { - case "extends": - const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - if (isString(value)) { - extendedConfigPath = getExtendsConfigPath( - value, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - } - 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) - )); - } + switch (key) { + case "extends": + const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; + if (isString(value)) { + extendedConfigPath = getExtendsConfigPath( + value, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + ); + } + 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; - } + } + return; + } }, onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { if (key === "excludes") { @@ -3846,9 +3846,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 "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,8 +3873,8 @@ function getDefaultValueForOption(option: CommandLineOption): {} { return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; case "list": return []; - case "listOrElement": - return getDefaultValueForOption(option.element); + case "listOrElement": + return getDefaultValueForOption(option.element); case "object": return {}; default: diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 06f01d1463067..4c45f09da7507 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -177,16 +177,16 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign // name and description const name = getDisplayNameTextOfOption(option); - // value type and possible value - const valueCandidates = getValueCandidate(option); - const defaultValueDescription = - typeof option.defaultValueDescription === "object" - ? getDiagnosticText(option.defaultValueDescription) - : formatDefaultValue( - option.defaultValueDescription, - option.type === "list" || option.type === "listOrElement" ? option.element.type : option.type - ); - const terminalWidth = sys.getWidthOfTerminal?.() ?? 0; + // value type and possible value + const valueCandidates = getValueCandidate(option); + const defaultValueDescription = + typeof option.defaultValueDescription === "object" + ? getDiagnosticText(option.defaultValueDescription) + : formatDefaultValue( + 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. if (terminalWidth >= 80) { @@ -295,7 +295,7 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign }; function getValueType(option: CommandLineOption) { - Debug.assert(option.type !== "listOrElement"); + Debug.assert(option.type !== "listOrElement"); switch (option.type) { case "string": case "number": @@ -308,34 +308,34 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign } } - function getPossibleValues(option: CommandLineOption) { - let possibleValues: string; - switch (option.type) { - case "string": - case "number": - case "boolean": - possibleValues = option.type; - break; - case "list": - case "listOrElement": - possibleValues = getPossibleValues(option.element); - break; - case "object": - possibleValues = ""; - break; - default: - // ESMap - // Group synonyms: es6/es2015 - const inverted: { [value: string]: string[] } = {}; - option.type.forEach((value, name) => { - (inverted[value] ||= []).push(name); - }); - return getEntries(inverted) - .map(([, synonyms]) => synonyms.join("/")) - .join(", "); - } - return possibleValues; + function getPossibleValues(option: CommandLineOption) { + let possibleValues: string; + switch (option.type) { + case "string": + case "number": + case "boolean": + possibleValues = option.type; + break; + case "list": + case "listOrElement": + possibleValues = getPossibleValues(option.element); + break; + case "object": + possibleValues = ""; + break; + default: + // ESMap + // Group synonyms: es6/es2015 + const inverted: { [value: string]: string[] } = {}; + option.type.forEach((value, name) => { + (inverted[value] ||= []).push(name); + }); + return getEntries(inverted) + .map(([, synonyms]) => synonyms.join("/")) + .join(", "); } + return possibleValues; + } } } diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index 522f23b2963ba..2580db97c452f 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -87,25 +87,25 @@ function updateTsconfigFiles(program: Program, changeTracker: textChanges.Change } return; } - 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); - } - else if (propertyName === "paths") { - forEachProperty(property.initializer, (pathsProperty) => { - if (!isArrayLiteralExpression(pathsProperty.initializer)) return; - for (const e of pathsProperty.initializer.elements) { - tryUpdateString(e); - } - }); - } - }); - return; - } - }); + 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); + } + else if (propertyName === "paths") { + forEachProperty(property.initializer, (pathsProperty) => { + if (!isArrayLiteralExpression(pathsProperty.initializer)) return; + for (const e of pathsProperty.initializer.elements) { + tryUpdateString(e); + } + }); + } + }); + return; + } + }); function updatePaths(property: PropertyAssignment): boolean { const elements = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; diff --git a/src/testRunner/unittests/config/configurationExtension.ts b/src/testRunner/unittests/config/configurationExtension.ts index da2c8b5689ee5..8677621a6e514 100644 --- a/src/testRunner/unittests/config/configurationExtension.ts +++ b/src/testRunner/unittests/config/configurationExtension.ts @@ -378,54 +378,54 @@ describe("unittests:: config:: configurationExtension", () => { testSuccess("can lookup via an implicit tsconfig in a package-relative directory with extension", "tsconfig.extendsBoxImpliedPath.json", { strict: true }, [ts.combinePaths(basePath, "main.ts")]); }); - it("adds extendedSourceFiles only once", () => { - const sourceFile = ts.readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); - const dir = ts.combinePaths(basePath, "configs"); - const expected = [ - ts.combinePaths(dir, "third.json"), - ts.combinePaths(dir, "second.json"), - ts.combinePaths(dir, "base.json"), - ]; - ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); - assert.deepEqual(sourceFile.extendedSourceFiles, expected); - }); + it("adds extendedSourceFiles only once", () => { + const sourceFile = ts.readJsonConfigFile("configs/fourth.json", (path) => host.readFile(path)); + const dir = ts.combinePaths(basePath, "configs"); + const expected = [ + ts.combinePaths(dir, "third.json"), + ts.combinePaths(dir, "second.json"), + ts.combinePaths(dir, "base.json"), + ]; + ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + assert.deepEqual(sourceFile.extendedSourceFiles, expected); + ts.parseJsonSourceFileConfigFileContent(sourceFile, host, dir, {}, "fourth.json"); + 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); - }); + 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 - }, []); + 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 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.` - }]); - }); + 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.` + }]); }); }); +}); diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 5c81a9451296f..a815ec7d7750c 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -147,10 +147,10 @@ describe("unittests:: config:: showConfig", () => { } break; } - case "listOrElement": { - ts.Debug.fail(); - break; - } + case "listOrElement": { + ts.Debug.fail(); + break; + } case "string": { if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 2cecfdc689aab..21b395b20710c 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -618,44 +618,44 @@ 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] - }) - }; + 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, @@ -727,6 +727,7 @@ export function someFn() { }`), caption: "Modify project 3", change: 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 diff --git a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js index e33f0ae70a6d0..38aa01cf7af96 100644 --- a/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js +++ b/tests/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js @@ -1014,23 +1014,25 @@ Change:: Modify project 3 Input:: //// [/a/b/project3.tsconfig.json] -{"extends":["./extendsConfig1.tsconfig.json","./extendsConfig2.tsconfig.json"],"files":["/a/b/other2.ts"]} +{"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 file 'project3.tsconfig.tsbuildinfo' does not exist +[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:16 AM] Found 0 errors. Watching for file changes. +[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":true,"strictNullChecks":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} +Program options: {"composite":false,"strictNullChecks":true,"watch":true,"configFilePath":"/a/b/project3.tsconfig.json"} Program structureReused: Not Program files:: /a/lib/lib.d.ts @@ -1072,57 +1074,7 @@ FsWatchesRecursive:: exitCode:: ExitStatus.undefined -//// [/a/b/other2.d.ts] -declare let k: number; - - -//// [/a/b/project3.tsconfig.tsbuildinfo] -{"program":{"fileNames":["../lib/lib.d.ts","./other2.ts"],"fileInfos":[{"version":"-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }","affectsGlobalScope":true},{"version":"2287258045-let k = 0;","signature":"3820390125-declare let k: number;\n","affectsGlobalScope":true}],"options":{"composite":true,"strictNullChecks":true},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[2,1],"latestChangedDtsFile":"./other2.d.ts"},"version":"FakeTSVersion"} - -//// [/a/b/project3.tsconfig.tsbuildinfo.readable.baseline.txt] -{ - "program": { - "fileNames": [ - "../lib/lib.d.ts", - "./other2.ts" - ], - "fileInfos": { - "../lib/lib.d.ts": { - "original": { - "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", - "affectsGlobalScope": true - }, - "version": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", - "signature": "-7698705165-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }", - "affectsGlobalScope": true - }, - "./other2.ts": { - "original": { - "version": "2287258045-let k = 0;", - "signature": "3820390125-declare let k: number;\n", - "affectsGlobalScope": true - }, - "version": "2287258045-let k = 0;", - "signature": "3820390125-declare let k: number;\n", - "affectsGlobalScope": true - } - }, - "options": { - "composite": true, - "strictNullChecks": true - }, - "referencedMap": {}, - "exportedModulesMap": {}, - "semanticDiagnosticsPerFile": [ - "./other2.ts", - "../lib/lib.d.ts" - ], - "latestChangedDtsFile": "./other2.d.ts" - }, - "version": "FakeTSVersion", - "size": 780 -} - +//// [/a/b/other2.js] file changed its modified time Change:: Delete extendedConfigFile2 and report error @@ -1131,13 +1083,13 @@ Input:: Output:: >> Screen clear -[12:03:18 AM] File change detected. Starting incremental compilation... +[12:03:13 AM] File change detected. Starting incremental compilation... -[12:03:19 AM] Project 'project3.tsconfig.json' is up to date because newest input 'other2.ts' is older than output 'project3.tsconfig.tsbuildinfo' +[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:20 AM] Found 1 error. Watching for file changes. +[12:03:15 AM] Found 1 error. Watching for file changes. From de792204620386e0db524ecf1db59d60de998f69 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 28 Nov 2022 15:42:21 -0800 Subject: [PATCH 06/12] Resolved errors after a merge conflict --- src/compiler/commandLineParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 1e0c2c6ecd7c7..52f2241133321 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -3377,7 +3377,7 @@ function getExtendedConfig( host: ParseConfigHost, resolutionStack: string[], errors: Push, - extendedConfigCache: ESMap | undefined, + extendedConfigCache: Map | undefined, result: ExtendsResult ): ParsedTsconfig | undefined { const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); From 1b36b57ccff236af22bcd8bb2a77a0933430ac44 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 28 Nov 2022 16:46:35 -0800 Subject: [PATCH 07/12] Added "string | list" type implentation --- src/compiler/commandLineParser.ts | 56 ++++++++++++++----- src/compiler/types.ts | 4 +- src/executeCommandLine/executeCommandLine.ts | 4 ++ src/harness/harnessIO.ts | 1 + src/testRunner/unittests/config/showConfig.ts | 19 +++++++ 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 52f2241133321..a67a6dcb6b5f6 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -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): (string | number)[] | undefined { +export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "", errors: Push): string | (string | number)[] | undefined { value = trimString(value); if (startsWith(value, "-")) { return undefined; } + if (opt.type === "string | list" && !stringContains(value, ",")) { + return validateJsonOptionValue(opt, value, errors); + } if (value === "") { return []; } @@ -1672,6 +1676,10 @@ 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": + case "string | list": + return Debug.fail(`List of ${opt.element.type} is not yet supported.`); default: return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); } @@ -1843,6 +1851,7 @@ function parseOptionValue( options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); i++; break; + case "string | list": case "list": const result = parseListTypeOption(opt, args[i], errors); options[opt.name] = result || []; @@ -2362,7 +2371,7 @@ 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" && (option.type !== "listOrElement" || (isString(option.element.type) && option.element.type !== "string"))); + reportInvalidOptionValue(option && isString(option.type) && option.type !== "string" && (option.type !== "listOrElement" || (isString(option.element.type) && option.element.type !== "string")) && option.type !== "string | list"); 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"); @@ -2394,7 +2403,7 @@ export function convertToObjectWorker( return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); case SyntaxKind.ObjectLiteralExpression: - reportInvalidOptionValue(option && option.type !== "object" && (option.type !== "listOrElement" || option.element.type !== "object")); + reportInvalidOptionValue(option && option.type !== "object" && (option.type !== "listOrElement" || option.element.type !== "object") && option.type !== "string | list"); const objectLiteralExpression = valueExpression as ObjectLiteralExpression; // Currently having element option declaration in the tsconfig with type "object" @@ -2472,6 +2481,9 @@ function isCompilerOptionsValue(option: CommandLineOption | undefined, value: an if (option.type === "listOrElement") { return isArray(value) || isCompilerOptionsValue(option.element, value); } + if (option.type === "string | list") { + return typeof value === "string" || isArray(value); + } const expectedType = isString(option.type) ? option.type : "string"; return typeof value === expectedType; } @@ -2576,15 +2588,29 @@ function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, } function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map | 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" || optionDefinition.type === "listOrElement") { - return getCustomTypeMapOfCommandLineOption(optionDefinition.element); - } - else { - return optionDefinition.type; + // 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" || optionDefinition.type === "listOrElement") { + // return getCustomTypeMapOfCommandLineOption(optionDefinition.element); + // } + // else { + // return optionDefinition.type; + // } + switch (optionDefinition.type) { + case "string": + case "number": + case "boolean": + case "object": + case "string | list": + // this is of a type CommandLineOptionOfPrimitiveType + return undefined; + case "list": + case "listOrElement": + return getCustomTypeMapOfCommandLineOption(optionDefinition.element); + default: + return optionDefinition.type; } } @@ -3495,7 +3521,7 @@ function convertOptionsFromJson(optionsNameMap: Map, export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { if (isCompilerOptionsValue(opt, value)) { const optType = opt.type; - if (optType === "list" && isArray(value)) { + if ((optType === "list" || opt.type === "string | list") && isArray(value)) { return convertJsonOptionOfListType(opt, value, basePath, errors); } else if (optType === "listOrElement") { @@ -3503,7 +3529,7 @@ export function convertJsonOption(opt: CommandLineOption, value: any, basePath: convertJsonOptionOfListType(opt, value, basePath, errors) : convertJsonOption(opt.element, value, basePath, errors); } - else if (!isString(optType)) { + else if (!isString(opt.type)) { return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); } const validatedValue = validateJsonOptionValue(opt, value, errors); @@ -3949,6 +3975,7 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): if (!isArray(value)) return getOptionValueWithEmptyStrings(value, option.element); // fall through to list case "list": + case "string | list": const elementType = option.element; return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; default: @@ -3968,6 +3995,7 @@ function getDefaultValueForOption(option: CommandLineOption): {} { case "boolean": return true; case "string": + case "string | list": const defaultValue = option.defaultValueDescription; return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; case "list": diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f9788f96bcbac..4ef984c08321d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6960,7 +6960,7 @@ export interface CreateProgramOptions { /** @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map ; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | "string | list" | Map ; // 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. @@ -7030,7 +7030,7 @@ export interface TsConfigOnlyOption extends CommandLineOptionBase { /** @internal */ export interface CommandLineOptionOfListType extends CommandLineOptionBase { - type: "list" | "listOrElement"; + type: "list" | "listOrElement" | "string | list"; element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption; listPreserveFalsyValues?: boolean; } diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 5a2936d8640cf..c2eb86ddbf0e7 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -372,6 +372,7 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign case "boolean": return getDiagnosticText(Diagnostics.type_Colon); case "list": + case "string | list": return getDiagnosticText(Diagnostics.one_or_more_Colon); default: return getDiagnosticText(Diagnostics.one_of_Colon); @@ -390,6 +391,9 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign case "listOrElement": possibleValues = getPossibleValues(option.element); break; + case "string | list": + possibleValues = "string"; + break; case "object": possibleValues = ""; break; diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 642e4990385b3..5c1098e3ea681 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -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 "string | list": return ts.parseListTypeOption(option, value, errors); default: return ts.parseCustomTypeOption(option as ts.CommandLineOptionOfCustomType, value, errors); diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index a815ec7d7750c..5b8cfc9564fad 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -126,6 +126,24 @@ describe("unittests:: config:: showConfig", () => { if (option.name === "project") return; let args: string[]; let optionValue: object | undefined; + if (option.type === "string | list") { + if (option.isTSConfigOnly) { + showTSConfigCorrectly( + `Shows tsconfig for single option/${option.name}WithStringValue`, + ["-p", "tsconfig.json"], + isCompilerOptions ? + { compilerOptions: { [option.name]: "someString" } } : + { watchOptions: { [option.name]: "someString" } } + ); + } + else { + showTSConfigCorrectly( + `Shows tsconfig for single option/${option.name}WithStringValue`, + [`--${option.name}`, "someString"], + /*configJson*/ undefined, + ); + } + } switch (option.type) { case "boolean": { if (option.isTSConfigOnly) { @@ -137,6 +155,7 @@ describe("unittests:: config:: showConfig", () => { } break; } + case "string | list": case "list": { if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; From 7a214bdbad6e7c36991ec836ef1c9eacbfa2a64a Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 29 Nov 2022 11:47:00 -0800 Subject: [PATCH 08/12] Removed string | list type implementation --- src/compiler/commandLineParser.ts | 17 +++++------------ src/compiler/types.ts | 4 ++-- src/executeCommandLine/executeCommandLine.ts | 4 ---- src/harness/harnessIO.ts | 2 +- src/testRunner/unittests/config/showConfig.ts | 19 ------------------- 5 files changed, 8 insertions(+), 38 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index a67a6dcb6b5f6..06bb6d24c1103 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1664,7 +1664,7 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "" if (startsWith(value, "-")) { return undefined; } - if (opt.type === "string | list" && !stringContains(value, ",")) { + if (opt.type === "listOrElement" && !stringContains(value, ",")) { return validateJsonOptionValue(opt, value, errors); } if (value === "") { @@ -1678,7 +1678,7 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "" return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); case "boolean": case "object": - case "string | list": + case "listOrElement": return Debug.fail(`List of ${opt.element.type} is not yet supported.`); default: return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors)); @@ -1851,7 +1851,6 @@ function parseOptionValue( options[opt.name] = validateJsonOptionValue(opt, args[i] || "", errors); i++; break; - case "string | list": case "list": const result = parseListTypeOption(opt, args[i], errors); options[opt.name] = result || []; @@ -2371,7 +2370,7 @@ 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" && (option.type !== "listOrElement" || (isString(option.element.type) && option.element.type !== "string")) && option.type !== "string | list"); + 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"); @@ -2403,7 +2402,7 @@ export function convertToObjectWorker( return validateValue(-Number(((valueExpression as PrefixUnaryExpression).operand as NumericLiteral).text)); case SyntaxKind.ObjectLiteralExpression: - reportInvalidOptionValue(option && option.type !== "object" && (option.type !== "listOrElement" || option.element.type !== "object") && option.type !== "string | list"); + 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" @@ -2481,9 +2480,6 @@ function isCompilerOptionsValue(option: CommandLineOption | undefined, value: an if (option.type === "listOrElement") { return isArray(value) || isCompilerOptionsValue(option.element, value); } - if (option.type === "string | list") { - return typeof value === "string" || isArray(value); - } const expectedType = isString(option.type) ? option.type : "string"; return typeof value === expectedType; } @@ -2603,7 +2599,6 @@ function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption case "number": case "boolean": case "object": - case "string | list": // this is of a type CommandLineOptionOfPrimitiveType return undefined; case "list": @@ -3521,7 +3516,7 @@ function convertOptionsFromJson(optionsNameMap: Map, export function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push): CompilerOptionsValue { if (isCompilerOptionsValue(opt, value)) { const optType = opt.type; - if ((optType === "list" || opt.type === "string | list") && isArray(value)) { + if ((optType === "list") && isArray(value)) { return convertJsonOptionOfListType(opt, value, basePath, errors); } else if (optType === "listOrElement") { @@ -3975,7 +3970,6 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption): if (!isArray(value)) return getOptionValueWithEmptyStrings(value, option.element); // fall through to list case "list": - case "string | list": const elementType = option.element; return isArray(value) ? value.map(v => getOptionValueWithEmptyStrings(v, elementType)) : ""; default: @@ -3995,7 +3989,6 @@ function getDefaultValueForOption(option: CommandLineOption): {} { case "boolean": return true; case "string": - case "string | list": const defaultValue = option.defaultValueDescription; return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : ""; case "list": diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4ef984c08321d..f9788f96bcbac 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6960,7 +6960,7 @@ export interface CreateProgramOptions { /** @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | "string | list" | Map ; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map ; // 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. @@ -7030,7 +7030,7 @@ export interface TsConfigOnlyOption extends CommandLineOptionBase { /** @internal */ export interface CommandLineOptionOfListType extends CommandLineOptionBase { - type: "list" | "listOrElement" | "string | list"; + type: "list" | "listOrElement"; element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption; listPreserveFalsyValues?: boolean; } diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index c2eb86ddbf0e7..5a2936d8640cf 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -372,7 +372,6 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign case "boolean": return getDiagnosticText(Diagnostics.type_Colon); case "list": - case "string | list": return getDiagnosticText(Diagnostics.one_or_more_Colon); default: return getDiagnosticText(Diagnostics.one_of_Colon); @@ -391,9 +390,6 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign case "listOrElement": possibleValues = getPossibleValues(option.element); break; - case "string | list": - possibleValues = "string"; - break; case "object": possibleValues = ""; break; diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 5c1098e3ea681..4dcfcbca99faa 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -380,7 +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 "string | list": + case "listOrElement": return ts.parseListTypeOption(option, value, errors); default: return ts.parseCustomTypeOption(option as ts.CommandLineOptionOfCustomType, value, errors); diff --git a/src/testRunner/unittests/config/showConfig.ts b/src/testRunner/unittests/config/showConfig.ts index 5b8cfc9564fad..a815ec7d7750c 100644 --- a/src/testRunner/unittests/config/showConfig.ts +++ b/src/testRunner/unittests/config/showConfig.ts @@ -126,24 +126,6 @@ describe("unittests:: config:: showConfig", () => { if (option.name === "project") return; let args: string[]; let optionValue: object | undefined; - if (option.type === "string | list") { - if (option.isTSConfigOnly) { - showTSConfigCorrectly( - `Shows tsconfig for single option/${option.name}WithStringValue`, - ["-p", "tsconfig.json"], - isCompilerOptions ? - { compilerOptions: { [option.name]: "someString" } } : - { watchOptions: { [option.name]: "someString" } } - ); - } - else { - showTSConfigCorrectly( - `Shows tsconfig for single option/${option.name}WithStringValue`, - [`--${option.name}`, "someString"], - /*configJson*/ undefined, - ); - } - } switch (option.type) { case "boolean": { if (option.isTSConfigOnly) { @@ -155,7 +137,6 @@ describe("unittests:: config:: showConfig", () => { } break; } - case "string | list": case "list": { if (option.isTSConfigOnly) { args = ["-p", "tsconfig.json"]; From bb0ae2f165a53d872c90e731b5eef2abbb072a95 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 29 Nov 2022 14:04:15 -0800 Subject: [PATCH 09/12] Fixed formatting --- src/compiler/commandLineParser.ts | 74 +++++++++----------- src/compiler/types.ts | 2 +- src/executeCommandLine/executeCommandLine.ts | 2 +- src/services/getEditsForFileRename.ts | 3 +- 4 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 06bb6d24c1103..394768cb704fa 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2372,9 +2372,9 @@ export function convertToObjectWorker( } 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) { + 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 @@ -2584,16 +2584,6 @@ function matchesSpecs(path: string, includeSpecs: readonly string[] | undefined, } function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption): Map | 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" || optionDefinition.type === "listOrElement") { - // return getCustomTypeMapOfCommandLineOption(optionDefinition.element); - // } - // else { - // return optionDefinition.type; - // } switch (optionDefinition.type) { case "string": case "number": @@ -3298,33 +3288,33 @@ function parseOwnConfigOfJsonSourceFile( switch (key) { case "extends": const newBase = configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath; - if (isString(value)) { - extendedConfigPath = getExtendsConfigPath( - value, - host, - newBase, - errors, - (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) - ); - } - 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) - )); - } + if (isString(value)) { + extendedConfigPath = getExtendsConfigPath( + value, + host, + newBase, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + ); + } + 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; + } + return; } }, onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { @@ -3399,7 +3389,7 @@ function getExtendedConfig( resolutionStack: string[], errors: Push, extendedConfigCache: Map | undefined, - result: ExtendsResult + result: ExtendsResult ): ParsedTsconfig | undefined { const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath); let value: ExtendedConfigCacheEntry | undefined; @@ -3422,10 +3412,10 @@ function getExtendedConfig( (result.extendedSourceFiles ??= new Set()).add(extendedResult.fileName); if (extendedResult.extendedSourceFiles) { for (const extenedSourceFile of extendedResult.extendedSourceFiles) { - result.extendedSourceFiles.add(extenedSourceFile); + result.extendedSourceFiles.add(extenedSourceFile); } } - } + } if (extendedResult.parseDiagnostics.length) { errors.push(...extendedResult.parseDiagnostics); return undefined; @@ -3538,7 +3528,7 @@ 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 === "listOrElement" && !isArray(value)) return normalizeOptionValue(option.element, basePath, value); - else if (option.type === "list" || option.type === "listOrElement") { + 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; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f9788f96bcbac..d603c0ab677c1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6960,7 +6960,7 @@ export interface CreateProgramOptions { /** @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map ; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map; // 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. diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 5a2936d8640cf..0853f9c6916f4 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -406,8 +406,8 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign } return possibleValues; } - } } +} function generateGroupOptionOutput(sys: System, optionsList: readonly CommandLineOption[]) { let maxLength = 0; diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index deee8f9b9fc73..e8722ce5e76e5 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -120,8 +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)) { From ef8bd6e191f62b56e64e8fb5382b6112b377149e Mon Sep 17 00:00:00 2001 From: navya9singh Date: Mon, 12 Dec 2022 14:01:11 -0800 Subject: [PATCH 10/12] Added compiler test --- .../configFileExtendsAsList.errors.txt | 31 +++++++++++++++++++ .../reference/configFileExtendsAsList.js | 25 +++++++++++++++ .../reference/configFileExtendsAsList.symbols | 13 ++++++++ .../reference/configFileExtendsAsList.types | 14 +++++++++ .../cases/compiler/configFileExtendsAsList.ts | 23 ++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 tests/baselines/reference/configFileExtendsAsList.errors.txt create mode 100644 tests/baselines/reference/configFileExtendsAsList.js create mode 100644 tests/baselines/reference/configFileExtendsAsList.symbols create mode 100644 tests/baselines/reference/configFileExtendsAsList.types create mode 100644 tests/cases/compiler/configFileExtendsAsList.ts diff --git a/tests/baselines/reference/configFileExtendsAsList.errors.txt b/tests/baselines/reference/configFileExtendsAsList.errors.txt new file mode 100644 index 0000000000000..1accde5ea2632 --- /dev/null +++ b/tests/baselines/reference/configFileExtendsAsList.errors.txt @@ -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. \ No newline at end of file diff --git a/tests/baselines/reference/configFileExtendsAsList.js b/tests/baselines/reference/configFileExtendsAsList.js new file mode 100644 index 0000000000000..e805b7a293656 --- /dev/null +++ b/tests/baselines/reference/configFileExtendsAsList.js @@ -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 diff --git a/tests/baselines/reference/configFileExtendsAsList.symbols b/tests/baselines/reference/configFileExtendsAsList.symbols new file mode 100644 index 0000000000000..97b24a733694e --- /dev/null +++ b/tests/baselines/reference/configFileExtendsAsList.symbols @@ -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, --, --)) + diff --git a/tests/baselines/reference/configFileExtendsAsList.types b/tests/baselines/reference/configFileExtendsAsList.types new file mode 100644 index 0000000000000..dfa1eadf113be --- /dev/null +++ b/tests/baselines/reference/configFileExtendsAsList.types @@ -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 + diff --git a/tests/cases/compiler/configFileExtendsAsList.ts b/tests/cases/compiler/configFileExtendsAsList.ts new file mode 100644 index 0000000000000..8b6e1cf971544 --- /dev/null +++ b/tests/cases/compiler/configFileExtendsAsList.ts @@ -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 \ No newline at end of file From e0d9de8d465dd745adc200c2656bf5f4bd84f303 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 13 Dec 2022 09:46:40 -0800 Subject: [PATCH 11/12] Resolving programUpdate errors --- src/testRunner/unittests/tsbuildWatch/programUpdates.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index 87931c96e38ef..b4498d1f7e5f9 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -730,7 +730,7 @@ export function someFn() { }`), }, { caption: "Modify extendsConfigFile2", - change: sys => sys.writeFile("/a/b/extendsConfig2.tsconfig.json", JSON.stringify({ + edit: sys => sys.writeFile("/a/b/extendsConfig2.tsconfig.json", JSON.stringify({ compilerOptions: { strictNullChecks: true } })), timeouts: sys => { // Build project3 @@ -740,7 +740,7 @@ export function someFn() { }`), }, { caption: "Modify project 3", - change: sys => sys.writeFile("/a/b/project3.tsconfig.json", JSON.stringify({ + 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"] @@ -752,7 +752,7 @@ export function someFn() { }`), }, { caption: "Delete extendedConfigFile2 and report error", - change: sys => sys.deleteFile("./extendsConfig2.tsconfig.json"), + edit: sys => sys.deleteFile("./extendsConfig2.tsconfig.json"), timeouts: sys => { // Build project3 sys.checkTimeoutQueueLengthAndRun(1); sys.checkTimeoutQueueLength(0); From 7d6bdb630cba33a2389d5be956c21eeffe570b42 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Tue, 13 Dec 2022 10:50:46 -0800 Subject: [PATCH 12/12] Fixing commandLineParser error --- src/compiler/commandLineParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index d2b057b6e7c8a..97a09f716345e 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1678,7 +1678,6 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = "" return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors)); case "boolean": case "object": - case "listOrElement": return Debug.fail(`List of ${opt.element.type} is not yet supported.`); default: return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors));