diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6d62d7b67d90a..acaa8283a2af2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27,6 +27,12 @@ namespace ts { return symbol.id; } + export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); + } + export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { // Cancellation that controls whether or not we can cancel in the middle of type checking. // In general cancelling is *not* safe for the type checker. We might be in the middle of diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index a21c9bd5cd94c..7b508351aeccc 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2,7 +2,7 @@ /// /// /// -/// +/// namespace ts { /* @internal */ @@ -640,7 +640,7 @@ namespace ts { ]; /* @internal */ - export let typeAcquisitionDeclarations: CommandLineOption[] = [ + export const typeAcquisitionDeclarations: CommandLineOption[] = [ { /* @deprecated typingOptions.enableAutoDiscovery * Use typeAcquisition.enable instead. @@ -719,8 +719,12 @@ namespace ts { /* @internal */ export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { + return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic); + } + + function createDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType, createDiagnostic: (message: DiagnosticMessage, arg0: string, arg1: string) => Diagnostic): Diagnostic { const namesOfType = arrayFrom(opt.type.keys()).map(key => `'${key}'`).join(", "); - return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); + return createDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); } /* @internal */ @@ -880,7 +884,7 @@ namespace ts { text = readFile(fileName); } catch (e) { - return { error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) }; + return { config: {}, error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) }; } return parseConfigFileTextToJson(fileName, text); } @@ -890,13 +894,306 @@ namespace ts { * @param fileName The path to the config file * @param jsonText The text of the config file */ - export function parseConfigFileTextToJson(fileName: string, jsonText: string, stripComments = true): { config?: any; error?: Diagnostic } { + export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { + const jsonSourceFile = parseJsonText(fileName, jsonText); + return { + config: convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics), + error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined + }; + } + + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + export function readJsonConfigFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { + let text = ""; try { - const jsonTextToParse = stripComments ? removeComments(jsonText) : jsonText; - return { config: /\S/.test(jsonTextToParse) ? JSON.parse(jsonTextToParse) : {} }; + text = readFile(fileName); } catch (e) { - return { error: createCompilerDiagnostic(Diagnostics.Failed_to_parse_file_0_Colon_1, fileName, e.message) }; + return { parseDiagnostics: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; + } + return parseJsonText(fileName, text); + } + + function commandLineOptionsToMap(options: CommandLineOption[]) { + return arrayToMap(options, option => option.name); + } + + let _tsconfigRootOptions: Map; + function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + elementOptions: commandLineOptionsToMap(optionDeclarations), + extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0 + }, + { + name: "typingOptions", + type: "object", + elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations), + extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 + }, + { + name: "typeAcquisition", + type: "object", + elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations), + extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 + }, + { + name: "extends", + type: "string" + }, + { + name: "files", + type: "list", + element: { + name: "files", + type: "string" + } + }, + { + name: "include", + type: "list", + element: { + name: "include", + type: "string" + } + }, + { + name: "exclude", + type: "list", + element: { + name: "exclude", + type: "string" + } + }, + compileOnSaveCommandLineOption + ]); + } + return _tsconfigRootOptions; + } + + interface JsonConversionNotifier { + /** + * Notifies parent option object is being set with the optionKey and a valid optionValue + * Currently it notifies only if there is element with type object (parentOption) and + * has element's option declarations map associated with it + * @param parentOption parent option name in which the option and value are being set + * @param option option declaration which is being set with the value + * @param value value of the option + */ + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue): void; + /** + * Notify when valid root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file + */ + onSetValidOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + /** + * Notify when unknown root key value option is being set + * @param key option key + * @param keyNode node corresponding to node in the source file + * @param value computed value of the key + * @param ValueNode node corresponding to value in the source file + */ + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression): void; + } + + /** + * Convert the json syntax tree into the json value + */ + export function convertToObject(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { + return convertToObjectWorker(sourceFile, errors, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); + } + + /** + * Convert the json syntax tree into the json value + */ + function convertToObjectWorker( + sourceFile: JsonSourceFile, + errors: Diagnostic[], + knownRootOptions: Map | undefined, + jsonConversionNotifier: JsonConversionNotifier | undefined): any { + if (!sourceFile.jsonObject) { + return {}; + } + + return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions, + /*extraKeyDiagnosticMessage*/ undefined, /*parentOption*/ undefined); + + function convertObjectLiteralExpressionToJson( + node: ObjectLiteralExpression, + knownOptions: Map | undefined, + extraKeyDiagnosticMessage: DiagnosticMessage | undefined, + parentOption: string | undefined + ): any { + const result: any = {}; + for (const element of node.properties) { + if (element.kind !== SyntaxKind.PropertyAssignment) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); + continue; + } + + if (element.questionToken) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.questionToken, Diagnostics._0_can_only_be_used_in_a_ts_file, "?")); + } + if (!isDoubleQuotedString(element.name)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, Diagnostics.String_literal_with_double_quotes_expected)); + } + + const keyText = getTextOfPropertyName(element.name); + const option = knownOptions ? knownOptions.get(keyText) : undefined; + if (extraKeyDiagnosticMessage && !option) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText)); + } + const value = convertPropertyValueToJson(element.initializer, option); + if (typeof keyText !== undefined && typeof value !== undefined) { + result[keyText] = value; + // Notify key value set, if user asked for it + if (jsonConversionNotifier && + // Current callbacks are only on known parent option or if we are setting values in the root + (parentOption || knownOptions === knownRootOptions)) { + const isValidOptionValue = isCompilerOptionsValue(option, value); + if (parentOption) { + if (isValidOptionValue) { + // Notify option set in the parent if its a valid option value + jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option, value); + } + } + else if (knownOptions === knownRootOptions) { + if (isValidOptionValue) { + // Notify about the valid root key value being set + jsonConversionNotifier.onSetValidOptionKeyValueInRoot(keyText, element.name, value, element.initializer); + } + else if (!option) { + // Notify about the unknown root key value being set + jsonConversionNotifier.onSetUnknownOptionKeyValueInRoot(keyText, element.name, value, element.initializer); + } + } + } + } + } + return result; + } + + function convertArrayLiteralExpressionToJson( + elements: NodeArray, + elementOption: CommandLineOption | undefined + ): any[] { + const result: any[] = []; + for (const element of elements) { + result.push(convertPropertyValueToJson(element, elementOption)); + } + return result; + } + + function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption): any { + switch (valueExpression.kind) { + case SyntaxKind.TrueKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return true; + + case SyntaxKind.FalseKeyword: + reportInvalidOptionValue(option && option.type !== "boolean"); + return false; + + case SyntaxKind.NullKeyword: + reportInvalidOptionValue(!!option); + return null; // tslint:disable-line:no-null-keyword + + case SyntaxKind.StringLiteral: + if (!isDoubleQuotedString(valueExpression)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.String_literal_with_double_quotes_expected)); + } + reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string")); + const text = (valueExpression).text; + if (option && typeof option.type !== "string") { + const customOption = option; + // Validate custom option type + if (!customOption.type.has(text)) { + errors.push( + createDiagnosticForInvalidCustomType( + customOption, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) + ) + ); + } + } + return text; + + case SyntaxKind.NumericLiteral: + reportInvalidOptionValue(option && option.type !== "number"); + return Number((valueExpression).text); + + case SyntaxKind.ObjectLiteralExpression: + reportInvalidOptionValue(option && option.type !== "object"); + const objectLiteralExpression = valueExpression; + + // Currently having element option declaration in the tsconfig with type "object" + // determines if it needs onSetValidOptionKeyValueInParent callback or not + // At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions" + // that satifies it and need it to modify options set in them (for normalizing file paths) + // vs what we set in the json + // If need arises, we can modify this interface and callbacks as needed + if (option) { + const { elementOptions, extraKeyDiagnosticMessage, name: optionName } = option; + return convertObjectLiteralExpressionToJson(objectLiteralExpression, + elementOptions, extraKeyDiagnosticMessage, optionName); + } + else { + return convertObjectLiteralExpressionToJson( + objectLiteralExpression, /* knownOptions*/ undefined, + /*extraKeyDiagnosticMessage */ undefined, /*parentOption*/ undefined); + } + + case SyntaxKind.ArrayLiteralExpression: + reportInvalidOptionValue(option && option.type !== "list"); + return convertArrayLiteralExpressionToJson( + (valueExpression).elements, + option && (option).element); + } + + // Not in expected format + if (option) { + reportInvalidOptionValue(/*isError*/ true); + } + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); + } + + return undefined; + + function reportInvalidOptionValue(isError: boolean) { + if (isError) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option.name, getCompilerOptionValueTypeString(option))); + } + } + } + + function isDoubleQuotedString(node: Node) { + return node.kind === SyntaxKind.StringLiteral && getSourceTextOfNodeFromSourceFile(sourceFile, node).charCodeAt(0) === CharacterCodes.doubleQuote; + } + } + + function getCompilerOptionValueTypeString(option: CommandLineOption) { + return option.type === "list" ? + "Array" : + typeof option.type === "string" ? option.type : "string"; + } + + function isCompilerOptionsValue(option: CommandLineOption, value: any): value is CompilerOptionsValue { + if (option) { + if (option.type === "list") { + return isArray(value); + } + const expectedType = typeof option.type === "string" ? option.type : "string"; + return typeof value === expectedType; } } @@ -952,7 +1249,7 @@ namespace ts { if (optionsNameMap.has(name) && optionsNameMap.get(name).category === Diagnostics.Command_line_Options) { continue; } - const value = options[name]; + const value = options[name]; const optionDefinition = optionsNameMap.get(name.toLowerCase()); if (optionDefinition) { const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); @@ -1069,117 +1366,110 @@ namespace ts { } /** - * Remove the comments from a json like text. - * Comments can be single line comments (starting with # or //) or multiline comments using / * * / - * - * This method replace comment content by whitespace rather than completely remove them to keep positions in json parsing error reporting accurate. + * Parse the contents of a config file (tsconfig.json). + * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir */ - function removeComments(jsonText: string): string { - let output = ""; - const scanner = createScanner(ScriptTarget.ES5, /* skipTrivia */ false, LanguageVariant.Standard, jsonText); - let token: SyntaxKind; - while ((token = scanner.scan()) !== SyntaxKind.EndOfFileToken) { - switch (token) { - case SyntaxKind.SingleLineCommentTrivia: - case SyntaxKind.MultiLineCommentTrivia: - // replace comments with whitespace to preserve original character positions - output += scanner.getTokenText().replace(/\S/g, " "); - break; - default: - output += scanner.getTokenText(); - break; - } - } - return output; + export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: JsFileExtensionInfo[]): ParsedCommandLine { + return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions); } /** * Parse the contents of a config file (tsconfig.json). + * @param jsonNode The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. + * @param basePath A root directory to resolve relative path entries in the config + * file to. e.g. outDir + */ + export function parseJsonSourceFileConfigFileContent(sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: JsFileExtensionInfo[]): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions); + } + + /*@internal*/ + export function setConfigFileInOptions(options: CompilerOptions, configFile: JsonSourceFile) { + if (configFile) { + Object.defineProperty(options, "configFile", { enumerable: false, writable: false, value: configFile }); + } + } + + /** + * Parse the contents of a config file from json or json source file (tsconfig.json). * @param json The contents of the config file to parse + * @param sourceFile sourceFile corresponding to the Json * @param host Instance of ParseConfigHost used to enumerate files in folder. * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir * @param resolutionStack Only present for backwards-compatibility. Should be empty. */ - export function parseJsonConfigFileContent( - json: any, - host: ParseConfigHost, - basePath: string, - existingOptions: CompilerOptions = {}, - configFileName?: string, - resolutionStack: Path[] = [], - extraFileExtensions: JsFileExtensionInfo[] = [], - ): ParsedCommandLine { + function parseJsonConfigFileContentWorker( + json: any, + sourceFile: JsonSourceFile, + host: ParseConfigHost, + basePath: string, + existingOptions: CompilerOptions = {}, + configFileName?: string, + resolutionStack: Path[] = [], + extraFileExtensions: JsFileExtensionInfo[] = [], + ): ParsedCommandLine { + Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); const errors: Diagnostic[] = []; - let options = (() => { - const { include, exclude, files, options, compileOnSave } = parseConfig(json, host, basePath, configFileName, resolutionStack, errors); - if (include) { json.include = include; } - if (exclude) { json.exclude = exclude; } - if (files) { json.files = files; } - if (compileOnSave !== undefined) { json.compileOnSave = compileOnSave; } - return options; - })(); - - options = extend(existingOptions, options); + const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors); + const { raw } = parsedConfig; + const options = extend(existingOptions, parsedConfig.options || {}); options.configFilePath = configFileName; - - // typingOptions has been deprecated and is only supported for backward compatibility purposes. - // It should be removed in future releases - use typeAcquisition instead. - const jsonOptions = json["typeAcquisition"] || json["typingOptions"]; - const typeAcquisition: TypeAcquisition = convertTypeAcquisitionFromJsonWorker(jsonOptions, basePath, errors, configFileName); - + setConfigFileInOptions(options, sourceFile); const { fileNames, wildcardDirectories } = getFileNames(); - const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); - return { options, fileNames, - typeAcquisition, - raw: json, + typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(), + raw, errors, wildcardDirectories, - compileOnSave + compileOnSave: !!raw.compileOnSave }; function getFileNames(): ExpandResult { let fileNames: string[]; - if (hasProperty(json, "files")) { - if (isArray(json["files"])) { - fileNames = json["files"]; + if (hasProperty(raw, "files")) { + if (isArray(raw["files"])) { + fileNames = raw["files"]; if (fileNames.length === 0) { - errors.push(createCompilerDiagnostic(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json"); } } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array")); + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array"); } } let includeSpecs: string[]; - if (hasProperty(json, "include")) { - if (isArray(json["include"])) { - includeSpecs = json["include"]; + if (hasProperty(raw, "include")) { + if (isArray(raw["include"])) { + includeSpecs = raw["include"]; } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array")); + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array"); } } let excludeSpecs: string[]; - if (hasProperty(json, "exclude")) { - if (isArray(json["exclude"])) { - excludeSpecs = json["exclude"]; + if (hasProperty(raw, "exclude")) { + if (isArray(raw["exclude"])) { + excludeSpecs = raw["exclude"]; } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array")); + createCompilerDiagnosticOnlyIfJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"); } } else { // If no includes were specified, exclude common package folders and the outDir excludeSpecs = includeSpecs ? [] : ["node_modules", "bower_components", "jspm_packages"]; - const outDir = json["compilerOptions"] && json["compilerOptions"]["outDir"]; + const outDir = raw["compilerOptions"] && raw["compilerOptions"]["outDir"]; if (outDir) { excludeSpecs.push(outDir); } @@ -1189,9 +1479,9 @@ namespace ts { includeSpecs = ["**/*"]; } - const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions); + const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile); - if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) { + if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) { errors.push( createCompilerDiagnostic( Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, @@ -1202,14 +1492,23 @@ namespace ts { return result; } + + function createCompilerDiagnosticOnlyIfJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { + if (!sourceFile) { + errors.push(createCompilerDiagnostic(message, arg0, arg1)); + } + } } interface ParsedTsconfig { - include: string[] | undefined; - exclude: string[] | undefined; - files: string[] | undefined; - options: CompilerOptions; - compileOnSave: boolean | undefined; + raw: any; + options?: CompilerOptions; + typeAcquisition?: TypeAcquisition; + extendedConfigPath?: Path; + } + + function isSuccessfulParsedTsconfig(value: ParsedTsconfig) { + return !!value.options; } /** @@ -1218,94 +1517,223 @@ namespace ts { */ function parseConfig( json: any, + sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, configFileName: string, resolutionStack: Path[], errors: Diagnostic[], - ): ParsedTsconfig { - + ): ParsedTsconfig { basePath = normalizeSlashes(basePath); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); if (resolutionStack.indexOf(resolvedPath) >= 0) { errors.push(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))); - return { include: undefined, exclude: undefined, files: undefined, options: {}, compileOnSave: undefined }; + return { raw: json || convertToObject(sourceFile, errors) }; + } + + const ownConfig = json ? + parseOwnConfigOfJson(json, host, basePath, getCanonicalFileName, configFileName, errors) : + parseOwnConfigOfJsonSourceFile(sourceFile, host, basePath, getCanonicalFileName, configFileName, errors); + + 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, basePath, getCanonicalFileName, + resolutionStack, errors); + if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { + const baseRaw = extendedConfig.raw; + const raw = ownConfig.raw; + const setPropertyInRawIfNotUndefined = (propertyName: string) => { + const value = raw[propertyName] || baseRaw[propertyName]; + if (value) { + raw[propertyName] = value; + } + }; + setPropertyInRawIfNotUndefined("include"); + setPropertyInRawIfNotUndefined("exclude"); + setPropertyInRawIfNotUndefined("files"); + if (raw.compileOnSave === undefined) { + raw.compileOnSave = baseRaw.compileOnSave; + } + ownConfig.options = assign({}, extendedConfig.options, ownConfig.options); + // TODO extend type typeAcquisition + } } + return ownConfig; + } + + function parseOwnConfigOfJson( + json: any, + host: ParseConfigHost, + basePath: string, + getCanonicalFileName: (fileName: string) => string, + configFileName: string, + errors: Diagnostic[] + ): ParsedTsconfig { if (hasProperty(json, "excludes")) { errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); } - let options: CompilerOptions = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); - let include: string[] | undefined = json.include, exclude: string[] | undefined = json.exclude, files: string[] | undefined = json.files; - let compileOnSave: boolean | undefined = json.compileOnSave; + const options = convertCompilerOptionsFromJsonWorker(json.compilerOptions, basePath, errors, configFileName); + // typingOptions has been deprecated and is only supported for backward compatibility purposes. + // It should be removed in future releases - use typeAcquisition instead. + const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json["typeAcquisition"] || json["typingOptions"], basePath, errors, configFileName); + json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); + let extendedConfigPath: Path; if (json.extends) { - // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. - resolutionStack = resolutionStack.concat([resolvedPath]); - const base = getExtendedConfig(json.extends, host, basePath, getCanonicalFileName, resolutionStack, errors); - if (base) { - include = include || base.include; - exclude = exclude || base.exclude; - files = files || base.files; - if (compileOnSave === undefined) { - compileOnSave = base.compileOnSave; - } - options = assign({}, base.options, options); + if (typeof json.extends !== "string") { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); + } + else { + extendedConfigPath = getExtendsConfigPath(json.extends, host, basePath, getCanonicalFileName, errors, createCompilerDiagnostic); } } - - return { include, exclude, files, options, compileOnSave }; + return { raw: json, options, typeAcquisition, extendedConfigPath }; } - function getExtendedConfig( - extended: any, // Usually a string. - host: ts.ParseConfigHost, - basePath: string, - getCanonicalFileName: (fileName: string) => string, - resolutionStack: Path[], - errors: Diagnostic[], - ): ParsedTsconfig | undefined { - if (typeof extended !== "string") { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); - return undefined; + function parseOwnConfigOfJsonSourceFile( + sourceFile: JsonSourceFile, + host: ParseConfigHost, + basePath: string, + getCanonicalFileName: (fileName: string) => string, + configFileName: string, + errors: Diagnostic[] + ): ParsedTsconfig { + const options = getDefaultCompilerOptions(configFileName); + let typeAcquisition: TypeAcquisition, typingOptionstypeAcquisition: TypeAcquisition; + let extendedConfigPath: Path; + + const optionsIterator: JsonConversionNotifier = { + onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) { + Debug.assert(parentOption === "compilerOptions" || parentOption === "typeAcquisition" || parentOption === "typingOptions"); + const currentOption = parentOption === "compilerOptions" ? + options : + parentOption === "typeAcquisition" ? + (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))) : + (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); + + currentOption[option.name] = normalizeOptionValue(option, basePath, value); + }, + onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { + switch (key) { + case "extends": + extendedConfigPath = getExtendsConfigPath( + value, + host, + basePath, + getCanonicalFileName, + errors, + (message, arg0) => + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) + ); + return; + case "files": + if ((value).length === 0) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueNode, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + } + return; + } + }, + onSetUnknownOptionKeyValueInRoot(key: string, keyNode: PropertyName, _value: CompilerOptionsValue, _valueNode: Expression) { + if (key === "excludes") { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, keyNode, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + } + } + }; + const json = convertToObjectWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator); + if (!typeAcquisition) { + if (typingOptionstypeAcquisition) { + typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? + { + enable: typingOptionstypeAcquisition.enableAutoDiscovery, + include: typingOptionstypeAcquisition.include, + exclude: typingOptionstypeAcquisition.exclude + } : + typingOptionstypeAcquisition; + } + else { + typeAcquisition = getDefaultTypeAcquisition(configFileName); + } } - extended = normalizeSlashes(extended); + return { raw: json, options, typeAcquisition, extendedConfigPath }; + } + function getExtendsConfigPath( + extendedConfig: string, + host: ParseConfigHost, + basePath: string, + getCanonicalFileName: (fileName: string) => string, + errors: Diagnostic[], + createDiagnostic: (message: DiagnosticMessage, arg1?: string) => Diagnostic) { + extendedConfig = normalizeSlashes(extendedConfig); // If the path isn't a rooted or relative path, don't try to resolve it (we reserve the right to special case module-id like paths in the future) - if (!(isRootedDiskPath(extended) || startsWith(extended, "./") || startsWith(extended, "../"))) { - errors.push(createCompilerDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extended)); + if (!(isRootedDiskPath(extendedConfig) || startsWith(extendedConfig, "./") || startsWith(extendedConfig, "../"))) { + errors.push(createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig)); return undefined; } - - let extendedConfigPath = toPath(extended, basePath, getCanonicalFileName); + let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { - extendedConfigPath = extendedConfigPath + ".json" as Path; + extendedConfigPath = `${extendedConfigPath}.json` as Path; if (!host.fileExists(extendedConfigPath)) { - errors.push(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, extended)); + errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); return undefined; } } + return extendedConfigPath; + } - const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path)); - if (extendedResult.error) { - errors.push(extendedResult.error); + function getExtendedConfig( + sourceFile: JsonSourceFile, + extendedConfigPath: Path, + host: ts.ParseConfigHost, + basePath: string, + getCanonicalFileName: (fileName: string) => string, + resolutionStack: Path[], + errors: Diagnostic[], + ): ParsedTsconfig | undefined { + const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); + if (sourceFile) { + (sourceFile.extendedSourceFiles || (sourceFile.extendedSourceFiles = [])).push(extendedResult.fileName); + } + if (extendedResult.parseDiagnostics.length) { + errors.push(...extendedResult.parseDiagnostics); return undefined; } const extendedDirname = getDirectoryPath(extendedConfigPath); - const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); - const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); - const { include, exclude, files, options, compileOnSave } = parseConfig(extendedResult.config, host, extendedDirname, getBaseFileName(extendedConfigPath), resolutionStack, errors); - return { include: map(include, updatePath), exclude: map(exclude, updatePath), files: map(files, updatePath), compileOnSave, options }; + const extendedConfig = parseConfig(/*json*/ undefined, extendedResult, host, extendedDirname, + getBaseFileName(extendedConfigPath), resolutionStack, errors); + if (sourceFile) { + sourceFile.extendedSourceFiles.push(...extendedResult.extendedSourceFiles); + } + + if (isSuccessfulParsedTsconfig(extendedConfig)) { + // Update the paths to reflect base path + const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); + const updatePath = (path: string) => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); + const mapPropertiesInRawIfNotUndefined = (propertyName: string) => { + if (raw[propertyName]) { + raw[propertyName] = map(raw[propertyName], updatePath); + } + }; + + const { raw } = extendedConfig; + mapPropertiesInRawIfNotUndefined("include"); + mapPropertiesInRawIfNotUndefined("exclude"); + mapPropertiesInRawIfNotUndefined("files"); + } + + return extendedConfig; } - export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean { + function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean { if (!hasProperty(jsonOption, compileOnSaveCommandLineOption.name)) { - return false; + return undefined; } const result = convertJsonOption(compileOnSaveCommandLineOption, jsonOption["compileOnSave"], basePath, errors); if (typeof result === "boolean" && result) { @@ -1326,20 +1754,30 @@ namespace ts { return { options, errors }; } - function convertCompilerOptionsFromJsonWorker(jsonOptions: any, - basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions { - + function getDefaultCompilerOptions(configFileName?: string) { const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json" ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true } : {}; + return options; + } + + function convertCompilerOptionsFromJsonWorker(jsonOptions: any, + basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions { + + const options = getDefaultCompilerOptions(configFileName); convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors); return options; } + function getDefaultTypeAcquisition(configFileName?: string) { + const options: TypeAcquisition = { enable: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; + return options; + } + function convertTypeAcquisitionFromJsonWorker(jsonOptions: any, basePath: string, errors: Diagnostic[], configFileName?: string): TypeAcquisition { - const options: TypeAcquisition = { enable: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; + const options = getDefaultTypeAcquisition(configFileName); const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions); convertOptionsFromJson(typeAcquisitionDeclarations, typeAcquisition, basePath, options, Diagnostics.Unknown_type_acquisition_option_0, errors); @@ -1353,7 +1791,7 @@ namespace ts { return; } - const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); + const optionNameMap = commandLineOptionsToMap(optionDeclarations); for (const id in jsonOptions) { const opt = optionNameMap.get(id); @@ -1367,28 +1805,43 @@ namespace ts { } function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Diagnostic[]): CompilerOptionsValue { - const optType = opt.type; - const expectedType = typeof optType === "string" ? optType : "string"; - if (optType === "list" && isArray(value)) { - return convertJsonOptionOfListType(opt, value, basePath, errors); - } - else if (typeof value === expectedType) { - if (typeof optType !== "string") { - return convertJsonOptionOfCustomType(opt, value, errors); + if (isCompilerOptionsValue(opt, value)) { + const optType = opt.type; + if (optType === "list" && isArray(value)) { + return convertJsonOptionOfListType(opt, value, basePath, errors); } - else { - if (opt.isFilePath) { - value = normalizePath(combinePaths(basePath, value)); - if (value === "") { - value = "."; - } - } + else if (typeof optType !== "string") { + return convertJsonOptionOfCustomType(opt, value, errors); } - return value; + return normalizeNonListOptionValue(opt, basePath, value); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, expectedType)); + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, getCompilerOptionValueTypeString(opt))); + } + } + + function normalizeOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (option.type === "list") { + const listOption = option; + if (listOption.element.isFilePath || typeof listOption.element.type !== "string") { + return filter(map(value, v => normalizeOptionValue(listOption.element, basePath, v)), v => !!v); + } + return value; } + else if (typeof option.type !== "string") { + return option.type.get(value); + } + return normalizeNonListOptionValue(option, basePath, value); + } + + function normalizeNonListOptionValue(option: CommandLineOption, basePath: string, value: any): CompilerOptionsValue { + if (option.isFilePath) { + value = normalizePath(combinePaths(basePath, value)); + if (value === "") { + value = "."; + } + } + return value; } function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { @@ -1493,7 +1946,7 @@ namespace ts { * @param host The host used to resolve files and directories. * @param errors An array for diagnostic reporting. */ - function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], extraFileExtensions: JsFileExtensionInfo[]): ExpandResult { + function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], extraFileExtensions: JsFileExtensionInfo[], jsonSourceFile: JsonSourceFile): ExpandResult { basePath = normalizePath(basePath); // The exclude spec list is converted into a regular expression, which allows us to quickly @@ -1512,11 +1965,11 @@ namespace ts { const wildcardFileMap = createMap(); if (include) { - include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); + include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); } if (exclude) { - exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true); + exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); } // Wildcard directories (provided as part of a wildcard path) are stored in a @@ -1571,17 +2024,17 @@ namespace ts { }; } - function validateSpecs(specs: string[], errors: Diagnostic[], allowTrailingRecursion: boolean) { + function validateSpecs(specs: string[], errors: Diagnostic[], allowTrailingRecursion: boolean, jsonSourceFile: JsonSourceFile, specKey: string) { const validSpecs: string[] = []; for (const spec of specs) { if (!allowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) { - errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); + errors.push(createDiagnostic(Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); } else if (invalidMultipleRecursionPatterns.test(spec)) { - errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, spec)); + errors.push(createDiagnostic(Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, spec)); } else if (invalidDotDotAfterRecursiveWildcardPattern.test(spec)) { - errors.push(createCompilerDiagnostic(Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); + errors.push(createDiagnostic(Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec)); } else { validSpecs.push(spec); @@ -1589,6 +2042,21 @@ namespace ts { } return validSpecs; + + function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { + if (jsonSourceFile && jsonSourceFile.jsonObject) { + for (const property of getPropertyAssignment(jsonSourceFile.jsonObject, specKey)) { + if (isArrayLiteralExpression(property.initializer)) { + for (const element of property.initializer.elements) { + if (element.kind === SyntaxKind.StringLiteral && (element).text === spec) { + return createDiagnosticForNodeInSourceFile(jsonSourceFile, element, message, spec); + } + } + } + } + } + return createCompilerDiagnostic(message, spec); + } } /** diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 82b40c6231ac3..8e5bf01792471 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2139,6 +2139,8 @@ namespace ts { return ScriptKind.TS; case Extension.Tsx: return ScriptKind.TSX; + case ".json": + return ScriptKind.JSON; default: return ScriptKind.Unknown; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2854bb8f2ef68..c5c90dc627828 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1,4 +1,4 @@ -{ +{ "Unterminated string literal.": { "category": "Error", "code": 1002 @@ -899,6 +899,14 @@ "category": "Error", "code": 1326 }, + "String literal with double quotes expected.": { + "category": "Error", + "code": 1327 + }, + "Property value can only be string literal, numeric literal, 'true', 'false', 'null', object literal or array literal.": { + "category": "Error", + "code": 1328 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index a788bf6a4d057..65cbee9873a35 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -8,6 +8,70 @@ namespace ts { const delimiters = createDelimiterMap(); const brackets = createBracketsMap(); + /*@internal*/ + /** + * Iterates over the source files that are expected to have an emit output. + * + * @param host An EmitHost. + * @param action The action to execute. + * @param sourceFilesOrTargetSourceFile + * If an array, the full list of source files to emit. + * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. + */ + export function forEachEmittedFile( + host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => void, + sourceFilesOrTargetSourceFile?: SourceFile[] | SourceFile, + emitOnlyDtsFiles?: boolean) { + + const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile); + const options = host.getCompilerOptions(); + if (options.outFile || options.out) { + if (sourceFiles.length) { + const jsFilePath = options.outFile || options.out; + const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + Extension.Dts : ""; + action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles); + } + } + else { + for (const sourceFile of sourceFiles) { + const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options)); + const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); + const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; + action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles); + } + } + } + + function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { + return options.sourceMap ? jsFilePath + ".map" : undefined; + } + + // JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also. + // So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve. + // For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve + function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension { + if (options.jsx === JsxEmit.Preserve) { + if (isSourceFileJavaScript(sourceFile)) { + if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) { + return Extension.Jsx; + } + } + else if (sourceFile.languageVariant === LanguageVariant.JSX) { + // TypeScript source file preserving JSX syntax + return Extension.Jsx; + } + } + return Extension.Js; + } + + function getOriginalSourceFileOrBundle(sourceFileOrBundle: SourceFile | Bundle) { + if (sourceFileOrBundle.kind === SyntaxKind.Bundle) { + return updateBundle(sourceFileOrBundle, sameMap(sourceFileOrBundle.sourceFiles, getOriginalSourceFile)); + } + return getOriginalSourceFile(sourceFileOrBundle); + } + /*@internal*/ // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory[]): EmitResult { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 32244a8679265..ed3ed36e79a03 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2287,14 +2287,6 @@ namespace ts { return range; } - /** - * Gets flags that control emit behavior of a node. - */ - export function getEmitFlags(node: Node): EmitFlags | undefined { - const emitNode = node.emitNode; - return emitNode && emitNode.flags; - } - /** * Sets flags that control emit behavior of a node. */ @@ -3809,16 +3801,6 @@ namespace ts { return node; } - export function skipPartiallyEmittedExpressions(node: Expression): Expression; - export function skipPartiallyEmittedExpressions(node: Node): Node; - export function skipPartiallyEmittedExpressions(node: Node) { - while (node.kind === SyntaxKind.PartiallyEmittedExpression) { - node = (node).expression; - } - - return node; - } - function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) { switch (outerExpression.kind) { case SyntaxKind.ParenthesizedExpression: return updateParen(outerExpression, expression); @@ -4239,177 +4221,4 @@ namespace ts { Debug.assertNode(node, isExpression); return node; } - - export interface ExternalModuleInfo { - externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules - externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers - exportSpecifiers: Map; // export specifiers by name - exportedBindings: Identifier[][]; // exported names of local declarations - exportedNames: Identifier[]; // all exported names local to module - exportEquals: ExportAssignment | undefined; // an export= declaration if one was present - hasExportStarsToExportValues: boolean; // whether this module contains export* - } - - export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { - const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; - const exportSpecifiers = createMultiMap(); - const exportedBindings: Identifier[][] = []; - const uniqueExports = createMap(); - let exportedNames: Identifier[]; - let hasExportDefault = false; - let exportEquals: ExportAssignment = undefined; - let hasExportStarsToExportValues = false; - - for (const node of sourceFile.statements) { - switch (node.kind) { - case SyntaxKind.ImportDeclaration: - // import "mod" - // import x from "mod" - // import * as x from "mod" - // import { x, y } from "mod" - externalImports.push(node); - break; - - case SyntaxKind.ImportEqualsDeclaration: - if ((node).moduleReference.kind === SyntaxKind.ExternalModuleReference) { - // import x = require("mod") - externalImports.push(node); - } - - break; - - case SyntaxKind.ExportDeclaration: - if ((node).moduleSpecifier) { - if (!(node).exportClause) { - // export * from "mod" - externalImports.push(node); - hasExportStarsToExportValues = true; - } - else { - // export { x, y } from "mod" - externalImports.push(node); - } - } - else { - // export { x, y } - for (const specifier of (node).exportClause.elements) { - if (!uniqueExports.get(specifier.name.text)) { - const name = specifier.propertyName || specifier.name; - exportSpecifiers.add(name.text, specifier); - - const decl = resolver.getReferencedImportDeclaration(name) - || resolver.getReferencedValueDeclaration(name); - - if (decl) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); - } - - uniqueExports.set(specifier.name.text, true); - exportedNames = append(exportedNames, specifier.name); - } - } - } - break; - - case SyntaxKind.ExportAssignment: - if ((node).isExportEquals && !exportEquals) { - // export = x - exportEquals = node; - } - break; - - case SyntaxKind.VariableStatement: - if (hasModifier(node, ModifierFlags.Export)) { - for (const decl of (node).declarationList.declarations) { - exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames); - } - } - break; - - case SyntaxKind.FunctionDeclaration: - if (hasModifier(node, ModifierFlags.Export)) { - if (hasModifier(node, ModifierFlags.Default)) { - // export default function() { } - if (!hasExportDefault) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export function x() { } - const name = (node).name; - if (!uniqueExports.get(name.text)) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports.set(name.text, true); - exportedNames = append(exportedNames, name); - } - } - } - break; - - case SyntaxKind.ClassDeclaration: - if (hasModifier(node, ModifierFlags.Export)) { - if (hasModifier(node, ModifierFlags.Default)) { - // export default class { } - if (!hasExportDefault) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export class x { } - const name = (node).name; - if (!uniqueExports.get(name.text)) { - multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports.set(name.text, true); - exportedNames = append(exportedNames, name); - } - } - } - break; - } - } - - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues); - const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText)); - - if (externalHelpersImportDeclaration) { - externalImports.unshift(externalHelpersImportDeclaration); - } - - return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration }; - } - - function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map, exportedNames: Identifier[]) { - if (isBindingPattern(decl.name)) { - for (const element of decl.name.elements) { - if (!isOmittedExpression(element)) { - exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); - } - } - } - else if (!isGeneratedIdentifier(decl.name)) { - if (!uniqueExports.get(decl.name.text)) { - uniqueExports.set(decl.name.text, true); - exportedNames = append(exportedNames, decl.name); - } - } - return exportedNames; - } - - /** Use a sparse array as a multi-map. */ - function multiMapSparseArrayAdd(map: V[][], key: number, value: V): V[] { - let values = map[key]; - if (values) { - values.push(value); - } - else { - map[key] = values = [value]; - } - return values; - } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index e893dd600a878..0f08d6779107f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1,6 +1,5 @@ /// /// -/// namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; @@ -466,6 +465,15 @@ namespace ts { return Parser.parseIsolatedEntityName(text, languageVersion); } + /** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ + export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { + return Parser.parseJsonText(fileName, sourceText); + } + // See also `isExternalOrCommonJsModule` in utilities.ts export function isExternalModule(file: SourceFile): boolean { return file.externalModuleIndicator !== undefined; @@ -632,9 +640,34 @@ namespace ts { return isInvalid ? entityName : undefined; } + export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { + initializeState(sourceText, ScriptTarget.ES2015, /*syntaxCursor*/ undefined, ScriptKind.JSON); + // Set source file so that errors will be reported with this file name + sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON); + const result = sourceFile; + + // Prime the scanner. + nextToken(); + if (token() === SyntaxKind.EndOfFileToken) { + sourceFile.endOfFileToken = parseTokenNode(); + } + else if (token() === SyntaxKind.OpenBraceToken || + lookAhead(() => token() === SyntaxKind.StringLiteral)) { + result.jsonObject = parseObjectLiteralExpression(); + sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, /*reportAtCurrentPosition*/ false, Diagnostics.Unexpected_token); + } + else { + parseExpected(SyntaxKind.OpenBraceToken); + } + + sourceFile.parseDiagnostics = parseDiagnostics; + clearState(); + return result; + } + function getLanguageVariant(scriptKind: ScriptKind) { // .tsx and .jsx files are treated as jsx language variant. - return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard; + return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; } function initializeState(_sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, scriptKind: ScriptKind) { @@ -652,7 +685,7 @@ namespace ts { identifierCount = 0; nodeCount = 0; - contextFlags = scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX ? NodeFlags.JavaScriptFile : NodeFlags.None; + contextFlags = scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JSON ? NodeFlags.JavaScriptFile : NodeFlags.None; parseErrorBeforeNextFinishedNode = false; // Initialize and prime the scanner before parsing the source elements. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index cc6ae1a5813ab..bbc0fa0978090 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -442,6 +442,7 @@ namespace ts { // Map storing if there is emit blocking diagnostics for given input const hasEmitBlockingDiagnostics = createFileMap(getCanonicalFileName); + let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression; let moduleResolutionCache: ModuleResolutionCache; let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; @@ -1328,7 +1329,11 @@ namespace ts { function getOptionsDiagnostics(): Diagnostic[] { return sortAndDeduplicateDiagnostics(concatenate( fileProcessingDiagnostics.getGlobalDiagnostics(), - programDiagnostics.getGlobalDiagnostics())); + concatenate( + programDiagnostics.getGlobalDiagnostics(), + options.configFile ? programDiagnostics.getDiagnostics(options.configFile.fileName) : [] + ) + )); } function getGlobalDiagnostics(): Diagnostic[] { @@ -1773,33 +1778,33 @@ namespace ts { function verifyCompilerOptions() { if (options.isolatedModules) { if (options.declaration) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declaration", "isolatedModules")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declaration", "isolatedModules"); } if (options.noEmitOnError) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmitOnError", "isolatedModules")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmitOnError", "isolatedModules"); } if (options.out) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules"); } if (options.outFile) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "outFile", "isolatedModules"); } } if (options.inlineSourceMap) { if (options.sourceMap) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "sourceMap", "inlineSourceMap"); } if (options.mapRoot) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "mapRoot", "inlineSourceMap"); } } if (options.paths && options.baseUrl === undefined) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option)); + createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths"); } if (options.paths) { @@ -1808,63 +1813,65 @@ namespace ts { continue; } if (!hasZeroOrOneAsteriskCharacter(key)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key)); + createDiagnosticForOptionPaths(/*onKey*/ true, key, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, key); } if (isArray(options.paths[key])) { - if (options.paths[key].length === 0) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key)); + const len = options.paths[key].length; + if (len === 0) { + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_shouldn_t_be_an_empty_array, key); } - for (const subst of options.paths[key]) { + for (let i = 0; i < len; i++) { + const subst = options.paths[key][i]; const typeOfSubst = typeof subst; if (typeOfSubst === "string") { if (!hasZeroOrOneAsteriskCharacter(subst)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitution_0_in_pattern_1_in_can_have_at_most_one_Asterisk_character, subst, key)); + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_in_can_have_at_most_one_Asterisk_character, subst, key); } } else { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst)); + createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst); } } } else { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key)); + createDiagnosticForOptionPaths(/*onKey*/ false, key, Diagnostics.Substitutions_for_pattern_0_should_be_an_array, key); } } } if (!options.sourceMap && !options.inlineSourceMap) { if (options.inlineSources) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources")); + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "inlineSources"); } if (options.sourceRoot) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot")); + createDiagnosticForOptionName(Diagnostics.Option_0_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided, "sourceRoot"); } } if (options.out && options.outFile) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "outFile"); } if (options.mapRoot && !options.sourceMap) { // Error to specify --mapRoot without --sourcemap - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "mapRoot", "sourceMap"); } if (options.declarationDir) { if (!options.declaration) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationDir", "declaration")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "declarationDir", "declaration"); } if (options.out || options.outFile) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "declarationDir", options.out ? "out" : "outFile"); } } if (options.lib && options.noLib) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib"); } if (options.noImplicitUseStrict && (options.alwaysStrict === undefined ? options.strict : options.alwaysStrict)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); } const languageVersion = options.target || ScriptTarget.ES3; @@ -1873,7 +1880,7 @@ namespace ts { const firstNonAmbientExternalModuleSourceFile = forEach(files, f => isExternalModule(f) && !f.isDeclarationFile ? f : undefined); if (options.isolatedModules) { if (options.module === ModuleKind.None && languageVersion < ScriptTarget.ES2015) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher)); + createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); } const firstNonExternalModuleSourceFile = forEach(files, f => !isExternalModule(f) && !f.isDeclarationFile ? f : undefined); @@ -1891,7 +1898,7 @@ namespace ts { // Cannot specify module gen that isn't amd or system with --out if (outFile) { if (options.module && !(options.module === ModuleKind.AMD || options.module === ModuleKind.System)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile")); + createDiagnosticForOptionName(Diagnostics.Only_amd_and_system_modules_are_supported_alongside_0, options.out ? "out" : "outFile", "module"); } else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, firstNonAmbientExternalModuleSourceFile.externalModuleIndicator); @@ -1910,12 +1917,12 @@ namespace ts { // If we failed to find a good common directory, but outDir is specified and at least one of our files is on a windows drive/URL/other resource, add a failure if (options.outDir && dir === "" && forEach(files, file => getRootLength(file.fileName) > 1)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files)); + createDiagnosticForOptionName(Diagnostics.Cannot_find_the_common_subdirectory_path_for_the_input_files, "outDir"); } } if (!options.noEmit && options.allowJs && options.declaration) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration"); } if (options.checkJs && !options.allowJs) { @@ -1924,19 +1931,19 @@ namespace ts { if (options.emitDecoratorMetadata && !options.experimentalDecorators) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"); } if (options.jsxFactory) { if (options.reactNamespace) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory")); + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"); } if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory)); + createOptionValueDiagnostic("jsxFactory", Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory); } } else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace)); + createOptionValueDiagnostic("reactNamespace", Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace); } // If the emit is enabled make sure that every output file is unique and not overwriting any of the input files @@ -1976,6 +1983,91 @@ namespace ts { } } + function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0: string | number, arg1: string | number, arg2?: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer)) { + for (const keyProps of getPropertyAssignment(pathProp.initializer, key)) { + if (isArrayLiteralExpression(keyProps.initializer) && + keyProps.initializer.elements.length > valueIndex) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, keyProps.initializer.elements[valueIndex], message, arg0, arg1, arg2)); + needCompilerDiagnostic = false; + } + } + } + } + + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1, arg2)); + } + } + + function createDiagnosticForOptionPaths(onKey: boolean, key: string, message: DiagnosticMessage, arg0: string | number) { + let needCompilerDiagnostic = true; + const pathsSyntax = getOptionPathsSyntax(); + for (const pathProp of pathsSyntax) { + if (isObjectLiteralExpression(pathProp.initializer) && + createOptionDiagnosticInObjectLiteralSyntax( + pathProp.initializer, onKey, key, /*key2*/ undefined, + message, arg0)) { + needCompilerDiagnostic = false; + } + } + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0)); + } + } + + function getOptionPathsSyntax() { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + if (compilerOptionsObjectLiteralSyntax) { + return getPropertyAssignment(compilerOptionsObjectLiteralSyntax, "paths"); + } + return emptyArray; + } + + function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string) { + createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2); + } + + function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0: string) { + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0); + } + + function createDiagnosticForOption(onKey: boolean, option1: string, option2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number) { + const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax(); + const needCompilerDiagnostic = !compilerOptionsObjectLiteralSyntax || + !createOptionDiagnosticInObjectLiteralSyntax(compilerOptionsObjectLiteralSyntax, onKey, option1, option2, message, arg0, arg1); + + if (needCompilerDiagnostic) { + programDiagnostics.add(createCompilerDiagnostic(message, arg0, arg1)); + } + } + + function getCompilerOptionsObjectLiteralSyntax() { + if (_compilerOptionsObjectLiteralSyntax === undefined) { + _compilerOptionsObjectLiteralSyntax = null; // tslint:disable-line:no-null-keyword + if (options.configFile && options.configFile.jsonObject) { + for (const prop of getPropertyAssignment(options.configFile.jsonObject, "compilerOptions")) { + if (isObjectLiteralExpression(prop.initializer)) { + _compilerOptionsObjectLiteralSyntax = prop.initializer; + break; + } + } + } + } + return _compilerOptionsObjectLiteralSyntax; + } + + function createOptionDiagnosticInObjectLiteralSyntax(objectLiteral: ObjectLiteralExpression, onKey: boolean, key1: string, key2: string, message: DiagnosticMessage, arg0: string | number, arg1?: string | number): boolean { + const props = getPropertyAssignment(objectLiteral, key1, key2); + for (const prop of props) { + programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile, onKey ? prop.name : prop.initializer, message, arg0, arg1)); + } + return !!props.length; + } + function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) { hasEmitBlockingDiagnostics.set(toPath(emitFileName, currentDirectory, getCanonicalFileName), true); programDiagnostics.add(diag); diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index bfd3360e885c6..d61195ad74b54 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -1,4 +1,5 @@ /// +/// /// /// /// diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts new file mode 100644 index 0000000000000..a39115a788e92 --- /dev/null +++ b/src/compiler/transformers/utilities.ts @@ -0,0 +1,180 @@ +/* @internal */ +namespace ts { + export function getOriginalNodeId(node: Node) { + node = getOriginalNode(node); + return node ? getNodeId(node) : 0; + } + + export interface ExternalModuleInfo { + externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules + externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers + exportSpecifiers: Map; // export specifiers by name + exportedBindings: Identifier[][]; // exported names of local declarations + exportedNames: Identifier[]; // all exported names local to module + exportEquals: ExportAssignment | undefined; // an export= declaration if one was present + hasExportStarsToExportValues: boolean; // whether this module contains export* + } + + export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { + const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; + const exportSpecifiers = createMultiMap(); + const exportedBindings: Identifier[][] = []; + const uniqueExports = createMap(); + let exportedNames: Identifier[]; + let hasExportDefault = false; + let exportEquals: ExportAssignment = undefined; + let hasExportStarsToExportValues = false; + + for (const node of sourceFile.statements) { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + // import "mod" + // import x from "mod" + // import * as x from "mod" + // import { x, y } from "mod" + externalImports.push(node); + break; + + case SyntaxKind.ImportEqualsDeclaration: + if ((node).moduleReference.kind === SyntaxKind.ExternalModuleReference) { + // import x = require("mod") + externalImports.push(node); + } + + break; + + case SyntaxKind.ExportDeclaration: + if ((node).moduleSpecifier) { + if (!(node).exportClause) { + // export * from "mod" + externalImports.push(node); + hasExportStarsToExportValues = true; + } + else { + // export { x, y } from "mod" + externalImports.push(node); + } + } + else { + // export { x, y } + for (const specifier of (node).exportClause.elements) { + if (!uniqueExports.get(specifier.name.text)) { + const name = specifier.propertyName || specifier.name; + exportSpecifiers.add(name.text, specifier); + + const decl = resolver.getReferencedImportDeclaration(name) + || resolver.getReferencedValueDeclaration(name); + + if (decl) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); + } + + uniqueExports.set(specifier.name.text, true); + exportedNames = append(exportedNames, specifier.name); + } + } + } + break; + + case SyntaxKind.ExportAssignment: + if ((node).isExportEquals && !exportEquals) { + // export = x + exportEquals = node; + } + break; + + case SyntaxKind.VariableStatement: + if (hasModifier(node, ModifierFlags.Export)) { + for (const decl of (node).declarationList.declarations) { + exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames); + } + } + break; + + case SyntaxKind.FunctionDeclaration: + if (hasModifier(node, ModifierFlags.Export)) { + if (hasModifier(node, ModifierFlags.Default)) { + // export default function() { } + if (!hasExportDefault) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export function x() { } + const name = (node).name; + if (!uniqueExports.get(name.text)) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports.set(name.text, true); + exportedNames = append(exportedNames, name); + } + } + } + break; + + case SyntaxKind.ClassDeclaration: + if (hasModifier(node, ModifierFlags.Export)) { + if (hasModifier(node, ModifierFlags.Default)) { + // export default class { } + if (!hasExportDefault) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export class x { } + const name = (node).name; + if (!uniqueExports.get(name.text)) { + multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports.set(name.text, true); + exportedNames = append(exportedNames, name); + } + } + } + break; + } + } + + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues); + const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), + createLiteral(externalHelpersModuleNameText)); + + if (externalHelpersImportDeclaration) { + externalImports.unshift(externalHelpersImportDeclaration); + } + + return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration }; + } + + function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map, exportedNames: Identifier[]) { + if (isBindingPattern(decl.name)) { + for (const element of decl.name.elements) { + if (!isOmittedExpression(element)) { + exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames); + } + } + } + else if (!isGeneratedIdentifier(decl.name)) { + if (!uniqueExports.get(decl.name.text)) { + uniqueExports.set(decl.name.text, true); + exportedNames = append(exportedNames, decl.name); + } + } + return exportedNames; + } + + /** Use a sparse array as a multi-map. */ + function multiMapSparseArrayAdd(map: V[][], key: number, value: V): V[] { + let values = map[key]; + if (values) { + values.push(value); + } + else { + map[key] = values = [value]; + } + return values; + } +} \ No newline at end of file diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 637cfd0f16577..3b2422bfe089b 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -223,20 +223,13 @@ namespace ts { return; } - const result = parseConfigFileTextToJson(configFileName, cachedConfigFileText); - const configObject = result.config; - if (!configObject) { - reportDiagnostics([result.error], /* compilerHost */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } + const result = parseJsonText(configFileName, cachedConfigFileText); + reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + const cwd = sys.getCurrentDirectory(); - const configParseResult = parseJsonConfigFileContent(configObject, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); - if (configParseResult.errors.length > 0) { - reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } + const configParseResult = parseJsonSourceFileConfigFileContent(result, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); + reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined); + if (isWatchSet(configParseResult.options)) { if (!sys.watchFile) { reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined); diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 52bb92ee1f003..e896f4f2978e4 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -17,6 +17,7 @@ "checker.ts", "factory.ts", "visitor.ts", + "transformers/utilities.ts", "transformers/ts.ts", "transformers/jsx.ts", "transformers/esnext.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 76be7b20b1da2..ac64624e1a6aa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2375,6 +2375,11 @@ namespace ts { sourceFiles: SourceFile[]; } + export interface JsonSourceFile extends SourceFile { + jsonObject?: ObjectLiteralExpression; + extendedSourceFiles?: string[]; + } + export interface ScriptReferenceHost { getCompilerOptions(): CompilerOptions; getSourceFile(fileName: string): SourceFile; @@ -3488,6 +3493,7 @@ namespace ts { charset?: string; checkJs?: boolean; /* @internal */ configFilePath?: string; + /* @internal */ readonly configFile?: JsonSourceFile; declaration?: boolean; declarationDir?: string; /* @internal */ diagnostics?: boolean; @@ -3560,7 +3566,7 @@ namespace ts { /*@internal*/ version?: boolean; /*@internal*/ watch?: boolean; - [option: string]: CompilerOptionsValue | undefined; + [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; } export interface TypeAcquisition { @@ -3620,7 +3626,8 @@ namespace ts { JSX = 2, TS = 3, TSX = 4, - External = 5 + External = 5, + JSON = 6 } export const enum ScriptTarget { @@ -3692,6 +3699,8 @@ namespace ts { /* @internal */ export interface TsConfigOnlyOption extends CommandLineOptionBase { type: "object"; + elementOptions?: Map; + extraKeyDiagnosticMessage?: DiagnosticMessage; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3833fc3087544..c8827801e5e07 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -309,6 +309,14 @@ namespace ts { return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node, includeTrivia); } + /** + * Gets flags that control emit behavior of a node. + */ + export function getEmitFlags(node: Node): EmitFlags | undefined { + const emitNode = node.emitNode; + return emitNode && emitNode.flags; + } + export function getLiteralText(node: LiteralLikeNode, sourceFile: SourceFile) { // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. @@ -882,6 +890,15 @@ namespace ts { return predicate && predicate.kind === TypePredicateKind.This; } + export function getPropertyAssignment(objectLiteral: ObjectLiteralExpression, key: string, key2?: string) { + return filter(objectLiteral.properties, property => { + if (property.kind === SyntaxKind.PropertyAssignment) { + const propName = getTextOfPropertyName(property.name); + return key === propName || (key2 && key2 === propName); + } + }); + } + export function getContainingFunction(node: Node): FunctionLikeDeclaration { while (true) { node = node.parent; @@ -1242,12 +1259,6 @@ namespace ts { return false; } - export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { - const moduleState = getModuleInstanceState(node); - return moduleState === ModuleInstanceState.Instantiated || - (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); - } - export function isExternalModuleImportEqualsDeclaration(node: Node) { return node.kind === SyntaxKind.ImportEqualsDeclaration && (node).moduleReference.kind === SyntaxKind.ExternalModuleReference; } @@ -2013,14 +2024,7 @@ namespace ts { || positionIsSynthesized(node.end); } - export function getOriginalSourceFileOrBundle(sourceFileOrBundle: SourceFile | Bundle) { - if (sourceFileOrBundle.kind === SyntaxKind.Bundle) { - return updateBundle(sourceFileOrBundle, sameMap(sourceFileOrBundle.sourceFiles, getOriginalSourceFile)); - } - return getOriginalSourceFile(sourceFileOrBundle); - } - - function getOriginalSourceFile(sourceFile: SourceFile) { + export function getOriginalSourceFile(sourceFile: SourceFile) { return getParseTreeNode(sourceFile, isSourceFile) || sourceFile; } @@ -2028,11 +2032,6 @@ namespace ts { return sameMap(sourceFiles, getOriginalSourceFile); } - export function getOriginalNodeId(node: Node) { - node = getOriginalNode(node); - return node ? getNodeId(node) : 0; - } - export const enum Associativity { Left, Right @@ -2540,62 +2539,6 @@ namespace ts { return !(options.noEmitForJsFiles && isSourceFileJavaScript(sourceFile)) && !sourceFile.isDeclarationFile && !isSourceFileFromExternalLibrary(sourceFile); } - /** - * Iterates over the source files that are expected to have an emit output. - * - * @param host An EmitHost. - * @param action The action to execute. - * @param sourceFilesOrTargetSourceFile - * If an array, the full list of source files to emit. - * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. - */ - export function forEachEmittedFile( - host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => void, - sourceFilesOrTargetSourceFile?: SourceFile[] | SourceFile, - emitOnlyDtsFiles?: boolean) { - - const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile); - const options = host.getCompilerOptions(); - if (options.outFile || options.out) { - if (sourceFiles.length) { - const jsFilePath = options.outFile || options.out; - const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + Extension.Dts : ""; - action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles); - } - } - else { - for (const sourceFile of sourceFiles) { - const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options)); - const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; - action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles); - } - } - } - - function getSourceMapFilePath(jsFilePath: string, options: CompilerOptions) { - return options.sourceMap ? jsFilePath + ".map" : undefined; - } - - // JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also. - // So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve. - // For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve - function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension { - if (options.jsx === JsxEmit.Preserve) { - if (isSourceFileJavaScript(sourceFile)) { - if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) { - return Extension.Jsx; - } - } - else if (sourceFile.languageVariant === LanguageVariant.JSX) { - // TypeScript source file preserving JSX syntax - return Extension.Jsx; - } - } - return Extension.Js; - } - export function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) { let sourceFilePath = getNormalizedAbsolutePath(sourceFile.fileName, host.getCurrentDirectory()); const commonSourceDirectory = host.getCommonSourceDirectory(); @@ -4284,6 +4227,16 @@ namespace ts { return node.kind === SyntaxKind.ParenthesizedExpression; } + export function skipPartiallyEmittedExpressions(node: Expression): Expression; + export function skipPartiallyEmittedExpressions(node: Node): Node; + export function skipPartiallyEmittedExpressions(node: Node) { + while (node.kind === SyntaxKind.PartiallyEmittedExpression) { + node = (node).expression; + } + + return node; + } + export function isFunctionExpression(node: Node): node is FunctionExpression { return node.kind === SyntaxKind.FunctionExpression; } diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index ff418ab34de94..170a23e34f2e8 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -64,6 +64,7 @@ class CompilerBaselineRunner extends RunnerBase { let result: Harness.Compiler.CompilerResult; let options: ts.CompilerOptions; + let tsConfigFiles: Harness.Compiler.TestFile[]; // equivalent to the files that will be passed on the command line let toBeCompiled: Harness.Compiler.TestFile[]; // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) @@ -77,10 +78,12 @@ class CompilerBaselineRunner extends RunnerBase { const units = testCaseContent.testUnitData; harnessSettings = testCaseContent.settings; let tsConfigOptions: ts.CompilerOptions; + tsConfigFiles = []; if (testCaseContent.tsConfig) { assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); - tsConfigOptions = ts.clone(testCaseContent.tsConfig.options); + tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); + tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); } else { const baseUrl = harnessSettings["baseUrl"]; @@ -98,21 +101,22 @@ class CompilerBaselineRunner extends RunnerBase { otherFiles = []; if (testCaseContent.settings["noImplicitReferences"] || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) { - toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }); + toBeCompiled.push(this.createHarnessTestFile(lastUnit, rootDir)); units.forEach(unit => { if (unit.name !== lastUnit.name) { - otherFiles.push({ unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions }); + otherFiles.push(this.createHarnessTestFile(unit, rootDir)); } }); } else { toBeCompiled = units.map(unit => { - return { unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions }; + return this.createHarnessTestFile(unit, rootDir); }); } if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); + tsConfigOptions.configFile.fileName = tsConfigOptions.configFilePath; } const output = Harness.Compiler.compileFiles( @@ -132,11 +136,12 @@ class CompilerBaselineRunner extends RunnerBase { options = undefined; toBeCompiled = undefined; otherFiles = undefined; + tsConfigFiles = undefined; }); // check errors it("Correct errors for " + fileName, () => { - Harness.Compiler.doErrorBaseline(justName, toBeCompiled.concat(otherFiles), result.errors); + Harness.Compiler.doErrorBaseline(justName, tsConfigFiles.concat(toBeCompiled, otherFiles), result.errors); }); it (`Correct module resolution tracing for ${fileName}`, () => { @@ -165,7 +170,7 @@ class CompilerBaselineRunner extends RunnerBase { it("Correct JS output for " + fileName, () => { if (hasNonDtsFiles && this.emit) { - Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, toBeCompiled, otherFiles, harnessSettings); + Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, tsConfigFiles, toBeCompiled, otherFiles, harnessSettings); } }); @@ -183,6 +188,10 @@ class CompilerBaselineRunner extends RunnerBase { }); } + private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile { + return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; + } + public initializeTests() { describe(this.testSuiteName + " tests", () => { describe("Setup compiler for compiler baselines", () => { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index e944e68f11ea7..d73290f961f3b 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1125,7 +1125,7 @@ namespace Harness { compilerOptions: ts.CompilerOptions, // Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file currentDirectory: string): CompilationOutput { - const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.clone(compilerOptions) : { noResolve: false }; + const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false }; options.target = options.target || ts.ScriptTarget.ES3; options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed; options.noErrorTruncation = true; @@ -1557,7 +1557,7 @@ namespace Harness { } } - export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: CompilerResult, toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], harnessSettings: Harness.TestCaseParser.CompilerSettings) { + export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: CompilerResult, tsConfigFiles: Harness.Compiler.TestFile[], toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], harnessSettings: Harness.TestCaseParser.CompilerSettings) { if (!options.noEmit && result.files.length === 0 && result.errors.length === 0) { throw new Error("Expected at least one js file to be emitted or at least one error to be created."); } @@ -1593,7 +1593,7 @@ namespace Harness { if (declFileCompilationResult && declFileCompilationResult.declResult.errors.length) { jsCode += "\r\n\r\n//// [DtsFileErrors]\r\n"; jsCode += "\r\n\r\n"; - jsCode += Harness.Compiler.getErrorBaseline(declFileCompilationResult.declInputFiles.concat(declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors); + jsCode += Harness.Compiler.getErrorBaseline(tsConfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors); } if (jsCode.length > 0) { @@ -1744,7 +1744,12 @@ namespace Harness { } /** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */ - export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string): { settings: CompilerSettings; testUnitData: TestUnitData[]; tsConfig: ts.ParsedCommandLine } { + export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string): { + settings: CompilerSettings; + testUnitData: TestUnitData[]; + tsConfig: ts.ParsedCommandLine; + tsConfigFileUnitData: TestUnitData; + } { const settings = extractCompilerSettings(code); // List of all the subfiles we've parsed out @@ -1830,17 +1835,19 @@ namespace Harness { // check if project has tsconfig.json in the list of files let tsConfig: ts.ParsedCommandLine; + let tsConfigFileUnitData: TestUnitData; for (let i = 0; i < testUnitData.length; i++) { const data = testUnitData[i]; if (ts.getBaseFileName(data.name).toLowerCase() === "tsconfig.json") { - const configJson = ts.parseConfigFileTextToJson(data.name, data.content); - assert.isTrue(configJson.config !== undefined); + const configJson = ts.parseJsonText(data.name, data.content); + assert.isTrue(configJson.endOfFileToken !== undefined); let baseDir = ts.normalizePath(ts.getDirectoryPath(data.name)); if (rootDir) { baseDir = ts.getNormalizedAbsolutePath(baseDir, rootDir); } - tsConfig = ts.parseJsonConfigFileContent(configJson.config, parseConfigHost, baseDir); + tsConfig = ts.parseJsonSourceFileConfigFileContent(configJson, parseConfigHost, baseDir); tsConfig.options.configFilePath = data.name; + tsConfigFileUnitData = data; // delete entry from the list ts.orderedRemoveItemAt(testUnitData, i); @@ -1848,7 +1855,7 @@ namespace Harness { break; } } - return { settings, testUnitData, tsConfig }; + return { settings, testUnitData, tsConfig, tsConfigFileUnitData }; } } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 5d4d5a41f3adf..b02164034fa2e 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -24,6 +24,7 @@ interface BatchCompileProjectTestCaseEmittedFile extends Harness.Compiler.Genera } interface CompileProjectFilesResult { + configFileSourceFiles: ts.SourceFile[]; moduleKind: ts.ModuleKind; program?: ts.Program; compilerOptions?: ts.CompilerOptions; @@ -124,7 +125,8 @@ class ProjectRunner extends RunnerBase { return Harness.IO.resolvePath(testCase.projectRoot); } - function compileProjectFiles(moduleKind: ts.ModuleKind, getInputFiles: () => string[], + function compileProjectFiles(moduleKind: ts.ModuleKind, configFileSourceFiles: ts.SourceFile[], + getInputFiles: () => string[], getSourceFileTextImpl: (fileName: string) => string, writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void, compilerOptions: ts.CompilerOptions): CompileProjectFilesResult { @@ -148,6 +150,7 @@ class ProjectRunner extends RunnerBase { } return { + configFileSourceFiles, moduleKind, program, errors, @@ -196,6 +199,7 @@ class ProjectRunner extends RunnerBase { const outputFiles: BatchCompileProjectTestCaseEmittedFile[] = []; let inputFiles = testCase.inputFiles; let compilerOptions = createCompilerOptions(); + const configFileSourceFiles: ts.SourceFile[] = []; let configFileName: string; if (compilerOptions.project) { @@ -207,41 +211,31 @@ class ProjectRunner extends RunnerBase { configFileName = ts.findConfigFile("", fileExists); } + let errors: ts.Diagnostic[]; if (configFileName) { - const result = ts.readConfigFile(configFileName, getSourceFileText); - if (result.error) { - return { - moduleKind, - errors: [result.error] - }; - } - - const configObject = result.config; + const result = ts.readJsonConfigFile(configFileName, getSourceFileText); + configFileSourceFiles.push(result); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists, readDirectory, readFile }; - const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); - if (configParseResult.errors.length > 0) { - return { - moduleKind, - errors: configParseResult.errors - }; - } + const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); inputFiles = configParseResult.fileNames; compilerOptions = configParseResult.options; + errors = result.parseDiagnostics.concat(configParseResult.errors); } - const projectCompilerResult = compileProjectFiles(moduleKind, () => inputFiles, getSourceFileText, writeFile, compilerOptions); + const projectCompilerResult = compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, getSourceFileText, writeFile, compilerOptions); return { + configFileSourceFiles, moduleKind, program: projectCompilerResult.program, compilerOptions, sourceMapData: projectCompilerResult.sourceMapData, outputFiles, - errors: projectCompilerResult.errors, + errors: errors ? errors.concat(projectCompilerResult.errors) : projectCompilerResult.errors, }; function createCompilerOptions() { @@ -402,7 +396,7 @@ class ProjectRunner extends RunnerBase { }); // Dont allow config files since we are compiling existing source options - return compileProjectFiles(compilerResult.moduleKind, getInputFiles, getSourceFileText, writeFile, compilerResult.compilerOptions); + return compileProjectFiles(compilerResult.moduleKind, compilerResult.configFileSourceFiles, getInputFiles, getSourceFileText, writeFile, compilerResult.compilerOptions); function findOutputDtsFile(fileName: string) { return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.emittedFileName === fileName ? outputFile : undefined); @@ -428,16 +422,16 @@ class ProjectRunner extends RunnerBase { } function getErrorsBaseline(compilerResult: CompileProjectFilesResult) { - const inputFiles = compilerResult.program ? ts.map(ts.filter(compilerResult.program.getSourceFiles(), - sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)), - sourceFile => { - return { - unitName: ts.isRootedDiskPath(sourceFile.fileName) ? - RunnerBase.removeFullPaths(sourceFile.fileName) : - sourceFile.fileName, - content: sourceFile.text - }; - }) : []; + const inputFiles = ts.map(compilerResult.configFileSourceFiles.concat( + compilerResult.program ? + ts.filter(compilerResult.program.getSourceFiles(), sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)) : + []), + sourceFile => { + unitName: ts.isRootedDiskPath(sourceFile.fileName) ? + RunnerBase.removeFullPaths(sourceFile.fileName) : + sourceFile.fileName, + content: sourceFile.text + }); return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors); } diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 44d2c981c8476..128acb1005d68 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -30,6 +30,7 @@ namespace RWC { describe("Testing a RWC project: " + jsonPath, () => { let inputFiles: Harness.Compiler.TestFile[] = []; let otherFiles: Harness.Compiler.TestFile[] = []; + let tsconfigFiles: Harness.Compiler.TestFile[] = []; let compilerResult: Harness.Compiler.CompilerResult; let compilerOptions: ts.CompilerOptions; const baselineOpts: Harness.Baseline.BaselineOptions = { @@ -44,6 +45,7 @@ namespace RWC { // Therefore we have to clean out large objects after the test is done. inputFiles = []; otherFiles = []; + tsconfigFiles = []; compilerResult = undefined; compilerOptions = undefined; currentDirectory = undefined; @@ -74,16 +76,18 @@ namespace RWC { const tsconfigFile = ts.forEach(ioLog.filesRead, f => isTsConfigFile(f) ? f : undefined); if (tsconfigFile) { const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); - const parsedTsconfigFileContents = ts.parseConfigFileTextToJson(tsconfigFile.path, tsconfigFileContents.content); + tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); + const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists: Harness.IO.fileExists, readDirectory: Harness.IO.readDirectory, readFile: Harness.IO.readFile }; - const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); + const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); fileNames = configParseResult.fileNames; opts.options = ts.extend(opts.options, configParseResult.options); + ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); } // Load the files @@ -198,8 +202,8 @@ namespace RWC { return null; } // Do not include the library in the baselines to avoid noise - const baselineFiles = inputFiles.concat(otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); - const errors = compilerResult.errors.filter(e => e.file && !Harness.isDefaultLibraryFile(e.file.fileName)); + const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); + const errors = compilerResult.errors.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); return Harness.Compiler.getErrorBaseline(baselineFiles, errors); }, baselineOpts); }); @@ -218,7 +222,7 @@ namespace RWC { return Harness.Compiler.minimalDiagnosticsToString(declFileCompilationResult.declResult.errors) + Harness.IO.newLine() + Harness.IO.newLine() + - Harness.Compiler.getErrorBaseline(declFileCompilationResult.declInputFiles.concat(declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors); + Harness.Compiler.getErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors); }, baselineOpts); } }); @@ -259,4 +263,4 @@ class RWCRunner extends RunnerBase { private runTest(jsonFileName: string) { RWC.runRWCTest(jsonFileName); } -} \ No newline at end of file +} diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 7366df94ef6d9..eee6473f77f21 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -24,6 +24,7 @@ "../compiler/checker.ts", "../compiler/factory.ts", "../compiler/visitor.ts", + "../compiler/transformers/utilities.ts", "../compiler/transformers/ts.ts", "../compiler/transformers/jsx.ts", "../compiler/transformers/esnext.ts", diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 9bb264b263762..46d9aa462ce3f 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -158,7 +158,7 @@ namespace ts { // setting compiler options discards module resolution cache fileExistsCalled = false; - const compilerOptions = ts.clone(project.getCompilerOptions()); + const compilerOptions = ts.cloneCompilerOptions(project.getCompilerOptions()); compilerOptions.target = ts.ScriptTarget.ES5; project.setCompilerOptions(compilerOptions); diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index ab5fab2b545d5..2d50d2cb2af97 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -111,23 +111,47 @@ namespace ts { ["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost], ["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost] ], ([testName, basePath, host]) => { + function getParseCommandLine(entry: string) { + const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + return ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + } + + function getParseCommandLineJsonSourceFile(entry: string) { + const jsonSourceFile = ts.readJsonConfigFile(entry, name => host.readFile(name)); + assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); + return { + jsonSourceFile, + parsed: ts.parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry) + }; + } + function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { + expected.configFilePath = entry; it(name, () => { - const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + const parsed = getParseCommandLine(entry); + assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + assert.deepEqual(parsed.options, expected); + assert.deepEqual(parsed.fileNames, expectedFiles); + }); + + it(name + " with jsonSourceFile", () => { + const { parsed, jsonSourceFile } = getParseCommandLineJsonSourceFile(entry); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - expected.configFilePath = entry; assert.deepEqual(parsed.options, expected); + assert.equal(parsed.options.configFile, jsonSourceFile); assert.deepEqual(parsed.fileNames, expectedFiles); }); } - function testFailure(name: string, entry: string, expectedDiagnostics: {code: number, category: DiagnosticCategory, messageText: string}[]) { + function testFailure(name: string, entry: string, expectedDiagnostics: { code: number, category: DiagnosticCategory, messageText: string }[]) { it(name, () => { - const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); - const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); + const parsed = getParseCommandLine(entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); + + it(name + " with jsonSourceFile", () => { + const { parsed } = getParseCommandLineJsonSourceFile(entry); verifyDiagnostics(parsed.errors, expectedDiagnostics); }); } @@ -185,4 +209,4 @@ namespace ts { }); }); }); -} \ No newline at end of file +} diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index 798f8c6a76bb8..4b2fad32d7b55 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -4,6 +4,11 @@ namespace ts { describe("convertCompilerOptionsFromJson", () => { function assertCompilerOptions(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { + assertCompilerOptionsWithJson(json, configFileName, expectedResult); + assertCompilerOptionsWithJsonNode(json, configFileName, expectedResult); + } + + function assertCompilerOptionsWithJson(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { const { options: actualCompilerOptions, errors: actualErrors} = convertCompilerOptionsFromJson(json["compilerOptions"], "/apath/", configFileName); const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); @@ -21,6 +26,34 @@ namespace ts { } } + function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { + const fileText = JSON.stringify(json); + const result = parseJsonText(configFileName, fileText); + assert(!result.parseDiagnostics.length); + assert(!!result.endOfFileToken); + const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); + const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + expectedResult.compilerOptions["configFilePath"] = configFileName; + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + assert.equal(actualCompilerOptions.configFile, result); + + const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + const expectedErrors = expectedResult.errors; + assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); + for (let i = 0; i < actualErrors.length; i++) { + const actualError = actualErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); + assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); + } + } + // tsconfig.json tests it("Convert correctly format tsconfig.json to compiler-options ", () => { assertCompilerOptions( diff --git a/src/harness/unittests/convertTypeAcquisitionFromJson.ts b/src/harness/unittests/convertTypeAcquisitionFromJson.ts index 4c21b7ca2b99d..aae4ee3838212 100644 --- a/src/harness/unittests/convertTypeAcquisitionFromJson.ts +++ b/src/harness/unittests/convertTypeAcquisitionFromJson.ts @@ -2,14 +2,20 @@ /// namespace ts { + type ExpectedResult = { typeAcquisition: TypeAcquisition, errors: Diagnostic[] }; describe("convertTypeAcquisitionFromJson", () => { - function assertTypeAcquisition(json: any, configFileName: string, expectedResult: { typeAcquisition: TypeAcquisition, errors: Diagnostic[] }) { - const jsonOptions = json["typeAcquisition"] || json["typingOptions"]; - const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); + function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { + assertTypeAcquisitionWithJson(json, configFileName, expectedResult); + assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); + } + + function verifyAcquisition(actualTypeAcquisition: TypeAcquisition, expectedResult: ExpectedResult) { const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition); assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); + } + function verifyErrors(actualErrors: Diagnostic[], expectedResult: ExpectedResult, hasLocation?: boolean) { const expectedErrors = expectedResult.errors; assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`); for (let i = 0; i < actualErrors.length; i++) { @@ -17,9 +23,34 @@ namespace ts { const expectedError = expectedErrors[i]; assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`); assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`); + if (hasLocation) { + assert(actualError.file); + assert(actualError.start); + assert(actualError.length); + } } } + function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: ExpectedResult) { + const jsonOptions = json["typeAcquisition"] || json["typingOptions"]; + const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); + verifyErrors(actualErrors, expectedResult); + } + + function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { + const fileText = JSON.stringify(json); + const result = parseJsonText(configFileName, fileText); + assert(!result.parseDiagnostics.length); + assert(!!result.endOfFileToken); + const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); + const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); + verifyAcquisition(actualTypeAcquisition, expectedResult); + + const actualErrors = filter(actualParseErrors, error => error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); + } + // tsconfig.json it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => { assertTypeAcquisition( @@ -177,7 +208,7 @@ namespace ts { }, errors: [ { - category: Diagnostics.Unknown_compiler_option_0.category, + category: Diagnostics.Unknown_type_acquisition_option_0.category, code: Diagnostics.Unknown_type_acquisition_option_0.code, file: undefined, start: 0, diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index 652379abb3196..e04546719302a 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -95,6 +95,44 @@ namespace ts { assert.deepEqual(actual.errors, expected.errors); } + function validateMatches(expected: ts.ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { + { + const jsonText = JSON.stringify(json); + const result = parseJsonText(caseInsensitiveTsconfigPath, jsonText); + const actual = ts.parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); + for (const error of expected.errors) { + if (error.file) { + error.file = result; + } + } + assertParsed(actual, expected); + } + { + const actual = ts.parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); + expected.errors = map(expected.errors, error => { + return { + category: error.category, + code: error.code, + file: undefined, + length: undefined, + messageText: error.messageText, + start: undefined, + }; + }); + assertParsed(actual, expected); + } + } + + function createDiagnosticForConfigFile(json: any, start: number, length: number, diagnosticMessage: DiagnosticMessage, arg0: string) { + const text = JSON.stringify(json); + const file = { + fileName: caseInsensitiveTsconfigPath, + kind: SyntaxKind.SourceFile, + text + }; + return ts.createFileDiagnostic(file, start, length, diagnosticMessage, arg0); + } + describe("matchFiles", () => { it("with defaults", () => { const json = {}; @@ -109,8 +147,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); describe("with literal file list", () => { @@ -130,8 +167,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("missing files are still present", () => { const json = { @@ -149,8 +185,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("are not removed due to excludes", () => { const json = { @@ -171,8 +206,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -193,8 +227,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with non .ts file extensions are excluded", () => { const json = { @@ -212,8 +245,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("with missing files are excluded", () => { const json = { @@ -231,8 +263,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("with literal excludes", () => { const json = { @@ -252,8 +283,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with wildcard excludes", () => { const json = { @@ -280,8 +310,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with recursive excludes", () => { const json = { @@ -307,8 +336,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with case sensitive exclude", () => { const json = { @@ -327,8 +355,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseSensitiveHost, caseSensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); it("with common package folders and no exclusions", () => { const json = { @@ -352,8 +379,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and exclusions", () => { const json = { @@ -379,8 +405,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and empty exclude", () => { const json = { @@ -404,8 +429,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); }); @@ -436,8 +460,7 @@ namespace ts { "c:/dev/x": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("same named declarations are excluded", () => { @@ -458,8 +481,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("`*` matches only ts files", () => { const json = { @@ -479,8 +501,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("`?` matches only a single character", () => { const json = { @@ -499,8 +520,7 @@ namespace ts { "c:/dev/x": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with recursive directory", () => { const json = { @@ -521,8 +541,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with multiple recursive directories", () => { const json = { @@ -545,8 +564,7 @@ namespace ts { "c:/dev/z": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("case sensitive", () => { const json = { @@ -564,8 +582,7 @@ namespace ts { "/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseSensitiveHost, caseSensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); it("with missing files are excluded", () => { const json = { @@ -584,8 +601,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("always include literal files", () => { const json = { @@ -609,8 +625,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude folders", () => { const json = { @@ -634,8 +649,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with common package folders and no exclusions", () => { const json = { @@ -656,8 +670,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and exclusions", () => { const json = { @@ -680,8 +693,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and empty exclude", () => { const json = { @@ -703,8 +715,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("exclude .js files when allowJs=false", () => { const json = { @@ -728,8 +739,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("include .js files when allowJs=true", () => { const json = { @@ -753,8 +763,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include explicitly listed .min.js files when allowJs=true", () => { const json = { @@ -778,8 +787,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include paths outside of the project", () => { const json = { @@ -802,8 +810,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include paths outside of the project using relative paths", () => { const json = { @@ -825,8 +832,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude paths outside of the project using relative paths", () => { const json = { @@ -846,8 +852,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("include files with .. in their name", () => { const json = { @@ -866,8 +871,7 @@ namespace ts { ], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude files with .. in their name", () => { const json = { @@ -888,8 +892,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with jsx=none, allowJs=false", () => { const json = { @@ -911,8 +914,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=preserve, allowJs=false", () => { const json = { @@ -936,8 +938,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=react-native, allowJs=false", () => { const json = { @@ -961,8 +962,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=none, allowJs=true", () => { const json = { @@ -986,8 +986,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=preserve, allowJs=true", () => { const json = { @@ -1013,8 +1012,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=react-native, allowJs=true", () => { const json = { @@ -1040,8 +1038,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("exclude .min.js files using wildcards", () => { const json = { @@ -1067,8 +1064,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); describe("with trailing recursive directory", () => { it("in includes", () => { @@ -1080,15 +1076,14 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), + createDiagnosticForConfigFile(json, 12, 4, ts.Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**"), ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("in excludes", () => { const json = { @@ -1108,8 +1103,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); }); describe("with multiple recursive directory patterns", () => { @@ -1122,15 +1116,14 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**/*"), + createDiagnosticForConfigFile(json, 12, 11, ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**/*"), ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("in excludes", () => { const json = { @@ -1144,7 +1137,7 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**") + createDiagnosticForConfigFile(json, 34, 9, ts.Diagnostics.File_specification_cannot_contain_multiple_recursive_directory_wildcards_Asterisk_Asterisk_Colon_0, "**/x/**") ], fileNames: [ "c:/dev/a.ts", @@ -1156,8 +1149,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -1171,15 +1163,14 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), + createDiagnosticForConfigFile(json, 12, 9, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/../*"), ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("in includes after a subdirectory", () => { @@ -1191,15 +1182,14 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), + createDiagnosticForConfigFile(json, 12, 11, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/../*"), ts.createCompilerDiagnostic(ts.Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, caseInsensitiveTsconfigPath, JSON.stringify(json.include), "[]") ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); it("in excludes immediately after", () => { @@ -1214,7 +1204,7 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") + createDiagnosticForConfigFile(json, 34, 7, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/..") ], fileNames: [ "c:/dev/a.ts", @@ -1226,8 +1216,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in excludes after a subdirectory", () => { @@ -1242,7 +1231,7 @@ namespace ts { const expected: ts.ParsedCommandLine = { options: {}, errors: [ - ts.createCompilerDiagnostic(ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") + createDiagnosticForConfigFile(json, 34, 9, ts.Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, "**/y/..") ], fileNames: [ "c:/dev/a.ts", @@ -1254,8 +1243,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -1272,8 +1260,7 @@ namespace ts { "c:/dev/z": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); }); @@ -1297,8 +1284,7 @@ namespace ts { "c:/dev/w": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); describe("that are explicitly included", () => { it("without wildcards", () => { @@ -1317,8 +1303,7 @@ namespace ts { ], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); it("with recursive wildcards that match directories", () => { const json = { @@ -1339,8 +1324,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); it("with recursive wildcards that match nothing", () => { const json = { @@ -1361,8 +1345,7 @@ namespace ts { "c:/dev/x": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath); }); it("with wildcard excludes that implicitly exclude dotted files", () => { const json = { @@ -1382,8 +1365,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, /*existingOptions*/ undefined, caseInsensitiveTsconfigPath); }); }); }); diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 3db3675dbd6b0..30942fb2fdc6f 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -6,7 +6,10 @@ namespace ts.projectSystem { describe("Project errors", () => { function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: string[]) { assert.isTrue(projectFiles !== undefined, "missing project files"); - const errors = projectFiles.projectErrors; + checkProjectErrorsWorker(projectFiles.projectErrors, expectedErrors); + } + + function checkProjectErrorsWorker(errors: Diagnostic[], expectedErrors: string[]) { assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`); if (expectedErrors.length) { for (let i = 0; i < errors.length; i++) { @@ -122,12 +125,13 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, [ - "')' expected.", - "Declaration or statement expected.", - "Declaration or statement expected.", - "Failed to parse file '/a/b/tsconfig.json'" + checkProjectErrors(configuredProject, []); + const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file.fileName, corruptedConfig.path); } // fix config and trigger watcher host.reloadFS([file1, file2, correctConfig]); @@ -137,6 +141,8 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); + const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); } }); @@ -166,6 +172,8 @@ namespace ts.projectSystem { const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); checkProjectErrors(configuredProject, []); + const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, []); } // break config and trigger watcher host.reloadFS([file1, file2, corruptedConfig]); @@ -174,13 +182,14 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ configuredProjects: 1 }); const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f); assert.isTrue(configuredProject !== undefined, "should find configured project"); - checkProjectErrors(configuredProject, [ - "')' expected.", - "Declaration or statement expected.", - "Declaration or statement expected.", - "Failed to parse file '/a/b/tsconfig.json'" + checkProjectErrors(configuredProject, []); + const projectErrors = projectService.configuredProjects[0].getAllProjectErrors(); + checkProjectErrorsWorker(projectErrors, [ + "'{' expected." ]); + assert.isNotNull(projectErrors[0].file); + assert.equal(projectErrors[0].file.fileName, corruptedConfig.path); } }); }); -} \ No newline at end of file +} diff --git a/src/harness/unittests/telemetry.ts b/src/harness/unittests/telemetry.ts index 02be254c6f9ca..abc8dd2436049 100644 --- a/src/harness/unittests/telemetry.ts +++ b/src/harness/unittests/telemetry.ts @@ -45,7 +45,7 @@ namespace ts.projectSystem { it("works with external project", () => { const file1 = makeFile("/a.ts"); const et = new EventTracker([file1]); - const compilerOptions: ts.CompilerOptions = { strict: true }; + const compilerOptions: ts.server.protocol.CompilerOptions = { strict: true }; const projectFileName = "/hunter2/foo.csproj"; @@ -138,8 +138,6 @@ namespace ts.projectSystem { declaration: true, lib: ["es6", "dom"], - - checkJs: "" as any as boolean, }; (compilerOptions as any).unknownCompilerOption = "hunter2"; // These are always ignored. const tsconfig = makeFile("/tsconfig.json", { compilerOptions, files: ["/a.ts"] }); diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 7b980cdd2d8ee..8d56360bba50b 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -3,37 +3,69 @@ namespace ts { describe("parseConfigFileTextToJson", () => { - function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic }) { + function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject)); } function assertParseError(jsonText: string) { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - assert.isTrue(undefined === parsed.config); + assert.deepEqual(parsed.config, {}); assert.isTrue(undefined !== parsed.error); } function assertParseErrorWithExcludesKeyword(jsonText: string) { - const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "tests/cases/unittests"); - assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && - parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); + { + const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); + const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); + } + { + const parsed = ts.parseJsonText("/apath/tsconfig.json", jsonText); + const parsedCommand = ts.parseJsonSourceFileConfigFileContent(parsed, ts.sys, "tests/cases/unittests"); + assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 && + parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code); + } } - function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { - const json = JSON.parse(jsonText); + function getParsedCommandJson(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = ts.parseConfigFileTextToJson(configFileName, jsonText); const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList); - const parsed = ts.parseJsonConfigFileContent(json, host, basePath, /*existingOptions*/ undefined, configFileName); - assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); + return ts.parseJsonConfigFileContent(parsed.config, host, basePath, /*existingOptions*/ undefined, configFileName); } - function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number) { - const json = JSON.parse(jsonText); + function getParsedCommandJsonNode(jsonText: string, configFileName: string, basePath: string, allFileList: string[]) { + const parsed = ts.parseJsonText(configFileName, jsonText); const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList); - const parsed = ts.parseJsonConfigFileContent(json, host, basePath, /*existingOptions*/ undefined, configFileName); - assert.isTrue(parsed.errors.length >= 0); - assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + return ts.parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); + } + + function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); + } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort())); + } + } + + function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number, noLocation?: boolean) { + { + const parsed = getParsedCommandJson(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + } + { + const parsed = getParsedCommandJsonNode(jsonText, configFileName, basePath, allFileList); + assert.isTrue(parsed.errors.length >= 0); + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`); + if (!noLocation) { + assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode && e.file && e.start && e.length).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)} with location information`); + } + } } it("returns empty config for file with only whitespaces", () => { @@ -199,7 +231,9 @@ namespace ts { } "files": ["file1.ts"] }`; - const { configJsonObject, diagnostics } = sanitizeConfigFile("config.json", content); + const result = parseJsonText("config.json", content); + const diagnostics = result.parseDiagnostics; + const configJsonObject = convertToObject(result, diagnostics); const expectedResult = { compilerOptions: { allowJs: true, @@ -229,7 +263,8 @@ namespace ts { "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.js"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); }); it("generates errors for empty directory", () => { @@ -242,7 +277,8 @@ namespace ts { "/apath/tsconfig.json", "tests/cases/unittests", [], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); }); it("generates errors for empty include", () => { @@ -253,7 +289,8 @@ namespace ts { "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); }); it("generates errors for includes with outDir", () => { @@ -267,7 +304,8 @@ namespace ts { "/apath/tsconfig.json", "tests/cases/unittests", ["/apath/a.ts"], - Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code); + Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code, + /*noLocation*/ true); }); }); } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index d0ef78ad39dc9..3284c131bae75 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -735,7 +735,7 @@ namespace ts.projectSystem { checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path]); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); @@ -992,7 +992,7 @@ namespace ts.projectSystem { checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkProjectActualFiles(project, [file1.path, nodeModuleFile.path]); + checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); configFile.content = `{ @@ -1003,7 +1003,7 @@ namespace ts.projectSystem { }`; host.reloadFS(files); host.triggerFileWatcherCallback(configFile.path); - checkProjectActualFiles(project, [file1.path, classicModuleFile.path]); + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -1566,7 +1566,7 @@ namespace ts.projectSystem { host.reloadFS([file1, file2, file3, configFile]); host.triggerDirectoryWatcherCallback(getDirectoryPath(configFile.path), configFile.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, file3.path, configFile.path]); }); it("correctly migrate files between projects", () => { @@ -1624,7 +1624,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, configFile.path]); host.reloadFS([file1, file2, configFile]); @@ -1655,7 +1655,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, configFile.path]); const modifiedConfigFile = { path: configFile.path, @@ -1688,7 +1688,7 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, configFile.path]); const modifiedConfigFile = { path: configFile.path, @@ -1769,11 +1769,11 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); projectService.openClientFile(file2.path); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); host.reloadFS([file1, file2]); host.triggerFileWatcherCallback(config.path, /*removed*/ true); @@ -1808,13 +1808,13 @@ namespace ts.projectSystem { }); projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, config.path]); projectService.closeClientFile(f1.path); projectService.openClientFile(f2.path); projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, config.path]); checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); }); @@ -1838,7 +1838,7 @@ namespace ts.projectSystem { // HTML file will not be included in any projects yet checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); // Specify .html extension as mixed content const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; @@ -1847,7 +1847,7 @@ namespace ts.projectSystem { // HTML file still not included in the project as it is closed checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, config.path]); // Open HTML file projectService.applyChangesInOpenFiles( @@ -1857,7 +1857,7 @@ namespace ts.projectSystem { // Now HTML file is included in the project checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); // Check identifiers defined in HTML content are available in .ts file const project = projectService.configuredProjects[0]; @@ -1872,7 +1872,7 @@ namespace ts.projectSystem { // HTML file is still included in project checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]); // Check identifiers defined in HTML content are not available in .ts file completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5); @@ -2488,7 +2488,7 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, tsconfig.path]); // rename tsconfig.json back to lib.ts host.reloadFS([f1, f2]); @@ -2546,8 +2546,8 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path]); - checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path, cTsconfig.path]); + checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path, dTsconfig.path]); // remove one config file projectService.openExternalProject({ @@ -2557,7 +2557,7 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [dLib.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [dLib.path, dTsconfig.path]); // remove second config file projectService.openExternalProject({ @@ -2577,8 +2577,8 @@ namespace ts.projectSystem { options: {} }); projectService.checkNumberOfProjects({ configuredProjects: 2 }); - checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path]); - checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [cLib.path, cTsconfig.path]); + checkProjectActualFiles(projectService.configuredProjects[1], [dLib.path, dTsconfig.path]); // close all projects - no projects should be opened projectService.closeExternalProject(projectName); @@ -2634,13 +2634,13 @@ namespace ts.projectSystem { projectService.openClientFile(app.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]); host.reloadFS([libES5, libES2015Promise, app, config2]); host.triggerFileWatcherCallback(config1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]); }); it("should handle non-existing directories in config file", () => { @@ -2695,7 +2695,7 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, barTypings.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, barTypings.path, config.path]); }); }); @@ -2766,7 +2766,7 @@ namespace ts.projectSystem { projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t1.path, tsconfig.path]); // delete t1 host.reloadFS([f1, tsconfig]); @@ -2775,7 +2775,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, tsconfig.path]); // create t2 host.reloadFS([f1, tsconfig, t2]); @@ -2784,7 +2784,7 @@ namespace ts.projectSystem { host.runQueuedTimeoutCallbacks(); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t2.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, t2.path, tsconfig.path]); }); }); @@ -2969,7 +2969,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); projectService.openClientFile(f1.path); projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, node.path]); + checkProjectActualFiles(projectService.configuredProjects[0], [f1.path, node.path, config.path]); }); }); @@ -3980,4 +3980,69 @@ namespace ts.projectSystem { assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); }); }); + + describe("Options Diagnostic locations reported correctly with changes in configFile contents", () => { + it("when options change", () => { + const file = { + path: "/a/b/app.ts", + content: "let x = 10" + }; + const configFileContentBeforeComment = `{`; + const configFileContentComment = ` + // comment`; + const configFileContentAfterComment = ` + "compilerOptions": { + "allowJs": true, + "declaration": true + } + }`; + const configFileContentWithComment = configFileContentBeforeComment + configFileContentComment + configFileContentAfterComment; + const configFileContentWithoutCommentLine = configFileContentBeforeComment + configFileContentAfterComment; + + const configFile = { + path: "/a/b/tsconfig.json", + content: configFileContentWithComment + }; + const host = createServerHost([file, libFile, configFile]); + const session = createSession(host); + openFilesForSession([file], session); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const projectName = projectService.configuredProjects[0].getProjectName(); + + const diags = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + }).response; + assert.isTrue(diags.length === 2); + + configFile.content = configFileContentWithoutCommentLine; + host.reloadFS([file, configFile]); + host.triggerFileWatcherCallback(configFile.path); + + const diagsAfterEdit = session.executeCommand({ + type: "request", + command: server.CommandNames.SemanticDiagnosticsSync, + seq: 2, + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } + }).response; + assert.isTrue(diagsAfterEdit.length === 2); + + verifyDiagnostic(diags[0], diagsAfterEdit[0]); + verifyDiagnostic(diags[1], diagsAfterEdit[1]); + + function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePosition, afterEditDiag: server.protocol.DiagnosticWithLinePosition) { + assert.equal(beforeEditDiag.message, afterEditDiag.message); + assert.equal(beforeEditDiag.code, afterEditDiag.code); + assert.equal(beforeEditDiag.category, afterEditDiag.category); + assert.equal(beforeEditDiag.startLocation.line, afterEditDiag.startLocation.line + 1); + assert.equal(beforeEditDiag.startLocation.offset, afterEditDiag.startLocation.offset); + assert.equal(beforeEditDiag.endLocation.line, afterEditDiag.endLocation.line + 1); + assert.equal(beforeEditDiag.endLocation.offset, afterEditDiag.endLocation.offset); + } + }); + }); } diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index c356d0941c02c..6cbef411ebf16 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -80,7 +80,7 @@ namespace ts.projectSystem { const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(f1.path); service.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(service.configuredProjects[0], [f1.path, f2.path]); + checkProjectActualFiles(service.configuredProjects[0], [f1.path, f2.path, config.path]); installer.installAll(0); }); }); @@ -133,12 +133,12 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = projectService.configuredProjects[0]; - checkProjectActualFiles(p, [file1.path]); + checkProjectActualFiles(p, [file1.path, tsconfig.path]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(p, [file1.path, jquery.path]); + checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]); }); it("inferred project (typings installed)", () => { @@ -684,12 +684,12 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = projectService.configuredProjects[0]; - checkProjectActualFiles(p, [app.path]); + checkProjectActualFiles(p, [app.path, jsconfig.path]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(p, [app.path, jqueryDTS.path]); + checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); it("configured projects discover from bower_components", () => { @@ -730,13 +730,13 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = projectService.configuredProjects[0]; - checkProjectActualFiles(p, [app.path]); + checkProjectActualFiles(p, [app.path, jsconfig.path]); checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules"]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(p, [app.path, jqueryDTS.path]); + checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); it("configured projects discover from bower.json", () => { @@ -777,12 +777,12 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const p = projectService.configuredProjects[0]; - checkProjectActualFiles(p, [app.path]); + checkProjectActualFiles(p, [app.path, jsconfig.path]); installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(p, [app.path, jqueryDTS.path]); + checkProjectActualFiles(p, [app.path, jqueryDTS.path, jsconfig.path]); }); it("Malformed package.json should be watched", () => { @@ -1206,4 +1206,4 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]); }); }); -} \ No newline at end of file +} diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index ce7ea4875e5b1..649b5fd5a77e8 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -976,20 +976,14 @@ namespace ts.server { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); - let errors: Diagnostic[]; - - const result = parseConfigFileTextToJson(configFilename, configFileContent); - let config = result.config; - if (result.error) { - // try to reparse config file - const { configJsonObject: sanitizedConfig, diagnostics } = sanitizeConfigFile(configFilename, configFileContent); - config = sanitizedConfig; - errors = diagnostics.length ? diagnostics : [result.error]; + const result = parseJsonText(configFilename, configFileContent); + if (!result.endOfFileToken) { + result.endOfFileToken = { kind: SyntaxKind.EndOfFileToken }; } - - const parsedCommandLine = parseJsonConfigFileContent( - config, + const errors = result.parseDiagnostics; + const parsedCommandLine = parseJsonSourceFileConfigFileContent( + result, this.host, getDirectoryPath(configFilename), /*existingOptions*/ {}, @@ -998,23 +992,23 @@ namespace ts.server { this.hostConfiguration.extraFileExtensions); if (parsedCommandLine.errors.length) { - errors = concatenate(errors, parsedCommandLine.errors); + errors.push(...parsedCommandLine.errors); } Debug.assert(!!parsedCommandLine.fileNames); if (parsedCommandLine.fileNames.length === 0) { - (errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename)); + errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename)); return { success: false, configFileErrors: errors }; } const projectOptions: ProjectOptions = { files: parsedCommandLine.fileNames, compilerOptions: parsedCommandLine.options, - configHasExtendsProperty: config.extends !== undefined, - configHasFilesProperty: config.files !== undefined, - configHasIncludeProperty: config.include !== undefined, - configHasExcludeProperty: config.exclude !== undefined, + configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined, + configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined, + configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined, + configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined, wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories), typeAcquisition: parsedCommandLine.typeAcquisition, compileOnSave: parsedCommandLine.compileOnSave @@ -1183,7 +1177,7 @@ namespace ts.server { return { success: conversionResult.success, project, - errors: project.getProjectErrors() + errors: project.getGlobalProjectErrors() }; } diff --git a/src/server/project.ts b/src/server/project.ts index 0b9df73ccac43..ac040a77ace24 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -216,7 +216,14 @@ namespace ts.server { } } - getProjectErrors() { + /** + * Get the errors that dont have any file name associated + */ + getGlobalProjectErrors() { + return filter(this.projectErrors, diagnostic => !diagnostic.file); + } + + getAllProjectErrors() { return this.projectErrors; } @@ -363,7 +370,7 @@ namespace ts.server { return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); } - getFileNames(excludeFilesFromExternalLibraries?: boolean) { + getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { if (!this.program) { return []; } @@ -386,9 +393,39 @@ namespace ts.server { } result.push(asNormalizedPath(f.fileName)); } + if (!excludeConfigFiles) { + const configFile = this.program.getCompilerOptions().configFile; + if (configFile) { + result.push(asNormalizedPath(configFile.fileName)); + if (configFile.extendedSourceFiles) { + for (const f of configFile.extendedSourceFiles) { + result.push(asNormalizedPath(f)); + } + } + } + } return result; } + hasConfigFile(configFilePath: NormalizedPath) { + if (this.program && this.languageServiceEnabled) { + const configFile = this.program.getCompilerOptions().configFile; + if (configFile) { + if (configFilePath === asNormalizedPath(configFile.fileName)) { + return true; + } + if (configFile.extendedSourceFiles) { + for (const f of configFile.extendedSourceFiles) { + if (configFilePath === asNormalizedPath(f)) { + return true; + } + } + } + } + } + return false; + } + getAllEmittableFiles() { if (!this.languageServiceEnabled) { return []; @@ -663,7 +700,7 @@ namespace ts.server { if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { // if current structure version is the same - return info without any changes if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) { - return { info, projectErrors: this.projectErrors }; + return { info, projectErrors: this.getGlobalProjectErrors() }; } // compute and return the difference const lastReportedFileNames = this.lastReportedFileNames; @@ -685,14 +722,14 @@ namespace ts.server { }); this.lastReportedFileNames = currentFiles; this.lastReportedVersion = this.projectStructureVersion; - return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors }; + return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() }; } else { // unknown version - return everything const projectFileNames = this.getFileNames(); this.lastReportedFileNames = arrayToMap(projectFileNames, x => x); this.lastReportedVersion = this.projectStructureVersion; - return { info, files: projectFileNames, projectErrors: this.projectErrors }; + return { info, files: projectFileNames, projectErrors: this.getGlobalProjectErrors() }; } } @@ -782,7 +819,7 @@ namespace ts.server { setCompilerOptions(options?: CompilerOptions) { // Avoid manipulating the given options directly - const newOptions = options ? clone(options) : this.getCompilerOptions(); + const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions(); if (!newOptions) { return; } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index f79774abf2058..916d9c51a1440 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1943,6 +1943,13 @@ namespace ts.server.protocol { source?: string; } + export interface DiagnosticWithFileName extends Diagnostic { + /** + * Name of the file the diagnostic is in + */ + fileName: string; + } + export interface DiagnosticEventBody { /** * The file for which diagnostic information is reported. @@ -1977,7 +1984,7 @@ namespace ts.server.protocol { /** * An arry of diagnostic information items for the found config file. */ - diagnostics: Diagnostic[]; + diagnostics: DiagnosticWithFileName[]; } /** diff --git a/src/server/session.ts b/src/server/session.ts index c06e33eb9797f..3f4450e4f4d3e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -84,14 +84,20 @@ namespace ts.server { }; } - function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic { - return { - start: undefined, - end: undefined, - text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), - category: DiagnosticCategory[diag.category].toLowerCase(), - source: diag.source - }; + function convertToILineInfo(lineAndCharacter: LineAndCharacter): ILineInfo { + return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 }; + } + + function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName; + function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: false): protocol.Diagnostic; + function formatConfigFileDiag(diag: ts.Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName { + const start = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start)); + const end = diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)); + const text = ts.flattenDiagnosticMessageText(diag.messageText, "\n"); + const { code, source } = diag; + const category = DiagnosticCategory[diag.category].toLowerCase(); + return includeFileName ? { start, end, text, code, category, source, fileName: diag.file && diag.file.fileName } : + { start, end, text, code, category, source }; } export interface PendingErrorCheck { @@ -384,7 +390,7 @@ namespace ts.server { } public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) { - const bakedDiags = ts.map(diagnostics, formatConfigFileDiag); + const bakedDiags = ts.map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true)); const ev: protocol.ConfigFileDiagnosticEvent = { seq: 0, type: "event", @@ -517,9 +523,55 @@ namespace ts.server { return projectFileName && this.projectService.findProject(projectFileName); } + private getConfigFileAndProject(args: protocol.FileRequestArgs) { + const project = this.getProject(args.projectFileName); + const file = toNormalizedPath(args.file); + + return { + configFile: project && project.hasConfigFile(file) && file, + project + }; + } + + private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) { + const projectErrors = project.getAllProjectErrors(); + const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics(); + const diagnosticsForConfigFile = filter( + concatenate(projectErrors, optionsErrors), + diagnostic => diagnostic.file && diagnostic.file.fileName === configFile + ); + return includeLinePosition ? + this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) : + map( + diagnosticsForConfigFile, + diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ false) + ); + } + + private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) { + return diagnostics.map(d => { + message: flattenDiagnosticMessageText(d.messageText, this.host.newLine), + start: d.start, + length: d.length, + category: DiagnosticCategory[d.category].toLowerCase(), + code: d.code, + startLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start)), + endLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start + d.length)) + }); + } + private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) { const project = this.getProject(args.projectFileName); - return this.convertToDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics(), /*scriptInfo*/ undefined); + // Get diagnostics that dont have associated file with them + // The diagnostics which have file would be in config file and + // would be reported as part of configFileDiagnostics + return this.convertToDiagnosticsWithLinePosition( + filter( + project.getLanguageService().getCompilerOptionsDiagnostics(), + diagnostic => !diagnostic.file + ), + /*scriptInfo*/ undefined + ); } private convertToDiagnosticsWithLinePosition(diagnostics: Diagnostic[], scriptInfo: ScriptInfo) { @@ -645,10 +697,20 @@ namespace ts.server { } private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] { + const { configFile } = this.getConfigFileAndProject(args); + if (configFile) { + // all the config file errors are reported as part of semantic check so nothing to report here + return []; + } + return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition); } private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] { + const { configFile, project } = this.getConfigFileAndProject(args); + if (configFile) { + return this.getConfigFileDiagnostics(configFile, project, args.includeLinePosition); + } return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition); } @@ -692,15 +754,15 @@ namespace ts.server { } private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo { - return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList); + return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false); } - private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean) { + private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean, excludeConfigFiles: boolean) { const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*refreshInferredProjects*/ true, /*errorOnMissingProject*/ true); const projectInfo = { configFileName: project.getProjectName(), languageServiceDisabled: !project.languageServiceEnabled, - fileNames: needFileNameList ? project.getFileNames() : undefined + fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined }; return projectInfo; } @@ -1553,7 +1615,7 @@ namespace ts.server { } private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void { - const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true); + const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true); if (languageServiceDisabled) { return; } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 9e492601430f8..10290fe864da2 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -49,7 +49,7 @@ namespace ts.server { export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings { return { projectName: project.getProjectName(), - fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true), + fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true), compilerOptions: project.getCompilerOptions(), typeAcquisition, unresolvedImports, diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 5e31d6a0190f0..d84ae01e0a071 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -76,7 +76,7 @@ namespace ts.JsTyping { if (!safeList) { const result = readConfigFile(safeListPath, (path: string) => host.readFile(path)); - safeList = result.config ? createMapFromTemplate(result.config) : EmptySafeList; + safeList = createMapFromTemplate(result.config); } const filesToWatch: string[] = []; @@ -163,20 +163,18 @@ namespace ts.JsTyping { filesToWatch.push(jsonPath); } const result = readConfigFile(jsonPath, (path: string) => host.readFile(path)); - if (result.config) { - const jsonConfig: PackageJson = result.config; - if (jsonConfig.dependencies) { - mergeTypings(getOwnKeys(jsonConfig.dependencies)); - } - if (jsonConfig.devDependencies) { - mergeTypings(getOwnKeys(jsonConfig.devDependencies)); - } - if (jsonConfig.optionalDependencies) { - mergeTypings(getOwnKeys(jsonConfig.optionalDependencies)); - } - if (jsonConfig.peerDependencies) { - mergeTypings(getOwnKeys(jsonConfig.peerDependencies)); - } + const jsonConfig: PackageJson = result.config; + if (jsonConfig.dependencies) { + mergeTypings(getOwnKeys(jsonConfig.dependencies)); + } + if (jsonConfig.devDependencies) { + mergeTypings(getOwnKeys(jsonConfig.devDependencies)); + } + if (jsonConfig.optionalDependencies) { + mergeTypings(getOwnKeys(jsonConfig.optionalDependencies)); + } + if (jsonConfig.peerDependencies) { + mergeTypings(getOwnKeys(jsonConfig.peerDependencies)); } } @@ -222,9 +220,6 @@ namespace ts.JsTyping { continue; } const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); - if (!result.config) { - continue; - } const packageJson: PackageJson = result.config; // npm 3's package.json contains a "_requiredBy" field diff --git a/src/services/services.ts b/src/services/services.ts index 17ad547ed5e7f..b508285b182fb 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1299,8 +1299,20 @@ namespace ts { } } + const currentOptions = program.getCompilerOptions(); + const newOptions = hostCache.compilationSettings(); // If the compilation settings do no match, then the program is not up-to-date - return compareDataObjects(program.getCompilerOptions(), hostCache.compilationSettings()); + if (!compareDataObjects(currentOptions, newOptions)) { + return false; + } + + // If everything matches but the text of config file is changed, + // error locations can change for program options, so update the program + if (currentOptions.configFile && newOptions.configFile) { + return currentOptions.configFile.text === newOptions.configFile.text; + } + + return true; } } diff --git a/src/services/shims.ts b/src/services/shims.ts index 5ad7a5f6161e2..1c37f598021d3 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1109,27 +1109,16 @@ namespace ts { () => { const text = sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()); - const result = parseConfigFileTextToJson(fileName, text); - - if (result.error) { - return { - options: {}, - typeAcquisition: {}, - files: [], - raw: {}, - errors: [realizeDiagnostic(result.error, "\r\n")] - }; - } - + const result = parseJsonText(fileName, text); const normalizedFileName = normalizeSlashes(fileName); - const configFile = parseJsonConfigFileContent(result.config, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); + const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); return { options: configFile.options, typeAcquisition: configFile.typeAcquisition, files: configFile.fileNames, raw: configFile.raw, - errors: realizeDiagnostics(configFile.errors, "\r\n") + errors: realizeDiagnostics(result.parseDiagnostics.concat(configFile.errors), "\r\n") }; }); } @@ -1248,4 +1237,4 @@ namespace TypeScript.Services { // TODO: it should be moved into a namespace though. /* @internal */ -const toolsVersion = "2.5"; \ No newline at end of file +const toolsVersion = "2.5"; diff --git a/src/services/transpile.ts b/src/services/transpile.ts index e10fcc70a6449..561c188c6cdf4 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -130,7 +130,7 @@ namespace ts { commandLineOptionsStringToEnum = commandLineOptionsStringToEnum || filter(optionDeclarations, o => typeof o.type === "object" && !forEachEntry(o.type, v => typeof v !== "number")); - options = clone(options); + options = cloneCompilerOptions(options); for (const opt of commandLineOptionsStringToEnum) { if (!hasProperty(options, opt.name)) { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 0c1d76e7ae5d2..8f8ed11a18835 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -17,6 +17,7 @@ "../compiler/checker.ts", "../compiler/factory.ts", "../compiler/visitor.ts", + "../compiler/transformers/utilities.ts", "../compiler/transformers/ts.ts", "../compiler/transformers/jsx.ts", "../compiler/transformers/esnext.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b7717edd86723..78c7140373631 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -982,6 +982,12 @@ namespace ts { return false; } + export function cloneCompilerOptions(options: CompilerOptions): CompilerOptions { + const result = clone(options); + setConfigFileInOptions(result, options && options.configFile); + return result; + } + export function compareDataObjects(dst: any, src: any): boolean { if (!dst || !src || Object.keys(dst).length !== Object.keys(src).length) { return false; @@ -1306,30 +1312,6 @@ namespace ts { return ensureScriptKind(fileName, scriptKind); } - export function sanitizeConfigFile(configFileName: string, content: string) { - const options: TranspileOptions = { - fileName: "config.js", - compilerOptions: { - target: ScriptTarget.ES2015, - removeComments: true - }, - reportDiagnostics: true - }; - const { outputText, diagnostics } = ts.transpileModule("(" + content + ")", options); - // Becasue the content was wrapped in "()", the start position of diagnostics needs to be subtract by 1 - // also, the emitted result will have "(" in the beginning and ");" in the end. We need to strip these - // as well - const trimmedOutput = outputText.trim(); - for (const diagnostic of diagnostics) { - diagnostic.start = diagnostic.start - 1; - } - const {config, error} = parseConfigFileTextToJson(configFileName, trimmedOutput.substring(1, trimmedOutput.length - 2), /*stripComments*/ false); - return { - configJsonObject: config || {}, - diagnostics: error ? concatenate(diagnostics, [error]) : diagnostics - }; - } - export function getFirstNonSpaceCharacterPosition(text: string, position: number) { while (isWhiteSpaceLike(text.charCodeAt(position))) { position += 1; diff --git a/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt b/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt index 2218e7910aefd..e9455a4efbd2f 100644 --- a/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt +++ b/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt @@ -1,6 +1,16 @@ /index.ts(4,5): error TS2339: Property 'y' does not exist on type 'typeof "shortid"'. +==== /tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "module": "commonjs", + "moduleResolution": "node", + "outDir": "bin" + }, + "exclude": [ "node_modules" ] + } ==== /index.ts (1 errors) ==== /// import * as foo from "shortid"; diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt index 85e25ec99fb93..8a52830e566d3 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt @@ -1,7 +1,22 @@ -error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. +c:/root/tsconfig.json(5,9): error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. +==== c:/root/tsconfig.json (1 errors) ==== + // paths should error in the absence of baseurl + + { + "compilerOptions": { + "paths": { + ~~~~~~~ !!! error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. + "*": [ + "*", + "generated/*" + ] + } + } + } + ==== c:/root/f1.ts (0 errors) ==== export var x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt index 85e25ec99fb93..297f4dc85ad53 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt @@ -1,7 +1,21 @@ -error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. +c:/root/tsconfig.json(4,9): error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. +==== c:/root/tsconfig.json (1 errors) ==== + // paths should error in the absence of baseurl + { + "compilerOptions": { + "paths": { + ~~~~~~~ !!! error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. + "*": [ + "*", + "generated/*" + ] + } + } + } + ==== c:/root/f1.ts (0 errors) ==== export var x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt index 3b0a387f64441..3b0261dd39506 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt @@ -1,8 +1,23 @@ -error TS5061: Pattern '*1*' can have at most one '*' character. -error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. +tests/cases/compiler/root/tsconfig.json(8,13): error TS5061: Pattern '*1*' can have at most one '*' character. +tests/cases/compiler/root/tsconfig.json(8,22): error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. +==== tests/cases/compiler/root/tsconfig.json (2 errors) ==== + // baseurl is defined in tsconfig.json + // paths has errors + + { + "compilerOptions": { + "baseUrl": "./src", + "paths": { + "*1*": [ "*2*" ] + ~~~~~ !!! error TS5061: Pattern '*1*' can have at most one '*' character. + ~~~~~ !!! error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. + } + } + } + ==== tests/cases/compiler/root/src/folder1/file1.ts (0 errors) ==== export var x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt index 3b0a387f64441..3b0261dd39506 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt @@ -1,8 +1,23 @@ -error TS5061: Pattern '*1*' can have at most one '*' character. -error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. +tests/cases/compiler/root/tsconfig.json(8,13): error TS5061: Pattern '*1*' can have at most one '*' character. +tests/cases/compiler/root/tsconfig.json(8,22): error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. +==== tests/cases/compiler/root/tsconfig.json (2 errors) ==== + // baseurl is defined in tsconfig.json + // paths has errors + + { + "compilerOptions": { + "baseUrl": "./src", + "paths": { + "*1*": [ "*2*" ] + ~~~~~ !!! error TS5061: Pattern '*1*' can have at most one '*' character. + ~~~~~ !!! error TS5062: Substitution '*2*' in pattern '*1*' in can have at most one '*' character. + } + } + } + ==== tests/cases/compiler/root/src/folder1/file1.ts (0 errors) ==== export var x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt index 4b038b99bcc59..0ebe17354493f 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution_withExtension_failedLookup.errors.txt @@ -1,6 +1,16 @@ /a.ts(1,21): error TS2307: Cannot find module 'foo'. +==== /tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": ["foo/foo.ts"] + } + } + } + ==== /a.ts (1 errors) ==== import { foo } from "foo"; ~~~~~ diff --git a/tests/baselines/reference/pathsValidation1.errors.txt b/tests/baselines/reference/pathsValidation1.errors.txt index a2d7be5f355c5..f7330308da153 100644 --- a/tests/baselines/reference/pathsValidation1.errors.txt +++ b/tests/baselines/reference/pathsValidation1.errors.txt @@ -1,6 +1,16 @@ -error TS5063: Substitutions for pattern '*' should be an array. +tests/cases/compiler/tsconfig.json(5,18): error TS5063: Substitutions for pattern '*' should be an array. +==== tests/cases/compiler/tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": "*" + ~~~ !!! error TS5063: Substitutions for pattern '*' should be an array. + } + } + } ==== tests/cases/compiler/a.ts (0 errors) ==== let x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathsValidation2.errors.txt b/tests/baselines/reference/pathsValidation2.errors.txt index 8956b2fc15989..c8cf1617a9d8b 100644 --- a/tests/baselines/reference/pathsValidation2.errors.txt +++ b/tests/baselines/reference/pathsValidation2.errors.txt @@ -1,6 +1,16 @@ -error TS5064: Substitution '1' for pattern '*' has incorrect type, expected 'string', got 'number'. +tests/cases/compiler/tsconfig.json(5,19): error TS5064: Substitution '1' for pattern '*' has incorrect type, expected 'string', got 'number'. +==== tests/cases/compiler/tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [1] + ~ !!! error TS5064: Substitution '1' for pattern '*' has incorrect type, expected 'string', got 'number'. + } + } + } ==== tests/cases/compiler/a.ts (0 errors) ==== let x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/pathsValidation3.errors.txt b/tests/baselines/reference/pathsValidation3.errors.txt index 3bb85203e6e38..d701206290b83 100644 --- a/tests/baselines/reference/pathsValidation3.errors.txt +++ b/tests/baselines/reference/pathsValidation3.errors.txt @@ -1,6 +1,17 @@ -error TS5066: Substitutions for pattern 'foo' shouldn't be an empty array. +tests/cases/compiler/tsconfig.json(5,20): error TS5066: Substitutions for pattern 'foo' shouldn't be an empty array. +==== tests/cases/compiler/tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "foo": [] + ~~ !!! error TS5066: Substitutions for pattern 'foo' shouldn't be an empty array. + } + } + } + ==== tests/cases/compiler/a.ts (0 errors) ==== let x = 1; \ No newline at end of file diff --git a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/amd/emitDecoratorMetadataCommonJSISolatedModules.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/amd/emitDecoratorMetadataCommonJSISolatedModules.errors.txt index d6d803104c96a..f6ad25fddb1d9 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/amd/emitDecoratorMetadataCommonJSISolatedModules.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/amd/emitDecoratorMetadataCommonJSISolatedModules.errors.txt @@ -1,6 +1,20 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/node/emitDecoratorMetadataCommonJSISolatedModules.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/node/emitDecoratorMetadataCommonJSISolatedModules.errors.txt index d6d803104c96a..f6ad25fddb1d9 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/node/emitDecoratorMetadataCommonJSISolatedModules.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModules/node/emitDecoratorMetadataCommonJSISolatedModules.errors.txt @@ -1,6 +1,20 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/amd/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/amd/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt index d6d803104c96a..f4fe440f379c5 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/amd/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/amd/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt @@ -1,6 +1,21 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true, + "noResolve": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/node/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/node/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt index d6d803104c96a..f4fe440f379c5 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/node/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataCommonJSISolatedModulesNoResolve/node/emitDecoratorMetadataCommonJSISolatedModulesNoResolve.errors.txt @@ -1,6 +1,21 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true, + "noResolve": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/amd/emitDecoratorMetadataSystemJS.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/amd/emitDecoratorMetadataSystemJS.errors.txt index d6d803104c96a..bd34d2edc4e0e 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/amd/emitDecoratorMetadataSystemJS.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/amd/emitDecoratorMetadataSystemJS.errors.txt @@ -1,6 +1,19 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "emitDecoratorMetadata": true, + "experimentalDecorators": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/node/emitDecoratorMetadataSystemJS.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/node/emitDecoratorMetadataSystemJS.errors.txt index d6d803104c96a..bd34d2edc4e0e 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/node/emitDecoratorMetadataSystemJS.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJS/node/emitDecoratorMetadataSystemJS.errors.txt @@ -1,6 +1,19 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "emitDecoratorMetadata": true, + "experimentalDecorators": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/amd/emitDecoratorMetadataSystemJSISolatedModules.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/amd/emitDecoratorMetadataSystemJSISolatedModules.errors.txt index d6d803104c96a..b14b090b83964 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/amd/emitDecoratorMetadataSystemJSISolatedModules.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/amd/emitDecoratorMetadataSystemJSISolatedModules.errors.txt @@ -1,6 +1,21 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/node/emitDecoratorMetadataSystemJSISolatedModules.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/node/emitDecoratorMetadataSystemJSISolatedModules.errors.txt index d6d803104c96a..b14b090b83964 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/node/emitDecoratorMetadataSystemJSISolatedModules.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModules/node/emitDecoratorMetadataSystemJSISolatedModules.errors.txt @@ -1,6 +1,21 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/amd/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/amd/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt index d6d803104c96a..99fc00df24a34 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/amd/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/amd/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt @@ -1,6 +1,22 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true, + "noResolve": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/node/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/node/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt index d6d803104c96a..99fc00df24a34 100644 --- a/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/node/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt +++ b/tests/baselines/reference/project/emitDecoratorMetadataSystemJSISolatedModulesNoResolve/node/emitDecoratorMetadataSystemJSISolatedModulesNoResolve.errors.txt @@ -1,6 +1,22 @@ main.ts(1,21): error TS2307: Cannot find module 'angular2/core'. +==== tsconfig.json (0 errors) ==== + { + "compileOnSave": true, + "compilerOptions": { + "target": "es5", + "module": "system", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "isolatedModules": true, + "noResolve": true + }, + "files": [ + "main.ts" + ] + } ==== main.ts (1 errors) ==== import * as ng from "angular2/core"; ~~~~~~~~~~~~~~~ diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecified/node/jsFileCompilationDifferentNamesNotSpecified.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecified/node/jsFileCompilationDifferentNamesNotSpecified.errors.txt index e1a24fa384204..29ab30d47ec49 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecified/node/jsFileCompilationDifferentNamesNotSpecified.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecified/node/jsFileCompilationDifferentNamesNotSpecified.errors.txt @@ -1,6 +1,11 @@ -error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesNotSpecified/tsconfig.json(2,24): error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +==== DifferentNamesNotSpecified/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "out": "test.js" } + ~~~~~ !!! error TS6082: Only 'amd' and 'system' modules are supported alongside --out. + } ==== DifferentNamesNotSpecified/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt index 67b0592c05f1b..f7028d0655075 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt @@ -1,7 +1,15 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +DifferentNamesNotSpecifiedWithAllowJs/tsconfig.json(4,5): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== DifferentNamesNotSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "out": "test.js", + "allowJs": true + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + } + } ==== DifferentNamesNotSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; ==== DifferentNamesNotSpecifiedWithAllowJs/b.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt index dbebea69cceea..6a3c9863cc590 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesNotSpecifiedWithAllowJs.errors.txt @@ -1,9 +1,18 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesNotSpecifiedWithAllowJs/tsconfig.json(3,5): error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesNotSpecifiedWithAllowJs/tsconfig.json(4,5): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== DifferentNamesNotSpecifiedWithAllowJs/tsconfig.json (2 errors) ==== + { + "compilerOptions": { + "out": "test.js", + ~~~~~ !!! error TS6082: Only 'amd' and 'system' modules are supported alongside --out. + "allowJs": true + ~~~~~~~~~ +!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + } + } ==== DifferentNamesNotSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; ==== DifferentNamesNotSpecifiedWithAllowJs/b.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/amd/jsFileCompilationDifferentNamesSpecified.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/amd/jsFileCompilationDifferentNamesSpecified.errors.txt index 5fa60d18af99d..d737f25497c10 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/amd/jsFileCompilationDifferentNamesSpecified.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/amd/jsFileCompilationDifferentNamesSpecified.errors.txt @@ -2,5 +2,10 @@ error TS6054: File 'DifferentNamesSpecified/b.js' has unsupported extension. The !!! error TS6054: File 'DifferentNamesSpecified/b.js' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. +==== DifferentNamesSpecified/tsconfig.json (0 errors) ==== + { + "compilerOptions": { "out": "test.js" }, + "files": [ "a.ts", "b.js" ] + } ==== DifferentNamesSpecified/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/node/jsFileCompilationDifferentNamesSpecified.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/node/jsFileCompilationDifferentNamesSpecified.errors.txt index c05a142a93cc0..72b812099dc45 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/node/jsFileCompilationDifferentNamesSpecified.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecified/node/jsFileCompilationDifferentNamesSpecified.errors.txt @@ -1,8 +1,14 @@ error TS6054: File 'DifferentNamesSpecified/b.js' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. -error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesSpecified/tsconfig.json(2,24): error TS6082: Only 'amd' and 'system' modules are supported alongside --out. !!! error TS6054: File 'DifferentNamesSpecified/b.js' has unsupported extension. The only supported extensions are '.ts', '.tsx', '.d.ts'. +==== DifferentNamesSpecified/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "out": "test.js" }, + ~~~~~ !!! error TS6082: Only 'amd' and 'system' modules are supported alongside --out. + "files": [ "a.ts", "b.js" ] + } ==== DifferentNamesSpecified/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt index 35c913a5835ba..1c5034be858bc 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/amd/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt @@ -1,7 +1,16 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +DifferentNamesSpecifiedWithAllowJs/tsconfig.json(4,5): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== DifferentNamesSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { + "out": "test.js", + "allowJs": true + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + }, + "files": [ "a.ts", "b.js" ] + } ==== DifferentNamesSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; ==== DifferentNamesSpecifiedWithAllowJs/b.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt index c8c2ca976c61d..c2849fae3a580 100644 --- a/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationDifferentNamesSpecifiedWithAllowJs/node/jsFileCompilationDifferentNamesSpecifiedWithAllowJs.errors.txt @@ -1,9 +1,19 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesSpecifiedWithAllowJs/tsconfig.json(3,5): error TS6082: Only 'amd' and 'system' modules are supported alongside --out. +DifferentNamesSpecifiedWithAllowJs/tsconfig.json(4,5): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== DifferentNamesSpecifiedWithAllowJs/tsconfig.json (2 errors) ==== + { + "compilerOptions": { + "out": "test.js", + ~~~~~ !!! error TS6082: Only 'amd' and 'system' modules are supported alongside --out. + "allowJs": true + ~~~~~~~~~ +!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + }, + "files": [ "a.ts", "b.js" ] + } ==== DifferentNamesSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; ==== DifferentNamesSpecifiedWithAllowJs/b.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt index cdec4ffb398f3..1a625b3fe7d6f 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,12 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameDTsSpecifiedWithAllowJs/tsconfig.json(2,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameDTsSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "allowJs": true }, + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + "files": [ "a.d.ts" ] + } ==== SameNameDTsSpecifiedWithAllowJs/a.d.ts (0 errors) ==== declare var test: number; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/node/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/node/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt index cdec4ffb398f3..1a625b3fe7d6f 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/node/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameDTsSpecifiedWithAllowJs/node/jsFileCompilationSameNameDTsSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,12 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameDTsSpecifiedWithAllowJs/tsconfig.json(2,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameDTsSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "allowJs": true }, + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + "files": [ "a.d.ts" ] + } ==== SameNameDTsSpecifiedWithAllowJs/a.d.ts (0 errors) ==== declare var test: number; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt index eadcbd8ca994e..aa7418ccc1420 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt @@ -1,11 +1,14 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. error TS5055: Cannot write file 'SameNameDTsNotSpecifiedWithAllowJs/a.js' because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +SameNameDTsNotSpecifiedWithAllowJs/tsconfig.json(1,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. !!! error TS5055: Cannot write file 'SameNameDTsNotSpecifiedWithAllowJs/a.js' because it would overwrite input file. !!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== SameNameDTsNotSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { "compilerOptions": { "allowJs": true } } + ~~~~~~~~~ +!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. ==== SameNameDTsNotSpecifiedWithAllowJs/a.d.ts (0 errors) ==== declare var a: number; ==== SameNameDTsNotSpecifiedWithAllowJs/a.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt index eadcbd8ca994e..aa7418ccc1420 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameDtsNotSpecifiedWithAllowJs.errors.txt @@ -1,11 +1,14 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. error TS5055: Cannot write file 'SameNameDTsNotSpecifiedWithAllowJs/a.js' because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +SameNameDTsNotSpecifiedWithAllowJs/tsconfig.json(1,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. -!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. !!! error TS5055: Cannot write file 'SameNameDTsNotSpecifiedWithAllowJs/a.js' because it would overwrite input file. !!! error TS5055: Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig. +==== SameNameDTsNotSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { "compilerOptions": { "allowJs": true } } + ~~~~~~~~~ +!!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. ==== SameNameDTsNotSpecifiedWithAllowJs/a.d.ts (0 errors) ==== declare var a: number; ==== SameNameDTsNotSpecifiedWithAllowJs/a.js (0 errors) ==== diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt index 9eb81a5f9f13d..99e057bb6d05d 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,9 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameFilesNotSpecifiedWithAllowJs/tsconfig.json(1,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameFilesNotSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { "compilerOptions": { "allowJs": true } } + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. ==== SameNameFilesNotSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt index 9eb81a5f9f13d..99e057bb6d05d 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesNotSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,9 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameFilesNotSpecifiedWithAllowJs/tsconfig.json(1,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameFilesNotSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { "compilerOptions": { "allowJs": true } } + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. ==== SameNameFilesNotSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt index ddfb63686b402..f956b6780844e 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/amd/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,12 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameTsSpecifiedWithAllowJs/tsconfig.json(2,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameTsSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "allowJs": true }, + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + "files": [ "a.ts" ] + } ==== SameNameTsSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt b/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt index ddfb63686b402..f956b6780844e 100644 --- a/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt +++ b/tests/baselines/reference/project/jsFileCompilationSameNameFilesSpecifiedWithAllowJs/node/jsFileCompilationSameNameFilesSpecifiedWithAllowJs.errors.txt @@ -1,6 +1,12 @@ -error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +SameNameTsSpecifiedWithAllowJs/tsconfig.json(2,24): error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. +==== SameNameTsSpecifiedWithAllowJs/tsconfig.json (1 errors) ==== + { + "compilerOptions": { "allowJs": true }, + ~~~~~~~~~ !!! error TS5053: Option 'allowJs' cannot be specified with option 'declaration'. + "files": [ "a.ts" ] + } ==== SameNameTsSpecifiedWithAllowJs/a.ts (0 errors) ==== var test = 10; \ No newline at end of file diff --git a/tests/baselines/reference/project/nodeModulesImportHigher/amd/nodeModulesImportHigher.errors.txt b/tests/baselines/reference/project/nodeModulesImportHigher/amd/nodeModulesImportHigher.errors.txt index 8b4e11eb6065b..6c8b7319c9f28 100644 --- a/tests/baselines/reference/project/nodeModulesImportHigher/amd/nodeModulesImportHigher.errors.txt +++ b/tests/baselines/reference/project/nodeModulesImportHigher/amd/nodeModulesImportHigher.errors.txt @@ -1,6 +1,16 @@ importHigher/root.ts(6,1): error TS2322: Type '"10"' is not assignable to type 'number'. +==== importHigher/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "declaration": false, + "moduleResolution": "node", + "maxNodeModuleJsDepth": 2 + } + } + ==== entry.js (0 errors) ==== var m3 = require("m3"); diff --git a/tests/baselines/reference/project/nodeModulesImportHigher/node/nodeModulesImportHigher.errors.txt b/tests/baselines/reference/project/nodeModulesImportHigher/node/nodeModulesImportHigher.errors.txt index 8b4e11eb6065b..6c8b7319c9f28 100644 --- a/tests/baselines/reference/project/nodeModulesImportHigher/node/nodeModulesImportHigher.errors.txt +++ b/tests/baselines/reference/project/nodeModulesImportHigher/node/nodeModulesImportHigher.errors.txt @@ -1,6 +1,16 @@ importHigher/root.ts(6,1): error TS2322: Type '"10"' is not assignable to type 'number'. +==== importHigher/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "declaration": false, + "moduleResolution": "node", + "maxNodeModuleJsDepth": 2 + } + } + ==== entry.js (0 errors) ==== var m3 = require("m3"); diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt index 5b5199ad7ae8b..a1b170ce6656b 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/amd/nodeModulesMaxDepthExceeded.errors.txt @@ -2,6 +2,17 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property. +==== maxDepthExceeded/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "maxNodeModuleJsDepth": 1, // Note: Module m1 is already included as a root file + "outDir": "built" + }, + "include": ["**/*"], + "exclude": ["node_modules/m2/**/*"] + } + ==== relative.js (0 errors) ==== exports.relativeProp = true; diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt index 5b5199ad7ae8b..a1b170ce6656b 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt @@ -2,6 +2,17 @@ maxDepthExceeded/root.ts(3,1): error TS2322: Type '"10"' is not assignable to ty maxDepthExceeded/root.ts(4,4): error TS2540: Cannot assign to 'rel' because it is a constant or a read-only property. +==== maxDepthExceeded/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "maxNodeModuleJsDepth": 1, // Note: Module m1 is already included as a root file + "outDir": "built" + }, + "include": ["**/*"], + "exclude": ["node_modules/m2/**/*"] + } + ==== relative.js (0 errors) ==== exports.relativeProp = true; diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt index 684821d60a2fd..a0edfa3a5103d 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/amd/nodeModulesMaxDepthIncreased.errors.txt @@ -1,6 +1,14 @@ maxDepthIncreased/root.ts(7,1): error TS2322: Type '"10"' is not assignable to type 'number'. +==== maxDepthIncreased/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "maxNodeModuleJsDepth": 3 + } + } + ==== index.js (0 errors) ==== exports.person = { "name": "John Doe", diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt index 684821d60a2fd..a0edfa3a5103d 100644 --- a/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt +++ b/tests/baselines/reference/project/nodeModulesMaxDepthIncreased/node/nodeModulesMaxDepthIncreased.errors.txt @@ -1,6 +1,14 @@ maxDepthIncreased/root.ts(7,1): error TS2322: Type '"10"' is not assignable to type 'number'. +==== maxDepthIncreased/tsconfig.json (0 errors) ==== + { + "compilerOptions": { + "allowJs": true, + "maxNodeModuleJsDepth": 3 + } + } + ==== index.js (0 errors) ==== exports.person = { "name": "John Doe", diff --git a/tests/cases/fourslash/server/projectInfo02.ts b/tests/cases/fourslash/server/projectInfo02.ts index eb86c721ac793..3077deb453c47 100644 --- a/tests/cases/fourslash/server/projectInfo02.ts +++ b/tests/cases/fourslash/server/projectInfo02.ts @@ -10,4 +10,4 @@ ////{ "files": ["a.ts", "b.ts"] } goTo.file("a.ts") -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts"]) +verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts", "tsconfig.json"]) diff --git a/tests/cases/fourslash/server/projectWithNonExistentFiles.ts b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts index ceba136bf964e..0e263d9aca6bc 100644 --- a/tests/cases/fourslash/server/projectWithNonExistentFiles.ts +++ b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts @@ -10,4 +10,4 @@ ////{ "files": ["a.ts", "c.ts", "b.ts"] } goTo.file("a.ts"); -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts"]) +verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts", "tsconfig.json"]) diff --git a/tests/cases/unittests/matchFiles.ts b/tests/cases/unittests/matchFiles.ts index e3448136c082b..5faa9330bcedc 100644 --- a/tests/cases/unittests/matchFiles.ts +++ b/tests/cases/unittests/matchFiles.ts @@ -76,6 +76,40 @@ namespace ts { "c:/dev/jspm_packages/a.ts" ]); + function assertParsed(actual: ts.ParsedCommandLine, expected: ts.ParsedCommandLine): void { + assert.deepEqual(actual.fileNames, expected.fileNames); + assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); + assert.deepEqual(actual.errors, expected.errors); + } + + function validateMatches(expected: ts.ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { + { + const jsonText = JSON.stringify(json); + const result = parseJsonText("c:/dev/tsconfig.json", jsonText); + const actual = ts.parseJsonSourceFileConfigFileContent(result, host, basePath, existingOptions, configFileName, resolutionStack); + for (const error of expected.errors) { + if (error.file) { + error.file = result; + } + } + assertParsed(actual, expected); + } + { + const actual = ts.parseJsonConfigFileContent(json, host, basePath, existingOptions, configFileName, resolutionStack); + expected.errors = map(expected.errors, error => { + return { + category: error.category, + code: error.code, + file: undefined, + length: undefined, + messageText: error.messageText, + start: undefined, + }; + }); + assertParsed(actual, expected); + } + } + describe("matchFiles", () => { describe("with literal file list", () => { it("without exclusions", () => { @@ -94,10 +128,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("missing files are still present", () => { const json = { @@ -115,10 +146,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("are not removed due to excludes", () => { const json = { @@ -139,10 +167,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -163,10 +188,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with non .ts file extensions are excluded", () => { const json = { @@ -181,10 +203,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with missing files are excluded", () => { const json = { @@ -199,10 +218,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with literal excludes", () => { const json = { @@ -222,10 +238,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with wildcard excludes", () => { const json = { @@ -252,10 +265,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with recursive excludes", () => { const json = { @@ -281,10 +291,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with case sensitive exclude", () => { const json = { @@ -303,10 +310,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseSensitiveHost, caseSensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); it("with common package folders and no exclusions", () => { const json = { @@ -327,10 +331,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and exclusions", () => { const json = { @@ -356,10 +357,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and empty exclude", () => { const json = { @@ -384,10 +382,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); }); @@ -410,10 +405,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("`*` matches only ts files", () => { const json = { @@ -433,10 +425,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("`?` matches only a single character", () => { const json = { @@ -455,10 +444,7 @@ namespace ts { "c:/dev/x": ts.WatchDirectoryFlags.None }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with recursive directory", () => { const json = { @@ -479,10 +465,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with multiple recursive directories", () => { const json = { @@ -505,10 +488,7 @@ namespace ts { "c:/dev/z": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("case sensitive", () => { const json = { @@ -526,10 +506,7 @@ namespace ts { "/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseSensitiveHost, caseSensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseSensitiveHost, caseSensitiveBasePath); }); it("with missing files are excluded", () => { const json = { @@ -545,10 +522,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("always include literal files", () => { const json = { @@ -572,10 +546,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude folders", () => { const json = { @@ -599,10 +570,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with common package folders and no exclusions", () => { const json = { @@ -620,10 +588,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and exclusions", () => { const json = { @@ -646,10 +611,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("with common package folders and empty exclude", () => { const json = { @@ -671,10 +633,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); it("exclude .js files when allowJs=false", () => { const json = { @@ -695,10 +654,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include .js files when allowJs=true", () => { const json = { @@ -722,10 +678,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include paths outside of the project", () => { const json = { @@ -748,10 +701,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include paths outside of the project using relative paths", () => { @@ -774,10 +724,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude paths outside of the project using relative paths", () => { const json = { @@ -794,10 +741,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("include files with .. in their name", () => { const json = { @@ -816,10 +760,7 @@ namespace ts { ], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("exclude files with .. in their name", () => { const json = { @@ -840,10 +781,7 @@ namespace ts { "c:/ext": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("with jsx=none, allowJs=false", () => { @@ -866,10 +804,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=preserve, allowJs=false", () => { const json = { @@ -893,10 +828,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=react-native, allowJs=false", () => { const json = { @@ -947,10 +879,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=preserve, allowJs=true", () => { const json = { @@ -976,10 +905,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveMixedExtensionHost, caseInsensitiveBasePath); }); it("with jsx=react-native, allowJs=true", () => { const json = { @@ -1025,10 +951,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in excludes", () => { const json = { @@ -1045,10 +968,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); describe("with multiple recursive directory patterns", () => { @@ -1066,10 +986,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in excludes", () => { const json = { @@ -1095,10 +1012,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -1117,10 +1031,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in includes after a subdirectory", () => { @@ -1137,10 +1048,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in excludes immediately after", () => { @@ -1167,10 +1075,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); it("in excludes after a subdirectory", () => { @@ -1197,10 +1102,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assert.deepEqual(actual.fileNames, expected.fileNames); - assert.deepEqual(actual.wildcardDirectories, expected.wildcardDirectories); - assert.deepEqual(actual.errors, expected.errors); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); });