From cca98c308ff382e4826566c58a1c88daec7bd435 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 11 Nov 2016 13:41:43 -0800 Subject: [PATCH 01/15] Parse json using our own parser --- src/compiler/commandLineParser.ts | 134 +++++++++++++----- src/compiler/diagnosticMessages.json | 8 ++ src/compiler/parser.ts | 38 +++++ src/compiler/tsc.ts | 2 +- src/compiler/types.ts | 2 + src/harness/projectsRunner.ts | 10 +- .../unittests/configurationExtension.ts | 8 +- src/harness/unittests/projectErrors.ts | 10 +- src/harness/unittests/tsconfigParsing.ts | 9 +- src/server/editorServices.ts | 16 +-- src/services/shims.ts | 6 +- src/services/utilities.ts | 24 ---- 12 files changed, 172 insertions(+), 95 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 26877de43c4da..d273f59411fb9 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -681,13 +681,13 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readConfigFile(fileName: string, readFile: (path: string) => string): { config?: any; error?: Diagnostic } { + export function readConfigFile(fileName: string, readFile: (path: string) => string): { config?: any; errors: Diagnostic[] } { let text = ""; try { text = readFile(fileName); } catch (e) { - return { error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) }; + return { errors: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; } return parseConfigFileTextToJson(fileName, text); } @@ -697,13 +697,102 @@ 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 } { - try { - const jsonTextToParse = stripComments ? removeComments(jsonText) : jsonText; - return { config: /\S/.test(jsonTextToParse) ? JSON.parse(jsonTextToParse) : {} }; + export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config: any; errors: Diagnostic[] } { + const { node, errors } = parseJsonText(fileName, jsonText); + return { + config: convertToJson(node, errors), + errors + }; + } + + /** + * Convert the json syntax tree into the json value + * @param jsonNode + * @param errors + */ + function convertToJson(jsonNode: JsonNode, errors: Diagnostic[]): any { + if (!jsonNode) { + return undefined; } - catch (e) { - return { error: createCompilerDiagnostic(Diagnostics.Failed_to_parse_file_0_Colon_1, fileName, e.message) }; + + if (jsonNode.kind === SyntaxKind.EndOfFileToken) { + return {}; + } + + const sourceFile = jsonNode.parent; + return convertObjectLiteralExpressionToJson(jsonNode); + + function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression): any { + const result: any = {}; + for (const element of node.properties) { + switch (element.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); + break; + + case SyntaxKind.PropertyAssignment: + 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 value = parseValue(element.initializer); + if (typeof keyText !== undefined && typeof value !== undefined) { + result[keyText] = value; + } + } + } + return result; + } + + function convertArrayLiteralExpressionToJson(node: ArrayLiteralExpression): any[] { + const result: any[] = []; + for (const element of node.elements) { + result.push(parseValue(element)); + } + return result; + } + + function parseValue(node: Expression): any { + switch (node.kind) { + case SyntaxKind.TrueKeyword: + return true; + + case SyntaxKind.FalseKeyword: + return false; + + case SyntaxKind.NullKeyword: + return null; // tslint:disable-line:no-null-keyword + + case SyntaxKind.StringLiteral: + if (!isDoubleQuotedString(node)) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_literal_with_double_quotes_expected)); + } + return (node).text; + + case SyntaxKind.NumericLiteral: + return Number((node).text); + + case SyntaxKind.ObjectLiteralExpression: + return convertObjectLiteralExpressionToJson(node); + + case SyntaxKind.ArrayLiteralExpression: + return convertArrayLiteralExpressionToJson(node); + } + + // Not in expected format + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_number_object_array_true_false_or_null_expected)); + return undefined; + } + + function isDoubleQuotedString(node: Node) { + return node.kind === SyntaxKind.StringLiteral && getSourceTextOfNodeFromSourceFile(sourceFile, node).charCodeAt(0) === CharacterCodes.doubleQuote; } } @@ -795,31 +884,6 @@ 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. - */ - 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; - } - /** * Parse the contents of a config file (tsconfig.json). * @param json The contents of the config file to parse @@ -896,8 +960,8 @@ namespace ts { } } const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path)); - if (extendedResult.error) { - errors.push(extendedResult.error); + if (extendedResult.errors.length) { + errors.push(...extendedResult.errors); return; } const extendedDirname = getDirectoryPath(extendedConfigPath); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2dd4e76f8f44b..19232130ad891 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -851,6 +851,14 @@ "category": "Error", "code": 1317 }, + "String literal with double quotes expected.": { + "category": "Error", + "code": 1318 + }, + "String, number, object, array, true, false or null expected.": { + "category": "Error", + "code": 1319 + }, "Duplicate identifier '{0}'.": { "category": "Error", "code": 2300 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b08f75b0f5bbf..c2cf2d5bfbb6c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -449,6 +449,17 @@ namespace ts { return Parser.parseIsolatedEntityName(text, languageVersion); } + export type ParsedNodeResults = { node: T; errors: Diagnostic[] }; + + /** + * Parse json text into SyntaxTree and return node and parse errors if any + * @param fileName + * @param sourceText + */ + export function parseJsonText(fileName: string, sourceText: string): ParsedNodeResults { + return Parser.parseJsonText(fileName, sourceText); + } + export function isExternalModule(file: SourceFile): boolean { return file.externalModuleIndicator !== undefined; } @@ -610,6 +621,33 @@ namespace ts { return isInvalid ? entityName : undefined; } + export function parseJsonText(fileName: string, sourceText: string): ParsedNodeResults { + initializeState(sourceText, ScriptTarget.ES2015, /*syntaxCursor*/ undefined, ScriptKind.JS); + // Set source file so that errors will be reported with this file name + sourceFile = { kind: SyntaxKind.SourceFile, text: sourceText, fileName }; + let node: JsonNode; + // Prime the scanner. + nextToken(); + if (token() === SyntaxKind.EndOfFileToken) { + node = parseTokenNode(); + } + else if (token() === SyntaxKind.OpenBraceToken || + lookAhead(() => token() === SyntaxKind.StringLiteral)) { + node = parseObjectLiteralExpression(); + parseExpected(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); + } + else { + parseExpected(SyntaxKind.OpenBraceToken); + } + + if (node) { + node.parent = sourceFile; + } + const errors = parseDiagnostics; + clearState(); + return { node, errors }; + } + 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; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 10d4a0c1d3679..d435b5969537a 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -367,9 +367,9 @@ namespace ts { } const result = parseConfigFileTextToJson(configFileName, cachedConfigFileText); + reportDiagnostics(result.errors, /* compilerHost */ undefined); const configObject = result.config; if (!configObject) { - reportDiagnostics([result.error], /* compilerHost */ undefined); sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b5e3eefbb2d6a..04377709f3bb9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -525,6 +525,8 @@ namespace ts { export type AtToken = Token; export type ReadonlyToken = Token; + export type JsonNode = ObjectLiteralExpression | EndOfFileToken; + export type Modifier = Token | Token diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index f7541dc9bfe9e..973a7b5dac16d 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -208,12 +208,13 @@ class ProjectRunner extends RunnerBase { configFileName = ts.findConfigFile("", fileExists); } + let errors: ts.Diagnostic[]; if (configFileName) { const result = ts.readConfigFile(configFileName, getSourceFileText); - if (result.error) { + if (!result.config) { return { moduleKind, - errors: [result.error] + errors: result.errors }; } @@ -228,11 +229,12 @@ class ProjectRunner extends RunnerBase { if (configParseResult.errors.length > 0) { return { moduleKind, - errors: configParseResult.errors + errors: result.errors.concat(configParseResult.errors) }; } inputFiles = configParseResult.fileNames; compilerOptions = configParseResult.options; + errors = result.errors; } const projectCompilerResult = compileProjectFiles(moduleKind, () => inputFiles, getSourceFileText, writeFile, compilerOptions); @@ -242,7 +244,7 @@ class ProjectRunner extends RunnerBase { compilerOptions, sourceMapData: projectCompilerResult.sourceMapData, outputFiles, - errors: projectCompilerResult.errors, + errors: errors ? errors.concat(projectCompilerResult.errors) : projectCompilerResult.errors, }; function createCompilerOptions() { diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 8e845925eb2a9..010d03d681174 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -112,8 +112,8 @@ namespace ts { ], ([testName, basePath, host]) => { function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { it(name, () => { - const {config, error} = ts.readConfigFile(entry, name => host.readFile(name)); - assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n")); + const {config, errors} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); expected.configFilePath = entry; @@ -124,8 +124,8 @@ namespace ts { 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 {config, errors} = ts.readConfigFile(entry, name => host.readFile(name)); + assert(config && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry); verifyDiagnostics(parsed.errors, expectedDiagnostics); }); diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 3db3675dbd6b0..4548e063601bc 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -123,10 +123,7 @@ 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, [ - "')' expected.", - "Declaration or statement expected.", - "Declaration or statement expected.", - "Failed to parse file '/a/b/tsconfig.json'" + "'{' expected." ]); } // fix config and trigger watcher @@ -175,10 +172,7 @@ 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, [ - "')' expected.", - "Declaration or statement expected.", - "Declaration or statement expected.", - "Failed to parse file '/a/b/tsconfig.json'" + "'{' expected." ]); } }); diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 7b980cdd2d8ee..9a7ed315d7f17 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -3,15 +3,18 @@ namespace ts { describe("parseConfigFileTextToJson", () => { - function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic }) { + function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; errors?: Diagnostic[] }) { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); + if (!expectedConfigObject.errors) { + expectedConfigObject.errors = []; + } 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.isTrue(undefined !== parsed.error); + assert.isTrue(!!parsed.errors.length); } function assertParseErrorWithExcludesKeyword(jsonText: string) { @@ -199,7 +202,7 @@ namespace ts { } "files": ["file1.ts"] }`; - const { configJsonObject, diagnostics } = sanitizeConfigFile("config.json", content); + const { config: configJsonObject, errors: diagnostics } = parseConfigFileTextToJson("config.json", content); const expectedResult = { compilerOptions: { allowJs: true, diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f69b5c1154795..f6e05ac6788ca 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -786,18 +786,8 @@ 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 { config = {}, errors } = parseConfigFileTextToJson(configFilename, configFileContent); const parsedCommandLine = parseJsonConfigFileContent( config, this.host, @@ -806,13 +796,13 @@ namespace ts.server { configFilename); 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 }; } diff --git a/src/services/shims.ts b/src/services/shims.ts index 1c8132793a349..c210aa2925765 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1135,13 +1135,13 @@ namespace ts { const result = parseConfigFileTextToJson(fileName, text); - if (result.error) { + if (!result.config) { return { options: {}, typingOptions: {}, files: [], raw: {}, - errors: [realizeDiagnostic(result.error, "\r\n")] + errors: realizeDiagnostics(result.errors, "\r\n") }; } @@ -1153,7 +1153,7 @@ namespace ts { typingOptions: configFile.typingOptions, files: configFile.fileNames, raw: configFile.raw, - errors: realizeDiagnostics(configFile.errors, "\r\n") + errors: realizeDiagnostics(result.errors.concat(configFile.errors), "\r\n") }; }); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 82b13f8232769..5cd9853c4be8d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1334,28 +1334,4 @@ 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 - }; - } } From b3f816ba249f86fc5b7af98ee4aa2274589d1117 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 14 Nov 2016 11:13:06 -0800 Subject: [PATCH 02/15] Reorganize functions so commandline parser can include parser. This fixes build of typings installer --- Jakefile.js | 2 + src/compiler/checker.ts | 6 + src/compiler/commandLineParser.ts | 2 +- src/compiler/factory.ts | 171 ------------------------- src/compiler/parser.ts | 1 - src/compiler/transformer.ts | 1 + src/compiler/transformers/utilities.ts | 168 ++++++++++++++++++++++++ src/compiler/tsconfig.json | 1 + src/compiler/utilities.ts | 21 ++- src/harness/tsconfig.json | 1 + src/services/tsconfig.json | 1 + 11 files changed, 191 insertions(+), 184 deletions(-) create mode 100644 src/compiler/transformers/utilities.ts diff --git a/Jakefile.js b/Jakefile.js index 087bbb675117c..ff4b90f4a40d5 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -69,6 +69,7 @@ var compilerSources = [ "factory.ts", "visitor.ts", "transformers/destructuring.ts", + "transformers/utilities.ts", "transformers/ts.ts", "transformers/jsx.ts", "transformers/esnext.ts", @@ -106,6 +107,7 @@ var servicesSources = [ "factory.ts", "visitor.ts", "transformers/destructuring.ts", + "transformers/utilities.ts", "transformers/ts.ts", "transformers/jsx.ts", "transformers/esnext.ts", diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3d25fc8bbbb0d..3a53f61e0e494 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 d273f59411fb9..43a1e804e1dee 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -2,7 +2,7 @@ /// /// /// -/// +/// namespace ts { /* @internal */ diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d797fff92f4d5..09a048811a68c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2595,16 +2595,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; - } - export function startOnNewLine(node: T): T { node.startsOnNewLine = true; return node; @@ -3264,165 +3254,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: Map; // 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 = createMap(); - const exportedBindings = createMap(); - const uniqueExports = createMap(); - let exportedNames: Identifier[]; - let hasExportDefault = false; - let exportEquals: ExportAssignment = undefined; - let hasExportStarsToExportValues = false; - - const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions); - const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( - /*decorators*/ undefined, - /*modifiers*/ undefined, - createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), - createLiteral(externalHelpersModuleNameText)); - - if (externalHelpersImportDeclaration) { - externalImports.push(externalHelpersImportDeclaration); - } - - 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[specifier.name.text]) { - const name = specifier.propertyName || specifier.name; - multiMapAdd(exportSpecifiers, name.text, specifier); - - const decl = resolver.getReferencedImportDeclaration(name) - || resolver.getReferencedValueDeclaration(name); - - if (decl) { - multiMapAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); - } - - uniqueExports[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) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export function x() { } - const name = (node).name; - if (!uniqueExports[name.text]) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports[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) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); - hasExportDefault = true; - } - } - else { - // export class x { } - const name = (node).name; - if (!uniqueExports[name.text]) { - multiMapAdd(exportedBindings, getOriginalNodeId(node), name); - uniqueExports[name.text] = true; - exportedNames = append(exportedNames, name); - } - } - } - break; - } - } - - 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[decl.name.text]) { - uniqueExports[decl.name.text] = true; - exportedNames = append(exportedNames, decl.name); - } - } - return exportedNames; - } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c2cf2d5bfbb6c..11b42097926fe 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; diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 10a718448e9e7..0f241f3ef75ad 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..1338a30e36891 --- /dev/null +++ b/src/compiler/transformers/utilities.ts @@ -0,0 +1,168 @@ +/* @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: Map; // 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 = createMap(); + const exportedBindings = createMap(); + const uniqueExports = createMap(); + let exportedNames: Identifier[]; + let hasExportDefault = false; + let exportEquals: ExportAssignment = undefined; + let hasExportStarsToExportValues = false; + + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions); + const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)), + createLiteral(externalHelpersModuleNameText)); + + if (externalHelpersImportDeclaration) { + externalImports.push(externalHelpersImportDeclaration); + } + + 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[specifier.name.text]) { + const name = specifier.propertyName || specifier.name; + multiMapAdd(exportSpecifiers, name.text, specifier); + + const decl = resolver.getReferencedImportDeclaration(name) + || resolver.getReferencedValueDeclaration(name); + + if (decl) { + multiMapAdd(exportedBindings, getOriginalNodeId(decl), specifier.name); + } + + uniqueExports[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) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export function x() { } + const name = (node).name; + if (!uniqueExports[name.text]) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports[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) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), getDeclarationName(node)); + hasExportDefault = true; + } + } + else { + // export class x { } + const name = (node).name; + if (!uniqueExports[name.text]) { + multiMapAdd(exportedBindings, getOriginalNodeId(node), name); + uniqueExports[name.text] = true; + exportedNames = append(exportedNames, name); + } + } + } + break; + } + } + + 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[decl.name.text]) { + uniqueExports[decl.name.text] = true; + exportedNames = append(exportedNames, decl.name); + } + } + return exportedNames; + } +} diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index bd70a0afb10ed..002cd167f3831 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -25,6 +25,7 @@ "checker.ts", "factory.ts", "visitor.ts", + "transformers/utilities.ts", "transformers/ts.ts", "transformers/jsx.ts", "transformers/esnext.ts", diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 546416884f1dc..9af6c9b921ccf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1261,12 +1261,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; } @@ -2042,11 +2036,6 @@ namespace ts { return originalSourceFiles; } - export function getOriginalNodeId(node: Node) { - node = getOriginalNode(node); - return node ? getNodeId(node) : 0; - } - export const enum Associativity { Left, Right @@ -3930,6 +3919,16 @@ namespace ts { || kind === SyntaxKind.RawExpression; } + 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 isLeftHandSideExpression(node: Node): node is LeftHandSideExpression { return isLeftHandSideExpressionKind(skipPartiallyEmittedExpressions(node).kind); } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index d3a814f26caf0..2211dbee7e057 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -27,6 +27,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/tsconfig.json b/src/services/tsconfig.json index 502381b8be807..fdab900604641 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -27,6 +27,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", From 0b3074fbdd66d87b84c0fb476ea736a8d3ae45d4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 16 Nov 2016 12:25:24 -0800 Subject: [PATCH 03/15] Keep the original api and add new api that handles the JsonNode Also handle the JsonNode when converting to parsedCommandLine --- src/compiler/commandLineParser.ts | 462 ++++++++++++++---- src/compiler/parser.ts | 2 +- src/compiler/tsc.ts | 7 +- src/compiler/types.ts | 2 + src/harness/harness.ts | 6 +- src/harness/projectsRunner.ts | 7 +- src/harness/rwcRunner.ts | 4 +- .../unittests/configurationExtension.ts | 27 +- .../convertCompilerOptionsFromJson.ts | 32 ++ .../unittests/convertTypingOptionsFromJson.ts | 32 +- src/harness/unittests/matchFiles.ts | 203 ++++---- src/harness/unittests/tsconfigParsing.ts | 80 ++- src/server/editorServices.ts | 12 +- src/services/shims.ts | 6 +- 14 files changed, 633 insertions(+), 249 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 43a1e804e1dee..2cc2545b3c4bb 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -462,7 +462,7 @@ namespace ts { ]; /* @internal */ - export let typingOptionDeclarations: CommandLineOption[] = [ + export const typingOptionDeclarations: CommandLineOption[] = [ { name: "enableAutoDiscovery", type: "boolean", @@ -522,8 +522,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 = Object.keys(opt.type).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 */ @@ -681,13 +685,13 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readConfigFile(fileName: string, readFile: (path: string) => string): { config?: any; errors: Diagnostic[] } { + export function readConfigFile(fileName: string, readFile: (path: string) => string): { config?: any; error?: Diagnostic } { let text = ""; try { text = readFile(fileName); } catch (e) { - return { errors: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; + return { error: createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message) }; } return parseConfigFileTextToJson(fileName, text); } @@ -697,20 +701,107 @@ 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): { config: any; errors: Diagnostic[] } { + export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { const { node, errors } = parseJsonText(fileName, jsonText); return { config: convertToJson(node, errors), - errors + error: errors.length ? errors[0] : undefined }; } + /** + * Read tsconfig.json file + * @param fileName The path to the config file + */ + export function readConfigFileToJsonNode(fileName: string, readFile: (path: string) => string): ParsedNodeResults { + let text = ""; + try { + text = readFile(fileName); + } + catch (e) { + return { errors: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; + } + return parseJsonText(fileName, text); + } + + const tsconfigRootOptions: CommandLineOption[] = [ + { + name: "compilerOptions", + type: "object", + optionDeclarations: optionDeclarations, + extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0 + }, + { + name: "typingOptions", + type: "object", + optionDeclarations: typingOptionDeclarations, + extraKeyDiagnosticMessage: Diagnostics.Unknown_typing_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 + ]; + + function commandLineOptionsToMap(options: CommandLineOption[]) { + return arrayToMap(options, option => option.name); + } + + let _tsconfigRootOptionsMap: Map; + function getTsconfigRootOptionsMap() { + if (_tsconfigRootOptionsMap === undefined) { + _tsconfigRootOptionsMap = commandLineOptionsToMap(tsconfigRootOptions); + } + return _tsconfigRootOptionsMap; + } + + interface JsonConversionNotifier { + /** Notifies options object is being set with the optionKey and optionValue is being set */ + onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue): void; + /** Notify when root key value is being set */ + onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression): void; + } + /** * Convert the json syntax tree into the json value * @param jsonNode * @param errors */ - function convertToJson(jsonNode: JsonNode, errors: Diagnostic[]): any { + export function convertToJson(jsonNode: JsonNode, errors: Diagnostic[]): any { + return convertToJsonWorker(jsonNode, errors); + } + + /** + * Convert the json syntax tree into the json value + * @param jsonNode + * @param errors + */ + function convertToJsonWorker(jsonNode: JsonNode, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { if (!jsonNode) { return undefined; } @@ -720,75 +811,125 @@ namespace ts { } const sourceFile = jsonNode.parent; - return convertObjectLiteralExpressionToJson(jsonNode); + return convertObjectLiteralExpressionToJson(jsonNode, knownRootOptions); - function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression): any { + function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, options?: Map, extraKeyDiagnosticMessage?: DiagnosticMessage, optionsObject?: string): any { const result: any = {}; for (const element of node.properties) { - switch (element.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.SpreadAssignment: - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element, Diagnostics.Property_assignment_expected)); - break; - - case SyntaxKind.PropertyAssignment: - 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)); + 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 = options ? options[keyText] : undefined; + if (extraKeyDiagnosticMessage && !option) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText)); + } + const value = parseValue(element.initializer, option); + if (typeof keyText !== undefined && typeof value !== undefined) { + result[keyText] = value; + // Notify key value set, if user asked for it + if (optionsIterator && + (optionsObject || options === knownRootOptions)) { + const isValidOptionValue = isCompilerOptionsValue(option, value); + if (optionsObject && isValidOptionValue) { + optionsIterator.onSetOptionKeyValue(optionsObject, option, value); } - const keyText = getTextOfPropertyName(element.name); - const value = parseValue(element.initializer); - if (typeof keyText !== undefined && typeof value !== undefined) { - result[keyText] = value; + if (options === knownRootOptions && (isValidOptionValue || !option)) { + optionsIterator.onRootKeyValue(keyText, element.name, value, element.initializer); } + } + } } return result; } - function convertArrayLiteralExpressionToJson(node: ArrayLiteralExpression): any[] { + function convertArrayLiteralExpressionToJson(elements: NodeArray, option?: CommandLineOption): any[] { const result: any[] = []; - for (const element of node.elements) { - result.push(parseValue(element)); + for (const element of elements) { + result.push(parseValue(element, option)); } return result; } - function parseValue(node: Expression): any { + function parseValue(node: Expression, option: CommandLineOption): any { switch (node.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(node)) { errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_literal_with_double_quotes_expected)); } - return (node).text; + reportInvalidOptionValue(option && (typeof option.type === "string" && option.type !== "string")); + const text = (node).text; + if (option && typeof option.type !== "string") { + const customOption = option; + // Validate custom option type + if (!(text in customOption.type)) { + errors.push( + createDiagnosticForInvalidCustomType( + customOption, + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1) + ) + ); + } + } + return text; case SyntaxKind.NumericLiteral: + reportInvalidOptionValue(option && option.type !== "number"); return Number((node).text); case SyntaxKind.ObjectLiteralExpression: - return convertObjectLiteralExpressionToJson(node); + reportInvalidOptionValue(option && option.type !== "object"); + const objectOption = option; + const optionDeclarations = option && objectOption.optionDeclarations ? commandLineOptionsToMap(objectOption.optionDeclarations) : undefined; + return convertObjectLiteralExpressionToJson( + node, + optionDeclarations, + option && objectOption.extraKeyDiagnosticMessage, + optionDeclarations && option.name + ); case SyntaxKind.ArrayLiteralExpression: - return convertArrayLiteralExpressionToJson(node); + reportInvalidOptionValue(option && option.type !== "list"); + return convertArrayLiteralExpressionToJson((node).elements, option && (option).element); } // Not in expected format - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_number_object_array_true_false_or_null_expected)); + if (option) { + reportInvalidOptionValue(!!option); + } + else { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_number_object_array_true_false_or_null_expected)); + } + return undefined; + + function reportInvalidOptionValue(isError: boolean) { + if (isError) { + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option.name, getCompilerOptionValueTypeString(option))); + } + } } function isDoubleQuotedString(node: Node) { @@ -796,6 +937,22 @@ namespace ts { } } + 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; + } + } + /** * Generate tsconfig configuration when running command line "--init" * @param options commandlineOptions to be generated into tsconfig.json @@ -891,7 +1048,30 @@ namespace ts { * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir */ - export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine { + export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]): ParsedCommandLine { + return parseJsonConfigFileContentWorker(json, /*jsonNode*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack); + } + + /** + * 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 parseJsonNodeConfigFileContent(jsonNode: JsonNode, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, jsonNode, host, basePath, existingOptions, configFileName, resolutionStack); + } + + /** + * 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 parseJsonConfigFileContentWorker(json: any, jsonNode: JsonNode, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine { + Debug.assert((json === undefined && jsonNode !== undefined) || (json !== undefined && jsonNode === undefined)); const errors: Diagnostic[] = []; const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); @@ -900,22 +1080,67 @@ namespace ts { options: {}, fileNames: [], typingOptions: {}, - raw: json, - errors: [createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))], + raw: json || convertToJson(jsonNode, errors), + errors: errors.concat(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))), wildcardDirectories: {} }; } - let options: CompilerOptions = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName); - const typingOptions: TypingOptions = convertTypingOptionsFromJsonWorker(json["typingOptions"], basePath, errors, configFileName); + let options: CompilerOptions; + let typingOptions: TypingOptions; + let compileOnSave: boolean; + let hasExtendsError: boolean, extendedConfigPath: Path; + if (json) { + options = convertCompilerOptionsFromJsonWorker(json["compilerOptions"], basePath, errors, configFileName); + typingOptions = convertTypingOptionsFromJsonWorker(json["typingOptions"], basePath, errors, configFileName); + compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); + } + else { + options = getDefaultCompilerOptions(configFileName); + typingOptions = getDefaultTypingOptions(configFileName); + const optionsIterator: JsonConversionNotifier = { + onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue) { + Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typingOptions"); + const currentOption = optionsObject === "compilerOptions" ? options : typingOptions; + currentOption[option.name] = normalizeOptionValue(option, basePath, value); + }, + onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression) { + switch (key) { + case "extends": + const extendsDiagnostic = getExtendsConfigPath(value, (message, arg0) => + createDiagnosticForNodeInSourceFile(jsonNode.parent, node, message, arg0)); + if ((extendsDiagnostic).messageText) { + errors.push(extendsDiagnostic); + hasExtendsError = true; + } + else { + extendedConfigPath = extendsDiagnostic; + } + return; + case "excludes": + errors.push(createDiagnosticForNodeInSourceFile(jsonNode.parent, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + return; + case "files": + if ((value).length === 0) { + errors.push(createDiagnosticForNodeInSourceFile(jsonNode.parent, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + } + return; + case "compileOnSave": + compileOnSave = value; + return; + } + } + }; + json = convertToJsonWorker(jsonNode, errors, getTsconfigRootOptionsMap(), optionsIterator); + } if (json["extends"]) { let [include, exclude, files, baseOptions]: [string[], string[], string[], CompilerOptions] = [undefined, undefined, undefined, {}]; - if (typeof json["extends"] === "string") { - [include, exclude, files, baseOptions] = (tryExtendsName(json["extends"]) || [include, exclude, files, baseOptions]); + if (!hasExtendsError && typeof json["extends"] === "string") { + [include, exclude, files, baseOptions] = (tryExtendsName(json["extends"], extendedConfigPath) || [include, exclude, files, baseOptions]); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string")); + createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "extends", "string"); } if (include && !json["include"]) { json["include"] = include; @@ -933,7 +1158,6 @@ namespace ts { options.configFilePath = configFileName; const { fileNames, wildcardDirectories } = getFileNames(errors); - const compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors); return { options, @@ -945,21 +1169,35 @@ namespace ts { compileOnSave }; - function tryExtendsName(extendedConfig: string): [string[], string[], string[], CompilerOptions] { + function getExtendsConfigPath(extendedConfig: string, createDiagnostic: (message: DiagnosticMessage, arg1?: string) => T): T | Path { // 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(extendedConfig) || startsWith(normalizeSlashes(extendedConfig), "./") || startsWith(normalizeSlashes(extendedConfig), "../"))) { - errors.push(createCompilerDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig)); - return; + return createDiagnostic(Diagnostics.A_path_in_an_extends_option_must_be_relative_or_rooted_but_0_is_not, extendedConfig); } let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName); if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { extendedConfigPath = `${extendedConfigPath}.json` as Path; if (!host.fileExists(extendedConfigPath)) { - errors.push(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); + return createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig); + } + } + return extendedConfigPath; + } + + function tryExtendsName(extendedConfig: string, extendedConfigPath?: Path): [string[], string[], string[], CompilerOptions] { + if (!extendedConfigPath) { + const result = getExtendsConfigPath(extendedConfig, (message, arg1) => (createCompilerDiagnosticForJson(message, arg1), true)); + if (result === true) { return; } + extendedConfigPath = result; + } + + if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) { + extendedConfigPath = `${extendedConfigPath}.json` as Path; } - const extendedResult = readConfigFile(extendedConfigPath, path => host.readFile(path)); + + const extendedResult = readConfigFileToJsonNode(extendedConfigPath, path => host.readFile(path)); if (extendedResult.errors.length) { errors.push(...extendedResult.errors); return; @@ -968,11 +1206,11 @@ namespace ts { const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); // Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios) - const result = parseJsonConfigFileContent(extendedResult.config, host, extendedDirname, /*existingOptions*/undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath])); + const result = parseJsonNodeConfigFileContent(extendedResult.node, host, extendedDirname, /*existingOptions*/undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath])); errors.push(...result.errors); const [include, exclude, files] = map(["include", "exclude", "files"], key => { - if (!json[key] && extendedResult.config[key]) { - return map(extendedResult.config[key], updatePath); + if (!json[key] && result.raw[key]) { + return map(result.raw[key], updatePath); } }); return [include, exclude, files, result.options]; @@ -984,11 +1222,11 @@ namespace ts { if (isArray(json["files"])) { fileNames = json["files"]; if (fileNames.length === 0) { - errors.push(createCompilerDiagnostic(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + createCompilerDiagnosticForJson(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")); + createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "files", "Array"); } } @@ -998,7 +1236,7 @@ namespace ts { includeSpecs = json["include"]; } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array")); + createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "include", "Array"); } } @@ -1008,11 +1246,11 @@ namespace ts { excludeSpecs = json["exclude"]; } else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array")); + createCompilerDiagnosticForJson(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, "exclude", "Array"); } } else if (hasProperty(json, "excludes")) { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + createCompilerDiagnosticForJson(Diagnostics.Unknown_option_excludes_Did_you_mean_exclude); } else { // By default, exclude common package folders and the outDir @@ -1028,7 +1266,7 @@ namespace ts { includeSpecs = ["**/*"]; } - const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors); + const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, jsonNode); if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) { errors.push( @@ -1041,6 +1279,12 @@ namespace ts { return result; } + + function createCompilerDiagnosticForJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { + if (!jsonNode) { + errors.push(createCompilerDiagnostic(message, arg0, arg1)); + } + } } export function convertCompileOnSaveOptionFromJson(jsonOption: any, basePath: string, errors: Diagnostic[]): boolean { @@ -1066,20 +1310,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 getDefaultTypingOptions(configFileName?: string) { + const options: TypingOptions = { enableAutoDiscovery: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; + return options; + } + function convertTypingOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Diagnostic[], configFileName?: string): TypingOptions { - const options: TypingOptions = { enableAutoDiscovery: getBaseFileName(configFileName) === "jsconfig.json", include: [], exclude: [] }; + const options = getDefaultTypingOptions(configFileName); convertOptionsFromJson(typingOptionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_typing_option_0, errors); return options; } @@ -1091,7 +1345,7 @@ namespace ts { return; } - const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); + const optionNameMap = commandLineOptionsToMap(optionDeclarations); for (const id in jsonOptions) { if (id in optionNameMap) { @@ -1105,28 +1359,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[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[]) { @@ -1230,7 +1499,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[]): ExpandResult { + function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], jsonNode: JsonNode): ExpandResult { basePath = normalizePath(basePath); // The exclude spec list is converted into a regular expression, which allows us to quickly @@ -1249,11 +1518,11 @@ namespace ts { const wildcardFileMap = createMap(); if (include) { - include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false); + include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonNode, "include"); } if (exclude) { - exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true); + exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonNode, "exclude"); } // Wildcard directories (provided as part of a wildcard path) are stored in a @@ -1309,17 +1578,17 @@ namespace ts { }; } - function validateSpecs(specs: string[], errors: Diagnostic[], allowTrailingRecursion: boolean) { + function validateSpecs(specs: string[], errors: Diagnostic[], allowTrailingRecursion: boolean, jsonNode: JsonNode, 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); @@ -1327,6 +1596,23 @@ namespace ts { } return validSpecs; + + function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { + if (jsonNode && jsonNode.kind === SyntaxKind.ObjectLiteralExpression) { + for (const property of jsonNode.properties) { + if (property.kind === SyntaxKind.PropertyAssignment && getTextOfPropertyName(property.name) === specKey) { + const specsNode = property.initializer; + for (const element of specsNode.elements) { + if (element.kind === SyntaxKind.StringLiteral && (element).text === spec) { + return createDiagnosticForNodeInSourceFile(jsonNode.parent, element, message, spec); + } + } + } + } + + } + return createCompilerDiagnostic(message, spec); + } } /** diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 11b42097926fe..03a6d0d7cb9ea 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -448,7 +448,7 @@ namespace ts { return Parser.parseIsolatedEntityName(text, languageVersion); } - export type ParsedNodeResults = { node: T; errors: Diagnostic[] }; + export type ParsedNodeResults = { node?: T; errors: Diagnostic[] }; /** * Parse json text into SyntaxTree and return node and parse errors if any diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index d435b5969537a..ee653d688ed00 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -366,15 +366,14 @@ namespace ts { return; } - const result = parseConfigFileTextToJson(configFileName, cachedConfigFileText); + const result = parseJsonText(configFileName, cachedConfigFileText); reportDiagnostics(result.errors, /* compilerHost */ undefined); - const configObject = result.config; - if (!configObject) { + if (!result.node) { sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } const cwd = sys.getCurrentDirectory(); - const configParseResult = parseJsonConfigFileContent(configObject, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); + const configParseResult = parseJsonNodeConfigFileContent(result.node, 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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 04377709f3bb9..db8d31e13ef67 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3322,6 +3322,8 @@ namespace ts { /* @internal */ export interface TsConfigOnlyOption extends CommandLineOptionBase { type: "object"; + optionDeclarations?: CommandLineOption[]; + extraKeyDiagnosticMessage?: DiagnosticMessage; } /* @internal */ diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 88346557ba7c2..6be7adcde5f3e 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1876,13 +1876,13 @@ namespace Harness { 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.node !== 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.parseJsonNodeConfigFileContent(configJson.node, parseConfigHost, baseDir); tsConfig.options.configFilePath = data.name; // delete entry from the list diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 973a7b5dac16d..de22ff314fa38 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -210,22 +210,21 @@ class ProjectRunner extends RunnerBase { let errors: ts.Diagnostic[]; if (configFileName) { - const result = ts.readConfigFile(configFileName, getSourceFileText); - if (!result.config) { + const result = ts.readConfigFileToJsonNode(configFileName, getSourceFileText); + if (!result.node) { return { moduleKind, errors: result.errors }; } - const configObject = result.config; const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists, readDirectory, readFile }; - const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); + const configParseResult = ts.parseJsonNodeConfigFileContent(result.node, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); if (configParseResult.errors.length > 0) { return { moduleKind, diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 9f9bf8589f08a..fe9bed3f5ab3c 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -74,14 +74,14 @@ 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); + 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.parseJsonNodeConfigFileContent(parsedTsconfigFileContents.node, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); fileNames = configParseResult.fileNames; opts.options = ts.extend(opts.options, configParseResult.options); } diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 010d03d681174..4077f54ef3d15 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -112,23 +112,40 @@ namespace ts { ], ([testName, basePath, host]) => { function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) { it(name, () => { - const {config, errors} = ts.readConfigFile(entry, name => host.readFile(name)); - assert(config && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); + 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); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); expected.configFilePath = entry; assert.deepEqual(parsed.options, expected); assert.deepEqual(parsed.fileNames, expectedFiles); }); + + it(name, () => { + const {node, errors} = ts.readConfigFileToJsonNode(entry, name => host.readFile(name)); + assert(node && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); + const parsed = ts.parseJsonNodeConfigFileContent(node, host, basePath, {}, entry); + assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); + expected.configFilePath = entry; + assert.deepEqual(parsed.options, expected); + 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, errors} = ts.readConfigFile(entry, name => host.readFile(name)); - assert(config && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); + 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); verifyDiagnostics(parsed.errors, expectedDiagnostics); }); + + it(name, () => { + const {node, errors} = ts.readConfigFileToJsonNode(entry, name => host.readFile(name)); + assert(node && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); + const parsed = ts.parseJsonNodeConfigFileContent(node, host, basePath, {}, entry); + verifyDiagnostics(parsed.errors, expectedDiagnostics); + }); } describe(testName, () => { diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index f44dc259710e5..d191e2d2f3bc3 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,33 @@ namespace ts { } } + function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { + const fileText = JSON.stringify(json); + const { node, errors } = parseJsonText(configFileName, fileText); + assert(!errors.length); + assert(!!node); + const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); + const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonNodeConfigFileContent(node, host, "/apath/", /*existingOptions*/ undefined, configFileName); + expectedResult.compilerOptions["configFilePath"] = configFileName; + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + + 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/convertTypingOptionsFromJson.ts b/src/harness/unittests/convertTypingOptionsFromJson.ts index 439409b24b707..d2924eedf0379 100644 --- a/src/harness/unittests/convertTypingOptionsFromJson.ts +++ b/src/harness/unittests/convertTypingOptionsFromJson.ts @@ -4,6 +4,11 @@ namespace ts { describe("convertTypingOptionsFromJson", () => { function assertTypingOptions(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { + assertTypingOptionsWithJson(json, configFileName, expectedResult); + assertTypingOptionsWithJsonNode(json, configFileName, expectedResult); + } + + function assertTypingOptionsWithJson(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { const { options: actualTypingOptions, errors: actualErrors } = convertTypingOptionsFromJson(json["typingOptions"], "/apath/", configFileName); const parsedTypingOptions = JSON.stringify(actualTypingOptions); const expectedTypingOptions = JSON.stringify(expectedResult.typingOptions); @@ -19,6 +24,31 @@ namespace ts { } } + function assertTypingOptionsWithJsonNode(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { + const fileText = JSON.stringify(json); + const { node, errors } = parseJsonText(configFileName, fileText); + assert(!errors.length); + assert(!!node); + const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); + const { typingOptions: actualTypingOptions, errors: actualParseErrors } = parseJsonNodeConfigFileContent(node, host, "/apath/", /*existingOptions*/ undefined, configFileName); + const parsedTypingOptions = JSON.stringify(actualTypingOptions); + const expectedTypingOptions = JSON.stringify(expectedResult.typingOptions); + assert.equal(parsedTypingOptions, expectedTypingOptions); + + 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 it("Convert correctly format tsconfig.json to typing-options ", () => { assertTypingOptions( @@ -154,7 +184,7 @@ namespace ts { }, errors: [ { - category: Diagnostics.Unknown_compiler_option_0.category, + category: Diagnostics.Unknown_typing_option_0.category, code: Diagnostics.Unknown_typing_option_0.code, file: undefined, start: 0, diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index b514ac142c50b..6681be4596c99 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -97,6 +97,39 @@ 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 {node} = parseJsonText(caseInsensitiveTsconfigPath, jsonText); + const actual = ts.parseJsonNodeConfigFileContent(node, host, basePath, existingOptions, configFileName, resolutionStack); + 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", () => { describe("with literal file list", () => { it("without exclusions", () => { @@ -115,8 +148,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 = { @@ -134,8 +166,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 = { @@ -156,8 +187,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -178,8 +208,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 = { @@ -197,8 +226,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("with missing files are excluded", () => { const json = { @@ -216,8 +244,7 @@ namespace ts { fileNames: [], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("with literal excludes", () => { const json = { @@ -237,8 +264,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 = { @@ -265,8 +291,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 = { @@ -292,8 +317,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 = { @@ -312,8 +336,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 = { @@ -334,8 +357,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 = { @@ -361,8 +383,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 = { @@ -387,8 +408,7 @@ namespace ts { ], wildcardDirectories: {}, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveCommonFoldersHost, caseInsensitiveBasePath); }); }); @@ -411,8 +431,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 = { @@ -432,8 +451,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 = { @@ -452,8 +470,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 = { @@ -474,8 +491,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 = { @@ -498,8 +514,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 = { @@ -517,8 +532,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 = { @@ -537,8 +551,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive }, }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("always include literal files", () => { const json = { @@ -562,8 +575,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 = { @@ -587,8 +599,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 = { @@ -606,8 +617,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 = { @@ -630,8 +640,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 = { @@ -653,8 +662,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 = { @@ -678,8 +686,7 @@ namespace ts { "c:/dev/js": ts.WatchDirectoryFlags.None } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("include .js files when allowJs=true", () => { const json = { @@ -703,8 +710,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 = { @@ -728,8 +734,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 = { @@ -752,8 +757,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 = { @@ -775,8 +779,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 = { @@ -796,8 +799,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("include files with .. in their name", () => { const json = { @@ -816,8 +818,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 = { @@ -838,8 +839,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 = { @@ -861,8 +861,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 = { @@ -886,8 +885,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 = { @@ -911,8 +909,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 = { @@ -938,8 +935,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 = { @@ -965,8 +961,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", () => { @@ -978,15 +973,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), JSON.stringify(defaultExcludes)) ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("in excludes", () => { const json = { @@ -1006,8 +1000,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); }); describe("with multiple recursive directory patterns", () => { @@ -1020,15 +1013,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), JSON.stringify(defaultExcludes)) ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("in excludes", () => { const json = { @@ -1042,7 +1034,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", @@ -1054,8 +1046,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -1069,15 +1060,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), JSON.stringify(defaultExcludes)) ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("in includes after a subdirectory", () => { @@ -1089,15 +1079,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), JSON.stringify(defaultExcludes)) ], fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); it("in excludes immediately after", () => { @@ -1112,7 +1101,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", @@ -1124,8 +1113,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", () => { @@ -1140,7 +1128,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", @@ -1152,8 +1140,7 @@ namespace ts { "c:/dev": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); @@ -1170,8 +1157,7 @@ namespace ts { "c:/dev/z": ts.WatchDirectoryFlags.Recursive } }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveHost, caseInsensitiveBasePath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveHost, caseInsensitiveBasePath); }); }); }); @@ -1195,8 +1181,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", () => { @@ -1215,8 +1200,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 = { @@ -1237,8 +1221,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 = { @@ -1259,8 +1242,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 = { @@ -1280,8 +1262,7 @@ namespace ts { fileNames: [], wildcardDirectories: {} }; - const actual = ts.parseJsonConfigFileContent(json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); - assertParsed(actual, expected); + validateMatches(expected, json, caseInsensitiveDottedFoldersHost, caseInsensitiveBasePath, undefined, caseInsensitiveTsconfigPath); }); }); }); diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 9a7ed315d7f17..c31608ee2fe43 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -3,40 +3,69 @@ namespace ts { describe("parseConfigFileTextToJson", () => { - function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; errors?: Diagnostic[] }) { + function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic[] }) { const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText); - if (!expectedConfigObject.errors) { - expectedConfigObject.errors = []; - } 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.isTrue(!!parsed.errors.length); + 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.parseJsonNodeConfigFileContent(parsed.node, 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.parseJsonNodeConfigFileContent(parsed.node, 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", () => { @@ -202,7 +231,8 @@ namespace ts { } "files": ["file1.ts"] }`; - const { config: configJsonObject, errors: diagnostics } = parseConfigFileTextToJson("config.json", content); + const { node, errors: diagnostics } = parseJsonText("config.json", content); + const configJsonObject = convertToJson(node, diagnostics); const expectedResult = { compilerOptions: { allowJs: true, @@ -232,7 +262,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", () => { @@ -245,7 +276,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", () => { @@ -256,7 +288,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", () => { @@ -270,7 +303,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/server/editorServices.ts b/src/server/editorServices.ts index f6e05ac6788ca..19b5c178a7291 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -782,14 +782,18 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } + private getDefaultParsedJsonNode(): EndOfFileToken { + return { kind: SyntaxKind.EndOfFileToken }; + } + private convertConfigFileContentToProjectOptions(configFilename: string): ConfigFileConversionResult { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); - const { config = {}, errors } = parseConfigFileTextToJson(configFilename, configFileContent); - const parsedCommandLine = parseJsonConfigFileContent( - config, + const { node = this.getDefaultParsedJsonNode(), errors } = parseJsonText(configFilename, configFileContent); + const parsedCommandLine = parseJsonNodeConfigFileContent( + node, this.host, getDirectoryPath(configFilename), /*existingOptions*/ {}, @@ -809,7 +813,7 @@ namespace ts.server { const projectOptions: ProjectOptions = { files: parsedCommandLine.fileNames, compilerOptions: parsedCommandLine.options, - configHasFilesProperty: config["files"] !== undefined, + configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined, wildcardDirectories: createMap(parsedCommandLine.wildcardDirectories), typingOptions: parsedCommandLine.typingOptions, compileOnSave: parsedCommandLine.compileOnSave diff --git a/src/services/shims.ts b/src/services/shims.ts index c210aa2925765..1ded848f700fa 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1133,9 +1133,9 @@ namespace ts { () => { const text = sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()); - const result = parseConfigFileTextToJson(fileName, text); + const result = parseJsonText(fileName, text); - if (!result.config) { + if (!result.node) { return { options: {}, typingOptions: {}, @@ -1146,7 +1146,7 @@ namespace ts { } const normalizedFileName = normalizeSlashes(fileName); - const configFile = parseJsonConfigFileContent(result.config, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); + const configFile = parseJsonNodeConfigFileContent(result.node, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); return { options: configFile.options, From 005123f099ab59ee92bbf80d25896911deac032e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 23 Nov 2016 12:20:55 -0800 Subject: [PATCH 04/15] Handle typingOptions to typeAcquisition rename when converting parsed json node --- src/compiler/commandLineParser.ts | 29 ++++++++++++-- .../convertTypeAcquisitionFromJson.ts | 39 ++++++++++--------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 01e9ce86a3340..57a3931dda40f 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -758,6 +758,12 @@ namespace ts { optionDeclarations: typeAcquisitionDeclarations, extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 }, + { + name: "typeAcquisition", + type: "object", + optionDeclarations: typeAcquisitionDeclarations, + extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 + }, { name: "extends", type: "string" @@ -1121,11 +1127,14 @@ namespace ts { } else { options = getDefaultCompilerOptions(configFileName); - typeAcquisition = getDefaultTypeAcquisition(configFileName); + let typingOptionstypeAcquisition: TypeAcquisition; const optionsIterator: JsonConversionNotifier = { onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue) { - Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typingOptions"); - const currentOption = optionsObject === "compilerOptions" ? options : typingOptions; + Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typeAcquisition" || optionsObject === "typingOptions"); + const currentOption = optionsObject === "compilerOptions" ? options : + optionsObject === "typeAcquisition" ? (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName)) ) : + (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName))); + currentOption[option.name] = normalizeOptionValue(option, basePath, value); }, onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression) { @@ -1156,6 +1165,20 @@ namespace ts { } }; json = convertToJsonWorker(jsonNode, 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); + } + } } if (json["extends"]) { diff --git a/src/harness/unittests/convertTypeAcquisitionFromJson.ts b/src/harness/unittests/convertTypeAcquisitionFromJson.ts index d8f8d040f53b5..535e7555f4f26 100644 --- a/src/harness/unittests/convertTypeAcquisitionFromJson.ts +++ b/src/harness/unittests/convertTypeAcquisitionFromJson.ts @@ -2,19 +2,20 @@ /// namespace ts { + type ExpectedResult = { typeAcquisition: TypeAcquisition, errors: Diagnostic[] }; describe("convertTypeAcquisitionFromJson", () => { - function assertTypeAcquisition(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { + function assertTypeAcquisition(json: any, configFileName: string, expectedResult: ExpectedResult) { assertTypeAcquisitionWithJson(json, configFileName, expectedResult); assertTypeAcquisitionWithJsonNode(json, configFileName, expectedResult); } - function assertTypeAcquisitionWithJson(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { - const jsonOptions = json["typeAcquisition"] || json["typingOptions"]; - const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName); + 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++) { @@ -22,32 +23,32 @@ 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 assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { + 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 { node, errors } = parseJsonText(configFileName, fileText); assert(!errors.length); assert(!!node); const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); const { typeAcquisition: actualTypeAcquisition, errors: actualParseErrors } = parseJsonNodeConfigFileContent(node, host, "/apath/", /*existingOptions*/ undefined, configFileName); - const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition); - const expectedTypeAcquisition = JSON.stringify(expectedResult.actualTypeAcquisition); - assert.equal(parsedTypeAcquisition, expectedTypeAcquisition); + 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); - 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); - } + verifyErrors(actualErrors, expectedResult, /*hasLocation*/ true); } // tsconfig.json From 0dd944aa14b2325a283044749a9fbbb4fe173b33 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 23 Nov 2016 16:09:34 -0800 Subject: [PATCH 05/15] Clean up api so that parsing returns JsonSourceFile --- src/compiler/commandLineParser.ts | 76 +++++++++---------- src/compiler/core.ts | 2 + src/compiler/parser.ts | 30 ++++---- src/compiler/tsc.ts | 6 +- src/compiler/types.ts | 9 ++- src/harness/harness.ts | 4 +- src/harness/projectsRunner.ts | 15 +--- src/harness/rwcRunner.ts | 2 +- .../unittests/configurationExtension.ts | 32 ++++---- .../convertCompilerOptionsFromJson.ts | 8 +- .../convertTypeAcquisitionFromJson.ts | 8 +- src/harness/unittests/matchFiles.ts | 9 ++- src/harness/unittests/tsconfigParsing.ts | 9 ++- src/server/editorServices.ts | 14 ++-- src/services/shims.ts | 8 +- 15 files changed, 117 insertions(+), 115 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 57a3931dda40f..2ce46232a2554 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -462,7 +462,7 @@ namespace ts { ]; /* @internal */ - export let typeAcquisitionDeclarations: CommandLineOption[] = [ + export const typeAcquisitionDeclarations: CommandLineOption[] = [ { /* @deprecated typingOptions.enableAutoDiscovery * Use typeAcquisition.enable instead. @@ -723,10 +723,10 @@ namespace ts { * @param jsonText The text of the config file */ export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { - const { node, errors } = parseJsonText(fileName, jsonText); + const jsonSourceFile = parseJsonText(fileName, jsonText); return { - config: convertToJson(node, errors), - error: errors.length ? errors[0] : undefined + config: convertToJson(jsonSourceFile, jsonSourceFile.parseDiagnostics), + error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined }; } @@ -734,13 +734,13 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readConfigFileToJsonNode(fileName: string, readFile: (path: string) => string): ParsedNodeResults { + export function readConfigFileToJsonSourceFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { let text = ""; try { text = readFile(fileName); } catch (e) { - return { errors: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; + return { parseDiagnostics: [createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message)] }; } return parseJsonText(fileName, text); } @@ -819,8 +819,8 @@ namespace ts { * @param jsonNode * @param errors */ - export function convertToJson(jsonNode: JsonNode, errors: Diagnostic[]): any { - return convertToJsonWorker(jsonNode, errors); + export function convertToJson(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { + return convertToJsonWorker(sourceFile, errors); } /** @@ -828,17 +828,15 @@ namespace ts { * @param jsonNode * @param errors */ - function convertToJsonWorker(jsonNode: JsonNode, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { - if (!jsonNode) { + function convertToJsonWorker(sourceFile: JsonSourceFile, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { + if (!sourceFile.jsonObject) { + if (sourceFile.endOfFileToken) { + return {}; + } return undefined; } - if (jsonNode.kind === SyntaxKind.EndOfFileToken) { - return {}; - } - - const sourceFile = jsonNode.parent; - return convertObjectLiteralExpressionToJson(jsonNode, knownRootOptions); + return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions); function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, options?: Map, extraKeyDiagnosticMessage?: DiagnosticMessage, optionsObject?: string): any { const result: any = {}; @@ -1076,7 +1074,7 @@ namespace ts { * file to. e.g. outDir */ export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]): ParsedCommandLine { - return parseJsonConfigFileContentWorker(json, /*jsonNode*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack); + return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack); } /** @@ -1086,8 +1084,8 @@ namespace ts { * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir */ - export function parseJsonNodeConfigFileContent(jsonNode: JsonNode, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]): ParsedCommandLine { - return parseJsonConfigFileContentWorker(/*json*/ undefined, jsonNode, host, basePath, existingOptions, configFileName, resolutionStack); + export function parseJsonSourceFileConfigFileContent(sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]): ParsedCommandLine { + return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack); } /** @@ -1097,8 +1095,8 @@ namespace ts { * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir */ - function parseJsonConfigFileContentWorker(json: any, jsonNode: JsonNode, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine { - Debug.assert((json === undefined && jsonNode !== undefined) || (json !== undefined && jsonNode === undefined)); + function parseJsonConfigFileContentWorker(json: any, sourceFile: JsonSourceFile, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string, resolutionStack: Path[] = []): ParsedCommandLine { + Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined)); const errors: Diagnostic[] = []; const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); const resolvedPath = toPath(configFileName || "", basePath, getCanonicalFileName); @@ -1107,7 +1105,7 @@ namespace ts { options: {}, fileNames: [], typeAcquisition: {}, - raw: json || convertToJson(jsonNode, errors), + raw: json || convertToJson(sourceFile, errors), errors: errors.concat(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))), wildcardDirectories: {} }; @@ -1141,7 +1139,7 @@ namespace ts { switch (key) { case "extends": const extendsDiagnostic = getExtendsConfigPath(value, (message, arg0) => - createDiagnosticForNodeInSourceFile(jsonNode.parent, node, message, arg0)); + createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0)); if ((extendsDiagnostic).messageText) { errors.push(extendsDiagnostic); hasExtendsError = true; @@ -1151,11 +1149,11 @@ namespace ts { } return; case "excludes": - errors.push(createDiagnosticForNodeInSourceFile(jsonNode.parent, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); return; case "files": if ((value).length === 0) { - errors.push(createDiagnosticForNodeInSourceFile(jsonNode.parent, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); } return; case "compileOnSave": @@ -1164,7 +1162,7 @@ namespace ts { } } }; - json = convertToJsonWorker(jsonNode, errors, getTsconfigRootOptionsMap(), optionsIterator); + json = convertToJsonWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator); if (!typeAcquisition) { if (typingOptionstypeAcquisition) { typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? @@ -1244,16 +1242,16 @@ namespace ts { extendedConfigPath = `${extendedConfigPath}.json` as Path; } - const extendedResult = readConfigFileToJsonNode(extendedConfigPath, path => host.readFile(path)); - if (extendedResult.errors.length) { - errors.push(...extendedResult.errors); + const extendedResult = readConfigFileToJsonSourceFile(extendedConfigPath, path => host.readFile(path)); + if (extendedResult.parseDiagnostics.length) { + errors.push(...extendedResult.parseDiagnostics); return; } const extendedDirname = getDirectoryPath(extendedConfigPath); const relativeDifference = convertToRelativePath(extendedDirname, basePath, getCanonicalFileName); const updatePath: (path: string) => string = path => isRootedDiskPath(path) ? path : combinePaths(relativeDifference, path); // Merge configs (copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios) - const result = parseJsonNodeConfigFileContent(extendedResult.node, host, extendedDirname, /*existingOptions*/undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath])); + const result = parseJsonSourceFileConfigFileContent(extendedResult, host, extendedDirname, /*existingOptions*/undefined, getBaseFileName(extendedConfigPath), resolutionStack.concat([resolvedPath])); errors.push(...result.errors); const [include, exclude, files] = map(["include", "exclude", "files"], key => { if (!json[key] && result.raw[key]) { @@ -1313,7 +1311,7 @@ namespace ts { includeSpecs = ["**/*"]; } - const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, jsonNode); + const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, sourceFile); if (result.fileNames.length === 0 && !hasProperty(json, "files") && resolutionStack.length === 0) { errors.push( @@ -1328,7 +1326,7 @@ namespace ts { } function createCompilerDiagnosticForJson(message: DiagnosticMessage, arg0?: string, arg1?: string) { - if (!jsonNode) { + if (!sourceFile) { errors.push(createCompilerDiagnostic(message, arg0, arg1)); } } @@ -1548,7 +1546,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[], jsonNode: JsonNode): ExpandResult { + function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], jsonSourceFile: JsonSourceFile): ExpandResult { basePath = normalizePath(basePath); // The exclude spec list is converted into a regular expression, which allows us to quickly @@ -1567,11 +1565,11 @@ namespace ts { const wildcardFileMap = createMap(); if (include) { - include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonNode, "include"); + include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include"); } if (exclude) { - exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonNode, "exclude"); + exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude"); } // Wildcard directories (provided as part of a wildcard path) are stored in a @@ -1627,7 +1625,7 @@ namespace ts { }; } - function validateSpecs(specs: string[], errors: Diagnostic[], allowTrailingRecursion: boolean, jsonNode: JsonNode, specKey: string) { + 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)) { @@ -1647,13 +1645,13 @@ namespace ts { return validSpecs; function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { - if (jsonNode && jsonNode.kind === SyntaxKind.ObjectLiteralExpression) { - for (const property of jsonNode.properties) { + if (jsonSourceFile && jsonSourceFile.jsonObject) { + for (const property of jsonSourceFile.jsonObject.properties) { if (property.kind === SyntaxKind.PropertyAssignment && getTextOfPropertyName(property.name) === specKey) { const specsNode = property.initializer; for (const element of specsNode.elements) { if (element.kind === SyntaxKind.StringLiteral && (element).text === spec) { - return createDiagnosticForNodeInSourceFile(jsonNode.parent, element, message, spec); + return createDiagnosticForNodeInSourceFile(jsonSourceFile, element, message, spec); } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 89057dd2939a9..c1f4c61c272cb 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1928,6 +1928,8 @@ namespace ts { return ScriptKind.TS; case ".tsx": return ScriptKind.TSX; + case ".json": + return ScriptKind.JSON; default: return ScriptKind.Unknown; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b6885cb7ade0d..d534f42e1714d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -450,14 +450,12 @@ namespace ts { return Parser.parseIsolatedEntityName(text, languageVersion); } - export type ParsedNodeResults = { node?: T; errors: Diagnostic[] }; - /** * Parse json text into SyntaxTree and return node and parse errors if any * @param fileName * @param sourceText */ - export function parseJsonText(fileName: string, sourceText: string): ParsedNodeResults { + export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { return Parser.parseJsonText(fileName, sourceText); } @@ -622,36 +620,34 @@ namespace ts { return isInvalid ? entityName : undefined; } - export function parseJsonText(fileName: string, sourceText: string): ParsedNodeResults { - initializeState(sourceText, ScriptTarget.ES2015, /*syntaxCursor*/ undefined, ScriptKind.JS); + 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 = { kind: SyntaxKind.SourceFile, text: sourceText, fileName }; - let node: JsonNode; + sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON); + const result = sourceFile; + // Prime the scanner. nextToken(); if (token() === SyntaxKind.EndOfFileToken) { - node = parseTokenNode(); + sourceFile.endOfFileToken = parseTokenNode(); } else if (token() === SyntaxKind.OpenBraceToken || lookAhead(() => token() === SyntaxKind.StringLiteral)) { - node = parseObjectLiteralExpression(); - parseExpected(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); + result.jsonObject = parseObjectLiteralExpression(); + sourceFile.endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, /*reportAtCurrentPosition*/ false, Diagnostics.Unexpected_token); } else { parseExpected(SyntaxKind.OpenBraceToken); } - if (node) { - node.parent = sourceFile; - } - const errors = parseDiagnostics; + sourceFile.parseDiagnostics = parseDiagnostics; clearState(); - return { node, errors }; + 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) { @@ -669,7 +665,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/tsc.ts b/src/compiler/tsc.ts index 129c800d58fa1..43c3533f7e484 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -307,13 +307,13 @@ namespace ts { } const result = parseJsonText(configFileName, cachedConfigFileText); - reportDiagnostics(result.errors, /* compilerHost */ undefined); - if (!result.node) { + reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); + if (!result.endOfFileToken) { sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } const cwd = sys.getCurrentDirectory(); - const configParseResult = parseJsonNodeConfigFileContent(result.node, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd)); + const configParseResult = parseJsonSourceFileConfigFileContent(result, 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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cb57016c4ce80..b4753287fb55d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -527,8 +527,6 @@ namespace ts { export type AtToken = Token; export type ReadonlyToken = Token; - export type JsonNode = ObjectLiteralExpression | EndOfFileToken; - export type Modifier = Token | Token @@ -2189,6 +2187,10 @@ namespace ts { /* @internal */ ambientModuleNames: string[]; } + export interface JsonSourceFile extends SourceFile { + jsonObject?: ObjectLiteralExpression; + } + export interface ScriptReferenceHost { getCompilerOptions(): CompilerOptions; getSourceFile(fileName: string): SourceFile; @@ -3261,7 +3263,8 @@ namespace ts { JS = 1, JSX = 2, TS = 3, - TSX = 4 + TSX = 4, + JSON = 5 } export const enum ScriptTarget { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 63155e6a78293..e58a4f507a7dd 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1877,12 +1877,12 @@ namespace Harness { const data = testUnitData[i]; if (ts.getBaseFileName(data.name).toLowerCase() === "tsconfig.json") { const configJson = ts.parseJsonText(data.name, data.content); - assert.isTrue(configJson.node !== undefined); + assert.isTrue(configJson.endOfFileToken !== undefined); let baseDir = ts.normalizePath(ts.getDirectoryPath(data.name)); if (rootDir) { baseDir = ts.getNormalizedAbsolutePath(baseDir, rootDir); } - tsConfig = ts.parseJsonNodeConfigFileContent(configJson.node, parseConfigHost, baseDir); + tsConfig = ts.parseJsonSourceFileConfigFileContent(configJson, parseConfigHost, baseDir); tsConfig.options.configFilePath = data.name; // delete entry from the list diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index de22ff314fa38..a85f7a13e16d9 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -210,30 +210,23 @@ class ProjectRunner extends RunnerBase { let errors: ts.Diagnostic[]; if (configFileName) { - const result = ts.readConfigFileToJsonNode(configFileName, getSourceFileText); - if (!result.node) { - return { - moduleKind, - errors: result.errors - }; - } - + const result = ts.readConfigFileToJsonSourceFile(configFileName, getSourceFileText); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists, readDirectory, readFile }; - const configParseResult = ts.parseJsonNodeConfigFileContent(result.node, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); + const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); if (configParseResult.errors.length > 0) { return { moduleKind, - errors: result.errors.concat(configParseResult.errors) + errors: result.parseDiagnostics.concat(configParseResult.errors) }; } inputFiles = configParseResult.fileNames; compilerOptions = configParseResult.options; - errors = result.errors; + errors = result.parseDiagnostics; } const projectCompilerResult = compileProjectFiles(moduleKind, () => inputFiles, getSourceFileText, writeFile, compilerOptions); diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 6ae5dd63daabf..567519165c759 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -81,7 +81,7 @@ namespace RWC { readDirectory: Harness.IO.readDirectory, readFile: Harness.IO.readFile }; - const configParseResult = ts.parseJsonNodeConfigFileContent(parsedTsconfigFileContents.node, 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); } diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 4077f54ef3d15..e1136e5c0b02f 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -110,21 +110,29 @@ 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.readConfigFileToJsonSourceFile(entry, name => host.readFile(name)); + assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); + return ts.parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry); + } + function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: 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); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); expected.configFilePath = entry; assert.deepEqual(parsed.options, expected); assert.deepEqual(parsed.fileNames, expectedFiles); }); - it(name, () => { - const {node, errors} = ts.readConfigFileToJsonNode(entry, name => host.readFile(name)); - assert(node && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); - const parsed = ts.parseJsonNodeConfigFileContent(node, host, basePath, {}, entry); + it(name + "with jsonSourceFile", () => { + const parsed = getParseCommandLineJsonSourceFile(entry); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); expected.configFilePath = entry; assert.deepEqual(parsed.options, expected); @@ -134,16 +142,12 @@ namespace ts { 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, () => { - const {node, errors} = ts.readConfigFileToJsonNode(entry, name => host.readFile(name)); - assert(node && !errors.length, flattenDiagnosticMessageText(errors[0] && errors[0].messageText, "\n")); - const parsed = ts.parseJsonNodeConfigFileContent(node, host, basePath, {}, entry); + it(name + "with jsonSourceFile", () => { + const parsed = getParseCommandLineJsonSourceFile(entry); verifyDiagnostics(parsed.errors, expectedDiagnostics); }); } diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index d191e2d2f3bc3..a00ac5ef56b59 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -28,11 +28,11 @@ namespace ts { function assertCompilerOptionsWithJsonNode(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { const fileText = JSON.stringify(json); - const { node, errors } = parseJsonText(configFileName, fileText); - assert(!errors.length); - assert(!!node); + 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 } = parseJsonNodeConfigFileContent(node, host, "/apath/", /*existingOptions*/ undefined, configFileName); + const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); expectedResult.compilerOptions["configFilePath"] = configFileName; const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); diff --git a/src/harness/unittests/convertTypeAcquisitionFromJson.ts b/src/harness/unittests/convertTypeAcquisitionFromJson.ts index 535e7555f4f26..aae4ee3838212 100644 --- a/src/harness/unittests/convertTypeAcquisitionFromJson.ts +++ b/src/harness/unittests/convertTypeAcquisitionFromJson.ts @@ -40,11 +40,11 @@ namespace ts { function assertTypeAcquisitionWithJsonNode(json: any, configFileName: string, expectedResult: ExpectedResult) { const fileText = JSON.stringify(json); - const { node, errors } = parseJsonText(configFileName, fileText); - assert(!errors.length); - assert(!!node); + 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 } = parseJsonNodeConfigFileContent(node, host, "/apath/", /*existingOptions*/ undefined, configFileName); + 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); diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index 99614f075739d..f2d1f1e2ac73d 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -98,8 +98,13 @@ namespace ts { function validateMatches(expected: ts.ParsedCommandLine, json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[]) { { const jsonText = JSON.stringify(json); - const {node} = parseJsonText(caseInsensitiveTsconfigPath, jsonText); - const actual = ts.parseJsonNodeConfigFileContent(node, host, basePath, existingOptions, configFileName, resolutionStack); + 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); } { diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index c31608ee2fe43..39f3a1b3006d6 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -23,7 +23,7 @@ namespace ts { } { const parsed = ts.parseJsonText("/apath/tsconfig.json", jsonText); - const parsedCommand = ts.parseJsonNodeConfigFileContent(parsed.node, ts.sys, "tests/cases/unittests"); + 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); } @@ -38,7 +38,7 @@ namespace ts { 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); - return ts.parseJsonNodeConfigFileContent(parsed.node, host, basePath, /*existingOptions*/ undefined, configFileName); + return ts.parseJsonSourceFileConfigFileContent(parsed, host, basePath, /*existingOptions*/ undefined, configFileName); } function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) { @@ -231,8 +231,9 @@ namespace ts { } "files": ["file1.ts"] }`; - const { node, errors: diagnostics } = parseJsonText("config.json", content); - const configJsonObject = convertToJson(node, diagnostics); + const result = parseJsonText("config.json", content); + const diagnostics = result.parseDiagnostics; + const configJsonObject = convertToJson(result, diagnostics); const expectedResult = { compilerOptions: { allowJs: true, diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 4c7193be9213b..9637f6e20f808 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -790,18 +790,18 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private getDefaultParsedJsonNode(): EndOfFileToken { - return { kind: SyntaxKind.EndOfFileToken }; - } - private convertConfigFileContentToProjectOptions(configFilename: string): ConfigFileConversionResult { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); - const { node = this.getDefaultParsedJsonNode(), errors } = parseJsonText(configFilename, configFileContent); - const parsedCommandLine = parseJsonNodeConfigFileContent( - node, + const result = parseJsonText(configFilename, configFileContent); + if (!result.endOfFileToken) { + result.endOfFileToken = { kind: SyntaxKind.EndOfFileToken }; + } + const errors = result.parseDiagnostics; + const parsedCommandLine = parseJsonSourceFileConfigFileContent( + result, this.host, getDirectoryPath(configFilename), /*existingOptions*/ {}, diff --git a/src/services/shims.ts b/src/services/shims.ts index b6988198ad90e..22fd093561e3d 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1135,25 +1135,25 @@ namespace ts { const result = parseJsonText(fileName, text); - if (!result.node) { + if (!result.endOfFileToken) { return { options: {}, typeAcquisition: {}, files: [], raw: {}, - errors: realizeDiagnostics(result.errors, "\r\n") + errors: realizeDiagnostics(result.parseDiagnostics, "\r\n") }; } const normalizedFileName = normalizeSlashes(fileName); - const configFile = parseJsonNodeConfigFileContent(result.node, 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(result.errors.concat(configFile.errors), "\r\n") + errors: realizeDiagnostics(result.parseDiagnostics.concat(configFile.errors), "\r\n") }; }); } From 911511e011d28e2bd41781a639aeb5f0a8dda4bf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 29 Nov 2016 12:42:17 -0800 Subject: [PATCH 06/15] Report option diagnostic in tsconfig.json if possible --- src/compiler/commandLineParser.ts | 11 +- src/compiler/program.ts | 151 ++++++++++++++---- src/compiler/types.ts | 3 +- src/compiler/utilities.ts | 9 ++ src/harness/compilerRunner.ts | 19 ++- src/harness/harness.ts | 15 +- src/harness/projectsRunner.ts | 33 ++-- src/harness/rwcRunner.ts | 9 +- .../unittests/configurationExtension.ts | 20 +-- .../convertCompilerOptionsFromJson.ts | 1 + src/services/services.ts | 14 +- src/services/utilities.ts | 6 +- ...NodeModuleJsDepthDefaultsToZero.errors.txt | 11 ++ ...gBasedModuleResolution1_classic.errors.txt | 18 ++- ...pingBasedModuleResolution1_node.errors.txt | 17 +- ...gBasedModuleResolution2_classic.errors.txt | 20 ++- ...pingBasedModuleResolution2_node.errors.txt | 20 ++- ...tion_withExtension_failedLookup.errors.txt | 10 ++ .../reference/pathsValidation1.errors.txt | 12 +- .../reference/pathsValidation2.errors.txt | 12 +- .../reference/pathsValidation3.errors.txt | 13 +- ...MetadataCommonJSISolatedModules.errors.txt | 14 ++ ...MetadataCommonJSISolatedModules.errors.txt | 14 ++ ...ommonJSISolatedModulesNoResolve.errors.txt | 15 ++ ...ommonJSISolatedModulesNoResolve.errors.txt | 15 ++ .../emitDecoratorMetadataSystemJS.errors.txt | 13 ++ .../emitDecoratorMetadataSystemJS.errors.txt | 13 ++ ...MetadataSystemJSISolatedModules.errors.txt | 15 ++ ...MetadataSystemJSISolatedModules.errors.txt | 15 ++ ...ystemJSISolatedModulesNoResolve.errors.txt | 16 ++ ...ystemJSISolatedModulesNoResolve.errors.txt | 16 ++ ...ationDifferentNamesNotSpecified.errors.txt | 7 +- ...entNamesNotSpecifiedWithAllowJs.errors.txt | 10 +- ...entNamesNotSpecifiedWithAllowJs.errors.txt | 15 +- ...pilationDifferentNamesSpecified.errors.txt | 5 + ...pilationDifferentNamesSpecified.errors.txt | 8 +- ...ferentNamesSpecifiedWithAllowJs.errors.txt | 11 +- ...ferentNamesSpecifiedWithAllowJs.errors.txt | 16 +- ...SameNameDTsSpecifiedWithAllowJs.errors.txt | 8 +- ...SameNameDTsSpecifiedWithAllowJs.errors.txt | 8 +- ...eNameDtsNotSpecifiedWithAllowJs.errors.txt | 7 +- ...eNameDtsNotSpecifiedWithAllowJs.errors.txt | 7 +- ...ameFilesNotSpecifiedWithAllowJs.errors.txt | 5 +- ...ameFilesNotSpecifiedWithAllowJs.errors.txt | 5 +- ...meNameFilesSpecifiedWithAllowJs.errors.txt | 8 +- ...meNameFilesSpecifiedWithAllowJs.errors.txt | 8 +- .../amd/nodeModulesImportHigher.errors.txt | 10 ++ .../node/nodeModulesImportHigher.errors.txt | 10 ++ .../nodeModulesMaxDepthExceeded.errors.txt | 11 ++ .../nodeModulesMaxDepthExceeded.errors.txt | 11 ++ .../nodeModulesMaxDepthIncreased.errors.txt | 8 + .../nodeModulesMaxDepthIncreased.errors.txt | 8 + 52 files changed, 654 insertions(+), 102 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 74308951d54c4..dc31eb1b9bd08 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1026,7 +1026,7 @@ namespace ts { case "project": break; default: - const value = options[name]; + const value = options[name]; const optionDefinition = optionsNameMap.get(name.toLowerCase()); if (optionDefinition) { const customTypeMap = getCustomTypeMapOfCommandLineOption(optionDefinition); @@ -1193,6 +1193,7 @@ namespace ts { options = extend(existingOptions, options); options.configFilePath = configFileName; + options.configFile = sourceFile; const { fileNames, wildcardDirectories } = getFileNames(errors); @@ -1639,17 +1640,15 @@ namespace ts { function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic { if (jsonSourceFile && jsonSourceFile.jsonObject) { - for (const property of jsonSourceFile.jsonObject.properties) { - if (property.kind === SyntaxKind.PropertyAssignment && getTextOfPropertyName(property.name) === specKey) { - const specsNode = property.initializer; - for (const element of specsNode.elements) { + 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/program.ts b/src/compiler/program.ts index 650711afc790a..994603e0944c6 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -329,6 +329,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[]; @@ -1108,6 +1109,9 @@ namespace ts { const allDiagnostics: Diagnostic[] = []; addRange(allDiagnostics, fileProcessingDiagnostics.getGlobalDiagnostics()); addRange(allDiagnostics, programDiagnostics.getGlobalDiagnostics()); + if (options.configFile) { + addRange(allDiagnostics, programDiagnostics.getDiagnostics(options.configFile.fileName)); + } return sortAndDeduplicateDiagnostics(allDiagnostics); } @@ -1541,33 +1545,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) { @@ -1576,63 +1580,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) { - 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; @@ -1641,7 +1647,7 @@ namespace ts { const firstNonAmbientExternalModuleSourceFile = forEach(files, f => isExternalModule(f) && !isDeclarationFile(f) ? 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) && !isDeclarationFile(f) ? f : undefined); @@ -1659,7 +1665,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); @@ -1678,29 +1684,29 @@ 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.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 @@ -1740,6 +1746,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/types.ts b/src/compiler/types.ts index c7d8eef61b413..44d177d82a394 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3198,6 +3198,7 @@ baseUrl?: string; charset?: string; /* @internal */ configFilePath?: string; + /* @internal */ configFile?: JsonSourceFile; declaration?: boolean; declarationDir?: string; /* @internal */ diagnostics?: boolean; @@ -3266,7 +3267,7 @@ /*@internal*/ version?: boolean; /*@internal*/ watch?: boolean; - [option: string]: CompilerOptionsValue | undefined; + [option: string]: CompilerOptionsValue | JsonSourceFile | undefined; } export interface TypeAcquisition { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bbb0fd00cb8b3..85a46fb6a2af1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -948,6 +948,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; diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index 327932f366703..307b843dc49c3 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); + 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 c3bd46b2ba0de..7e45032c1df3e 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1614,7 +1614,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."); } @@ -1650,7 +1650,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) { @@ -1801,7 +1801,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 @@ -1887,6 +1892,7 @@ 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") { @@ -1898,6 +1904,7 @@ namespace Harness { } tsConfig = ts.parseJsonSourceFileConfigFileContent(configJson, parseConfigHost, baseDir); tsConfig.options.configFilePath = data.name; + tsConfigFileUnitData = data; // delete entry from the list ts.orderedRemoveItemAt(testUnitData, i); @@ -1905,7 +1912,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 d0ac213db7c2f..11df554ddfe9e 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; @@ -125,7 +126,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 { @@ -149,6 +151,7 @@ class ProjectRunner extends RunnerBase { } return { + configFileSourceFiles, moduleKind, program, errors, @@ -197,6 +200,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) { @@ -211,6 +215,7 @@ class ProjectRunner extends RunnerBase { let errors: ts.Diagnostic[]; if (configFileName) { const result = ts.readConfigFileToJsonSourceFile(configFileName, getSourceFileText); + configFileSourceFiles.push(result); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), fileExists, @@ -220,6 +225,7 @@ class ProjectRunner extends RunnerBase { const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); if (configParseResult.errors.length > 0) { return { + configFileSourceFiles, moduleKind, errors: result.parseDiagnostics.concat(configParseResult.errors) }; @@ -229,8 +235,9 @@ class ProjectRunner extends RunnerBase { errors = result.parseDiagnostics; } - const projectCompilerResult = compileProjectFiles(moduleKind, () => inputFiles, getSourceFileText, writeFile, compilerOptions); + const projectCompilerResult = compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, getSourceFileText, writeFile, compilerOptions); return { + configFileSourceFiles, moduleKind, program: projectCompilerResult.program, compilerOptions, @@ -397,7 +404,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); @@ -423,16 +430,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 567519165c759..6379f829d77b5 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,6 +76,7 @@ namespace RWC { const tsconfigFile = ts.forEach(ioLog.filesRead, f => isTsConfigFile(f) ? f : undefined); if (tsconfigFile) { const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); + tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), @@ -198,8 +201,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 +221,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); } }); diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 8963bf2e50576..05a7303a2c53e 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -119,23 +119,25 @@ namespace ts { function getParseCommandLineJsonSourceFile(entry: string) { const jsonSourceFile = ts.readConfigFileToJsonSourceFile(entry, name => host.readFile(name)); assert(jsonSourceFile.endOfFileToken && !jsonSourceFile.parseDiagnostics.length, flattenDiagnosticMessageText(jsonSourceFile.parseDiagnostics[0] && jsonSourceFile.parseDiagnostics[0].messageText, "\n")); - return ts.parseJsonSourceFileConfigFileContent(jsonSourceFile, host, basePath, {}, entry); + 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 parsed = getParseCommandLine(entry); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - expected.configFilePath = entry; - assert.deepEqual(parsed.options, expected); + assert.deepEqual(parsed.options, ts.extend(expected, { configFile: undefined })); assert.deepEqual(parsed.fileNames, expectedFiles); }); - it(name + "with jsonSourceFile", () => { - const parsed = getParseCommandLineJsonSourceFile(entry); + 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.deepEqual(parsed.options, ts.extend(expected, { configFile: jsonSourceFile })); assert.deepEqual(parsed.fileNames, expectedFiles); }); } @@ -146,8 +148,8 @@ namespace ts { verifyDiagnostics(parsed.errors, expectedDiagnostics); }); - it(name + "with jsonSourceFile", () => { - const parsed = getParseCommandLineJsonSourceFile(entry); + it(name + " with jsonSourceFile", () => { + const { parsed } = getParseCommandLineJsonSourceFile(entry); verifyDiagnostics(parsed.errors, expectedDiagnostics); }); } diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index a00ac5ef56b59..450ddface1d53 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -34,6 +34,7 @@ namespace ts { const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); expectedResult.compilerOptions["configFilePath"] = configFileName; + expectedResult.compilerOptions.configFile = result; const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); diff --git a/src/services/services.ts b/src/services/services.ts index cb38b1d500ee6..3b11b3c39f88c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1216,8 +1216,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, "configFile")) { + 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/utilities.ts b/src/services/utilities.ts index c65f308a2ce56..bd54fb9c987eb 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1047,8 +1047,12 @@ namespace ts { return false; } - export function compareDataObjects(dst: any, src: any): boolean { + export function compareDataObjects(dst: any, src: any, ignoreKey?: string): boolean { for (const e in dst) { + if (ignoreKey && ignoreKey === e) { + continue; + } + if (typeof dst[e] === "object") { if (!compareDataObjects(dst[e], src[e])) { return false; diff --git a/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt b/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt index 2218e7910aefd..94a22dc946fd5 100644 --- a/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt +++ b/tests/baselines/reference/maxNodeModuleJsDepthDefaultsToZero.errors.txt @@ -1,6 +1,17 @@ /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..f687e70a3cfd6 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution1_classic.errors.txt @@ -1,7 +1,23 @@ -error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option. +c:/root/tsconfig.json(6,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..6a7ae059c79bb 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution1_node.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/pathMappingBasedModuleResolution2_classic.errors.txt b/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt index ad954bd142b54..4161a67cedec5 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution2_classic.errors.txt @@ -1,8 +1,24 @@ -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(9,13): error TS5061: Pattern '*1*' can have at most one '*' character +tests/cases/compiler/root/tsconfig.json(9,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 ad954bd142b54..4161a67cedec5 100644 --- a/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt +++ b/tests/baselines/reference/pathMappingBasedModuleResolution2_node.errors.txt @@ -1,8 +1,24 @@ -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(9,13): error TS5061: Pattern '*1*' can have at most one '*' character +tests/cases/compiler/root/tsconfig.json(9,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 7578f730c334c..fcb8564429cee 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(2,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 668f037a03b4d..ecbecb0cc9125 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 668f037a03b4d..ecbecb0cc9125 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 99e7b193f0c42..bd62cce9843f0 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/**/*"] + } + ==== entry.js (0 errors) ==== var m3 = require("m3"); diff --git a/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt b/tests/baselines/reference/project/nodeModulesMaxDepthExceeded/node/nodeModulesMaxDepthExceeded.errors.txt index 99e7b193f0c42..bd62cce9843f0 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/**/*"] + } + ==== entry.js (0 errors) ==== var m3 = require("m3"); 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", From 98bd31017d70e7fa683ba2f94d3c9f98f1414807 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 29 Nov 2016 12:42:17 -0800 Subject: [PATCH 07/15] Update the protocol to return file name in compiler diagnostics and project errors --- .../unittests/tsserverProjectSystem.ts | 65 ++++++++++++++ src/server/protocol.ts | 15 +++- src/server/session.ts | 86 ++++++++++++++++--- 3 files changed, 150 insertions(+), 16 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 5ed0d9815115d..a6cf0ac6cd802 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3102,4 +3102,69 @@ namespace ts.projectSystem { }); }); + 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.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + }).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.CompilerOptionsDiagnosticsFull, + seq: 2, + arguments: { projectFileName: projectName } + }).response; + assert.isTrue(diagsAfterEdit.length === 2); + + verifyDiagnostic(diags[0], diagsAfterEdit[0]); + verifyDiagnostic(diags[1], diagsAfterEdit[1]); + + function verifyDiagnostic(beforeEditDiag: server.protocol.DiagnosticWithLinePositionAndFileName, afterEditDiag: server.protocol.DiagnosticWithLinePositionAndFileName) { + 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); + assert.equal(beforeEditDiag.fileName, afterEditDiag.fileName); + } + }); + }); } \ No newline at end of file diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 680b81dff9986..284125396b00f 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -358,6 +358,10 @@ namespace ts.server.protocol { code: number; } + export interface DiagnosticWithLinePositionAndFileName extends DiagnosticWithLinePosition { + fileName: string; + } + /** * Response message for "projectInfo" request */ @@ -960,7 +964,7 @@ namespace ts.server.protocol { /** * List of errors in project */ - projectErrors: DiagnosticWithLinePosition[]; + projectErrors: DiagnosticWithLinePositionAndFileName[]; } /** @@ -1786,6 +1790,13 @@ namespace ts.server.protocol { code?: number; } + 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. @@ -1820,7 +1831,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 1fa3986f66fbb..e34b03a9c8cbd 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -60,11 +60,17 @@ namespace ts.server { }; } - function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic { + function convertToILineInfo(lineAndCharacter: LineAndCharacter): ILineInfo { + return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 }; + } + + function formatConfigFileDiag(diag: ts.Diagnostic): protocol.DiagnosticWithFileName { return { - start: undefined, - end: undefined, - text: ts.flattenDiagnosticMessageText(diag.messageText, "\n") + start: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start)), + end: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)), + text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), + code: diag.code, + fileName: diag.file && diag.file.fileName }; } @@ -193,6 +199,31 @@ namespace ts.server { return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`; } + /** + * Should Remap project Files with ts diagnostics if + * - there are project errors + * - options contain configFile - so we can remove it from options before serializing + * @param p project files with ts diagnostics + */ + function shouldRemapProjectFilesWithTSDiagnostics(p: ProjectFilesWithTSDiagnostics) { + return (p.projectErrors && !!p.projectErrors.length) || + (p.info && !!p.info.options.configFile); + } + + /** + * Get the compiler options without configFile key + * @param options + */ + function getCompilerOptionsWithoutConfigFile(options: CompilerOptions) { + const result: CompilerOptions = {}; + for (const option in options) { + if (option !== "configFile") { + result[option] = options[option]; + } + } + return result; + } + export class Session implements EventSender { private readonly gcTimer: GcTimer; protected projectService: ProjectService; @@ -410,7 +441,20 @@ namespace ts.server { private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) { const project = this.getProject(args.projectFileName); - return this.convertToDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics(), /*scriptInfo*/ undefined); + return this.convertToCompilerOptionsDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics()); + } + + private convertToCompilerOptionsDiagnosticsWithLinePosition(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)), + fileName: d.file && d.file.fileName + }); } private convertToDiagnosticsWithLinePosition(diagnostics: Diagnostic[], scriptInfo: ScriptInfo) { @@ -1408,19 +1452,33 @@ namespace ts.server { }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects); - if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) { + if (!result.some(shouldRemapProjectFilesWithTSDiagnostics)) { return this.requiredResponse(result); } const converted = map(result, p => { - if (!p.projectErrors || p.projectErrors.length === 0) { - return p; + if (shouldRemapProjectFilesWithTSDiagnostics(p)) { + const projectErrors = p.projectErrors && p.projectErrors.length ? + this.convertToCompilerOptionsDiagnosticsWithLinePosition(p.projectErrors) : + p.projectErrors; + + const info = p.info && !!p.info.options.configFile ? + { + projectName: p.info.projectName, + isInferred: p.info.isInferred, + version: p.info.version, + options: getCompilerOptionsWithoutConfigFile(p.info.options), + languageServiceDisabled: p.info.languageServiceDisabled + } : p.info; + + return { + info, + changes: p.changes, + files: p.files, + projectErrors + }; } - return { - info: p.info, - changes: p.changes, - files: p.files, - projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) - }; + + return p; }); return this.requiredResponse(converted); }, From cb1b16435d37c1f754a62db2f905d99e8a2772ae Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 8 Feb 2017 11:45:03 -0800 Subject: [PATCH 08/15] Encorporated PR feedback --- src/compiler/commandLineParser.ts | 142 +++++++++--------- src/compiler/diagnosticMessages.json | 2 +- src/compiler/types.ts | 2 +- src/harness/projectsRunner.ts | 2 +- .../unittests/configurationExtension.ts | 2 +- src/harness/unittests/tsconfigParsing.ts | 2 +- 6 files changed, 75 insertions(+), 77 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index dc31eb1b9bd08..7372d52ec1b30 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -717,7 +717,7 @@ namespace ts { export function parseConfigFileTextToJson(fileName: string, jsonText: string): { config?: any; error?: Diagnostic } { const jsonSourceFile = parseJsonText(fileName, jsonText); return { - config: convertToJson(jsonSourceFile, jsonSourceFile.parseDiagnostics), + config: convertToObject(jsonSourceFile, jsonSourceFile.parseDiagnostics), error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined }; } @@ -726,7 +726,7 @@ namespace ts { * Read tsconfig.json file * @param fileName The path to the config file */ - export function readConfigFileToJsonSourceFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { + export function readJsonConfigFile(fileName: string, readFile: (path: string) => string): JsonSourceFile { let text = ""; try { text = readFile(fileName); @@ -737,66 +737,64 @@ namespace ts { return parseJsonText(fileName, text); } - const tsconfigRootOptions: CommandLineOption[] = [ - { - name: "compilerOptions", - type: "object", - optionDeclarations: optionDeclarations, - extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0 - }, - { - name: "typingOptions", - type: "object", - optionDeclarations: typeAcquisitionDeclarations, - extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 - }, - { - name: "typeAcquisition", - type: "object", - optionDeclarations: 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 - ]; - function commandLineOptionsToMap(options: CommandLineOption[]) { return arrayToMap(options, option => option.name); } - let _tsconfigRootOptionsMap: Map; + let _tsconfigRootOptions: Map; function getTsconfigRootOptionsMap() { - if (_tsconfigRootOptionsMap === undefined) { - _tsconfigRootOptionsMap = commandLineOptionsToMap(tsconfigRootOptions); + if (_tsconfigRootOptions === undefined) { + _tsconfigRootOptions = commandLineOptionsToMap([ + { + name: "compilerOptions", + type: "object", + optionDeclarations: commandLineOptionsToMap(optionDeclarations), + extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0 + }, + { + name: "typingOptions", + type: "object", + optionDeclarations: commandLineOptionsToMap(typeAcquisitionDeclarations), + extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 + }, + { + name: "typeAcquisition", + type: "object", + optionDeclarations: 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 _tsconfigRootOptionsMap; + return _tsconfigRootOptions; } interface JsonConversionNotifier { @@ -811,8 +809,8 @@ namespace ts { * @param jsonNode * @param errors */ - export function convertToJson(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { - return convertToJsonWorker(sourceFile, errors); + export function convertToObject(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { + return convertToObjectWorker(sourceFile, errors); } /** @@ -820,7 +818,7 @@ namespace ts { * @param jsonNode * @param errors */ - function convertToJsonWorker(sourceFile: JsonSourceFile, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { + function convertToObjectWorker(sourceFile: JsonSourceFile, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { if (!sourceFile.jsonObject) { if (sourceFile.endOfFileToken) { return {}; @@ -830,7 +828,7 @@ namespace ts { return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions); - function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, options?: Map, extraKeyDiagnosticMessage?: DiagnosticMessage, optionsObject?: string): any { + function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, knownOptions: Map, extraKeyDiagnosticMessage?: DiagnosticMessage, optionsObject?: string): any { const result: any = {}; for (const element of node.properties) { if (element.kind !== SyntaxKind.PropertyAssignment) { @@ -846,21 +844,21 @@ namespace ts { } const keyText = getTextOfPropertyName(element.name); - const option = options ? options.get(keyText) : undefined; + const option = knownOptions ? knownOptions.get(keyText) : undefined; if (extraKeyDiagnosticMessage && !option) { errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText)); } - const value = parseValue(element.initializer, option); + 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 (optionsIterator && - (optionsObject || options === knownRootOptions)) { + (optionsObject || knownOptions === knownRootOptions)) { const isValidOptionValue = isCompilerOptionsValue(option, value); if (optionsObject && isValidOptionValue) { optionsIterator.onSetOptionKeyValue(optionsObject, option, value); } - if (options === knownRootOptions && (isValidOptionValue || !option)) { + if (knownOptions === knownRootOptions && (isValidOptionValue || !option)) { optionsIterator.onRootKeyValue(keyText, element.name, value, element.initializer); } } @@ -873,12 +871,12 @@ namespace ts { function convertArrayLiteralExpressionToJson(elements: NodeArray, option?: CommandLineOption): any[] { const result: any[] = []; for (const element of elements) { - result.push(parseValue(element, option)); + result.push(convertPropertyValueToJson(element, option)); } return result; } - function parseValue(node: Expression, option: CommandLineOption): any { + function convertPropertyValueToJson(node: Expression, option: CommandLineOption): any { switch (node.kind) { case SyntaxKind.TrueKeyword: reportInvalidOptionValue(option && option.type !== "boolean"); @@ -919,7 +917,7 @@ namespace ts { case SyntaxKind.ObjectLiteralExpression: reportInvalidOptionValue(option && option.type !== "object"); const objectOption = option; - const optionDeclarations = option && objectOption.optionDeclarations ? commandLineOptionsToMap(objectOption.optionDeclarations) : undefined; + const optionDeclarations = option && objectOption.optionDeclarations; return convertObjectLiteralExpressionToJson( node, optionDeclarations, @@ -934,10 +932,10 @@ namespace ts { // Not in expected format if (option) { - reportInvalidOptionValue(!!option); + reportInvalidOptionValue(/*isError*/ true); } else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_number_object_array_true_false_or_null_expected)); + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); } return undefined; @@ -1097,7 +1095,7 @@ namespace ts { options: {}, fileNames: [], typeAcquisition: {}, - raw: json || convertToJson(sourceFile, errors), + raw: json || convertToObject(sourceFile, errors), errors: errors.concat(createCompilerDiagnostic(Diagnostics.Circularity_detected_while_resolving_configuration_Colon_0, [...resolutionStack, resolvedPath].join(" -> "))), wildcardDirectories: {} }; @@ -1154,7 +1152,7 @@ namespace ts { } } }; - json = convertToJsonWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator); + json = convertToObjectWorker(sourceFile, errors, getTsconfigRootOptionsMap(), optionsIterator); if (!typeAcquisition) { if (typingOptionstypeAcquisition) { typeAcquisition = (typingOptionstypeAcquisition.enableAutoDiscovery !== undefined) ? @@ -1235,7 +1233,7 @@ namespace ts { extendedConfigPath = `${extendedConfigPath}.json` as Path; } - const extendedResult = readConfigFileToJsonSourceFile(extendedConfigPath, path => host.readFile(path)); + const extendedResult = readJsonConfigFile(extendedConfigPath, path => host.readFile(path)); if (extendedResult.parseDiagnostics.length) { errors.push(...extendedResult.parseDiagnostics); return; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 4d14e41a0818e..a9174af8e4269 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -863,7 +863,7 @@ "category": "Error", "code": 1320 }, - "String, number, object, array, true, false or null expected.": { + "Property value can only be string literal, numeric literal, 'true', 'false', 'null', object literal or array literal.": { "category": "Error", "code": 1321 }, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 44d177d82a394..c54b01fda5c7e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3395,7 +3395,7 @@ /* @internal */ export interface TsConfigOnlyOption extends CommandLineOptionBase { type: "object"; - optionDeclarations?: CommandLineOption[]; + optionDeclarations?: Map; extraKeyDiagnosticMessage?: DiagnosticMessage; } diff --git a/src/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 11df554ddfe9e..fce2f5587bcf6 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -214,7 +214,7 @@ class ProjectRunner extends RunnerBase { let errors: ts.Diagnostic[]; if (configFileName) { - const result = ts.readConfigFileToJsonSourceFile(configFileName, getSourceFileText); + const result = ts.readJsonConfigFile(configFileName, getSourceFileText); configFileSourceFiles.push(result); const configParseHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), diff --git a/src/harness/unittests/configurationExtension.ts b/src/harness/unittests/configurationExtension.ts index 05a7303a2c53e..f4a5f337b121d 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -117,7 +117,7 @@ namespace ts { } function getParseCommandLineJsonSourceFile(entry: string) { - const jsonSourceFile = ts.readConfigFileToJsonSourceFile(entry, name => host.readFile(name)); + 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, diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 39f3a1b3006d6..98ad67a8bb3ff 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -233,7 +233,7 @@ namespace ts { }`; const result = parseJsonText("config.json", content); const diagnostics = result.parseDiagnostics; - const configJsonObject = convertToJson(result, diagnostics); + const configJsonObject = convertToObject(result, diagnostics); const expectedResult = { compilerOptions: { allowJs: true, From c97b389b07ae46fc500e41552905208005ea50b5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 May 2017 09:06:01 -0700 Subject: [PATCH 09/15] Simplifying the json conversion notifier --- src/compiler/commandLineParser.ts | 160 +++++++++++++++++++--------- src/compiler/types.ts | 2 +- src/harness/unittests/matchFiles.ts | 9 +- 3 files changed, 112 insertions(+), 59 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 1b230e43540f6..39e45abe712f5 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -917,19 +917,19 @@ namespace ts { { name: "compilerOptions", type: "object", - optionDeclarations: commandLineOptionsToMap(optionDeclarations), + elementOptions: commandLineOptionsToMap(optionDeclarations), extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0 }, { name: "typingOptions", type: "object", - optionDeclarations: commandLineOptionsToMap(typeAcquisitionDeclarations), + elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations), extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 }, { name: "typeAcquisition", type: "object", - optionDeclarations: commandLineOptionsToMap(typeAcquisitionDeclarations), + elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations), extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0 }, { @@ -967,27 +967,48 @@ namespace ts { } interface JsonConversionNotifier { - /** Notifies options object is being set with the optionKey and optionValue is being set */ - onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue): void; - /** Notify when root key value is being set */ - onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression): void; + /** + * 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 - * @param jsonNode - * @param errors */ export function convertToObject(sourceFile: JsonSourceFile, errors: Diagnostic[]): any { - return convertToObjectWorker(sourceFile, errors); + return convertToObjectWorker(sourceFile, errors, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); } /** * Convert the json syntax tree into the json value - * @param jsonNode - * @param errors */ - function convertToObjectWorker(sourceFile: JsonSourceFile, errors: Diagnostic[], knownRootOptions?: Map, optionsIterator?: JsonConversionNotifier): any { + function convertToObjectWorker( + sourceFile: JsonSourceFile, + errors: Diagnostic[], + knownRootOptions: Map | undefined, + jsonConversionNotifier: JsonConversionNotifier | undefined): any { if (!sourceFile.jsonObject) { if (sourceFile.endOfFileToken) { return {}; @@ -995,9 +1016,15 @@ namespace ts { return undefined; } - return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions); + return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions, + /*extraKeyDiagnosticMessage*/ undefined, /*parentOption*/ undefined); - function convertObjectLiteralExpressionToJson(node: ObjectLiteralExpression, knownOptions: Map, extraKeyDiagnosticMessage?: DiagnosticMessage, optionsObject?: string): any { + 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) { @@ -1021,32 +1048,45 @@ namespace ts { if (typeof keyText !== undefined && typeof value !== undefined) { result[keyText] = value; // Notify key value set, if user asked for it - if (optionsIterator && - (optionsObject || knownOptions === knownRootOptions)) { + 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 (optionsObject && isValidOptionValue) { - optionsIterator.onSetOptionKeyValue(optionsObject, option, value); + if (parentOption) { + if (isValidOptionValue) { + // Notify option set in the parent if its a valid option value + jsonConversionNotifier.onSetValidOptionKeyValueInParent(parentOption, option, value); + } } - if (knownOptions === knownRootOptions && (isValidOptionValue || !option)) { - optionsIterator.onRootKeyValue(keyText, element.name, value, element.initializer); + 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, option?: CommandLineOption): any[] { + function convertArrayLiteralExpressionToJson( + elements: NodeArray, + elementOption: CommandLineOption | undefined + ): any[] { const result: any[] = []; for (const element of elements) { - result.push(convertPropertyValueToJson(element, option)); + result.push(convertPropertyValueToJson(element, elementOption)); } return result; } - function convertPropertyValueToJson(node: Expression, option: CommandLineOption): any { - switch (node.kind) { + function convertPropertyValueToJson(valueExpression: Expression, option: CommandLineOption): any { + switch (valueExpression.kind) { case SyntaxKind.TrueKeyword: reportInvalidOptionValue(option && option.type !== "boolean"); return true; @@ -1060,11 +1100,11 @@ namespace ts { return null; // tslint:disable-line:no-null-keyword case SyntaxKind.StringLiteral: - if (!isDoubleQuotedString(node)) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.String_literal_with_double_quotes_expected)); + 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 = (node).text; + const text = (valueExpression).text; if (option && typeof option.type !== "string") { const customOption = option; // Validate custom option type @@ -1072,7 +1112,7 @@ namespace ts { errors.push( createDiagnosticForInvalidCustomType( customOption, - (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0, arg1) + (message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, message, arg0, arg1) ) ); } @@ -1081,22 +1121,34 @@ namespace ts { case SyntaxKind.NumericLiteral: reportInvalidOptionValue(option && option.type !== "number"); - return Number((node).text); + return Number((valueExpression).text); case SyntaxKind.ObjectLiteralExpression: reportInvalidOptionValue(option && option.type !== "object"); - const objectOption = option; - const optionDeclarations = option && objectOption.optionDeclarations; - return convertObjectLiteralExpressionToJson( - node, - optionDeclarations, - option && objectOption.extraKeyDiagnosticMessage, - optionDeclarations && option.name - ); + 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((node).elements, option && (option).element); + return convertArrayLiteralExpressionToJson( + (valueExpression).elements, + option && (option).element); } // Not in expected format @@ -1104,14 +1156,14 @@ namespace ts { reportInvalidOptionValue(/*isError*/ true); } else { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.Property_value_can_only_be_string_literal_numeric_literal_true_false_null_object_literal_or_array_literal)); + 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, node, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option.name, getCompilerOptionValueTypeString(option))); + errors.push(createDiagnosticForNodeInSourceFile(sourceFile, valueExpression, Diagnostics.Compiler_option_0_requires_a_value_of_type_1, option.name, getCompilerOptionValueTypeString(option))); } } } @@ -1542,15 +1594,17 @@ namespace ts { let extendedConfigPath: Path; const optionsIterator: JsonConversionNotifier = { - onSetOptionKeyValue(optionsObject: string, option: CommandLineOption, value: CompilerOptionsValue) { - Debug.assert(optionsObject === "compilerOptions" || optionsObject === "typeAcquisition" || optionsObject === "typingOptions"); - const currentOption = optionsObject === "compilerOptions" ? options : - optionsObject === "typeAcquisition" ? (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))) : + 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); }, - onRootKeyValue(key: string, propertyName: PropertyName, value: CompilerOptionsValue, node: Expression) { + onSetValidOptionKeyValueInRoot(key: string, _keyNode: PropertyName, value: CompilerOptionsValue, valueNode: Expression) { switch (key) { case "extends": extendedConfigPath = getExtendsConfigPath( @@ -1560,18 +1614,20 @@ namespace ts { getCanonicalFileName, errors, (message, arg0) => - createDiagnosticForNodeInSourceFile(sourceFile, node, message, arg0) + createDiagnosticForNodeInSourceFile(sourceFile, valueNode, message, arg0) ); return; - case "excludes": - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, propertyName, Diagnostics.Unknown_option_excludes_Did_you_mean_exclude)); - return; case "files": if ((value).length === 0) { - errors.push(createDiagnosticForNodeInSourceFile(sourceFile, node, Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json")); + 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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f6310d85a6951..18cd8086a237a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3603,7 +3603,7 @@ namespace ts { /* @internal */ export interface TsConfigOnlyOption extends CommandLineOptionBase { type: "object"; - optionDeclarations?: Map; + elementOptions?: Map; extraKeyDiagnosticMessage?: DiagnosticMessage; } diff --git a/src/harness/unittests/matchFiles.ts b/src/harness/unittests/matchFiles.ts index a9ec450795204..e04546719302a 100644 --- a/src/harness/unittests/matchFiles.ts +++ b/src/harness/unittests/matchFiles.ts @@ -460,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", () => { @@ -963,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 = { @@ -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 = { From ea60e9966d0dca40458f470909d682c9b88245f4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 15 May 2017 13:13:00 -0700 Subject: [PATCH 10/15] Get configFiles as part of file names --- src/compiler/commandLineParser.ts | 9 ++- src/compiler/types.ts | 1 + .../unittests/tsserverProjectSystem.ts | 58 +++++++++---------- src/harness/unittests/typingsInstaller.ts | 20 +++---- src/server/project.ts | 13 ++++- src/server/session.ts | 8 +-- src/server/utilities.ts | 2 +- tests/cases/fourslash/server/projectInfo02.ts | 2 +- .../server/projectWithNonExistentFiles.ts | 2 +- 9 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 39e45abe712f5..0da5ce3f6b953 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1526,7 +1526,7 @@ namespace ts { if (ownConfig.extendedConfigPath) { // copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios. resolutionStack = resolutionStack.concat([resolvedPath]); - const extendedConfig = getExtendedConfig(ownConfig.extendedConfigPath, host, basePath, getCanonicalFileName, + const extendedConfig = getExtendedConfig(sourceFile, ownConfig.extendedConfigPath, host, basePath, getCanonicalFileName, resolutionStack, errors); if (extendedConfig && isSuccessfulParsedTsconfig(extendedConfig)) { const baseRaw = extendedConfig.raw; @@ -1674,6 +1674,7 @@ namespace ts { } function getExtendedConfig( + sourceFile: JsonSourceFile, extendedConfigPath: Path, host: ts.ParseConfigHost, basePath: string, @@ -1682,6 +1683,9 @@ namespace ts { 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; @@ -1690,6 +1694,9 @@ namespace ts { const extendedDirname = getDirectoryPath(extendedConfigPath); 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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 00b3abe113ee4..c4fe490091db0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2338,6 +2338,7 @@ namespace ts { export interface JsonSourceFile extends SourceFile { jsonObject?: ObjectLiteralExpression; + extendedSourceFiles?: string[]; } export interface ScriptReferenceHost { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 9e10b7c4fdfcb..c252b9b4f5987 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -731,7 +731,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]); @@ -988,7 +988,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 = `{ @@ -999,7 +999,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); }); @@ -1562,7 +1562,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", () => { @@ -1620,7 +1620,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]); @@ -1651,7 +1651,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, @@ -1684,7 +1684,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, @@ -1765,11 +1765,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); @@ -1804,13 +1804,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]); }); @@ -1834,7 +1834,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 }]; @@ -1843,7 +1843,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( @@ -1853,7 +1853,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]; @@ -1868,7 +1868,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); @@ -2483,7 +2483,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]); @@ -2541,8 +2541,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({ @@ -2552,7 +2552,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({ @@ -2572,8 +2572,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); @@ -2629,13 +2629,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", () => { @@ -2690,7 +2690,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]); }); }); @@ -2761,7 +2761,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]); @@ -2770,7 +2770,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]); @@ -2779,7 +2779,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]); }); }); @@ -2964,7 +2964,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]); }); }); @@ -4040,4 +4040,4 @@ namespace ts.projectSystem { } }); }); -} \ No newline at end of file +} diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index af95874a32cc8..84618c5e524fc 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", () => { @@ -1185,4 +1185,4 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [f1.path]); }); }); -} \ No newline at end of file +} diff --git a/src/server/project.ts b/src/server/project.ts index 56c394379a0b9..3095b02c41501 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -362,7 +362,7 @@ namespace ts.server { return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); } - getFileNames(excludeFilesFromExternalLibraries?: boolean) { + getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { if (!this.program) { return []; } @@ -385,6 +385,17 @@ 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; } diff --git a/src/server/session.ts b/src/server/session.ts index 50adfcb945b40..1de3c3a208b4c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -812,15 +812,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; } @@ -1587,7 +1587,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 ffc09f29ccdea..da84369e8ed83 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/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"]) From 7cf93f94d45fd6838c3120f9bc2dbbe6c98e0661 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 23 May 2017 15:38:03 -0700 Subject: [PATCH 11/15] Report config file errors as part of syntactic diagnostic on the file --- src/harness/unittests/projectErrors.ts | 23 ++++- .../unittests/tsserverProjectSystem.ts | 15 ++- src/server/editorServices.ts | 2 +- src/server/project.ts | 36 ++++++- src/server/protocol.ts | 6 +- src/server/session.ts | 98 +++++++++++++------ 6 files changed, 127 insertions(+), 53 deletions(-) diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 4548e063601bc..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,9 +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, [ + 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]); @@ -134,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, []); } }); @@ -163,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]); @@ -171,10 +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, [ + 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/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index c252b9b4f5987..7836988e9d9f6 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4005,11 +4005,11 @@ namespace ts.projectSystem { checkNumberOfProjects(projectService, { configuredProjects: 1 }); const projectName = projectService.configuredProjects[0].getProjectName(); - const diags = session.executeCommand({ + const diags = session.executeCommand({ type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, + command: server.CommandNames.SemanticDiagnosticsSync, seq: 2, - arguments: { projectFileName: projectName } + arguments: { file: configFile.path, projectFileName: projectName, includeLinePosition: true } }).response; assert.isTrue(diags.length === 2); @@ -4017,18 +4017,18 @@ namespace ts.projectSystem { host.reloadFS([file, configFile]); host.triggerFileWatcherCallback(configFile.path); - const diagsAfterEdit = session.executeCommand({ + const diagsAfterEdit = session.executeCommand({ type: "request", - command: server.CommandNames.CompilerOptionsDiagnosticsFull, + command: server.CommandNames.SemanticDiagnosticsSync, seq: 2, - arguments: { projectFileName: projectName } + 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.DiagnosticWithLinePositionAndFileName, afterEditDiag: server.protocol.DiagnosticWithLinePositionAndFileName) { + 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); @@ -4036,7 +4036,6 @@ namespace ts.projectSystem { 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); - assert.equal(beforeEditDiag.fileName, afterEditDiag.fileName); } }); }); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 14558a7b704cb..e8289bf248fde 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1048,7 +1048,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 3095b02c41501..0663d253188f7 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -221,7 +221,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; } @@ -399,6 +406,25 @@ namespace ts.server { 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 []; @@ -655,7 +681,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; @@ -677,14 +703,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() }; } } @@ -1114,4 +1140,4 @@ namespace ts.server { this.typeAcquisition = newTypeAcquisition; } } -} \ No newline at end of file +} diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 3bce6d7c78f46..cab1601fb4c81 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -358,10 +358,6 @@ namespace ts.server.protocol { code: number; } - export interface DiagnosticWithLinePositionAndFileName extends DiagnosticWithLinePosition { - fileName: string; - } - /** * Response message for "projectInfo" request */ @@ -969,7 +965,7 @@ namespace ts.server.protocol { /** * List of errors in project */ - projectErrors: DiagnosticWithLinePositionAndFileName[]; + projectErrors: DiagnosticWithLinePosition[]; } /** diff --git a/src/server/session.ts b/src/server/session.ts index 1de3c3a208b4c..af57e0dfd4baf 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -88,16 +88,16 @@ namespace ts.server { return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 }; } - function formatConfigFileDiag(diag: ts.Diagnostic): protocol.DiagnosticWithFileName { - return { - start: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start)), - end: diag.file && convertToILineInfo(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length)), - text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"), - code: diag.code, - category: DiagnosticCategory[diag.category].toLowerCase(), - fileName: diag.file && diag.file.fileName, - source: diag.source - }; + 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 { @@ -225,17 +225,6 @@ namespace ts.server { return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`; } - /** - * Should Remap project Files with ts diagnostics if - * - there are project errors - * - options contain configFile - so we can remove it from options before serializing - * @param p project files with ts diagnostics - */ - function shouldRemapProjectFilesWithTSDiagnostics(p: ProjectFilesWithTSDiagnostics) { - return (p.projectErrors && !!p.projectErrors.length) || - (p.info && !!p.info.options.configFile); - } - /** * Get the compiler options without configFile key * @param options @@ -491,7 +480,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", @@ -624,24 +613,57 @@ namespace ts.server { return projectFileName && this.projectService.findProject(projectFileName); } - private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) { + private getConfigFileAndProject(args: protocol.FileRequestArgs) { const project = this.getProject(args.projectFileName); - return this.convertToCompilerOptionsDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics()); + 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 convertToCompilerOptionsDiagnosticsWithLinePosition(diagnostics: Diagnostic[]) { - return diagnostics.map(d => { + 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)), - fileName: d.file && d.file.fileName + endLocation: d.file && convertToILineInfo(getLineAndCharacterOfPosition(d.file, d.start + d.length)) }); } + private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) { + const project = this.getProject(args.projectFileName); + // 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) { return diagnostics.map(d => { message: flattenDiagnosticMessageText(d.messageText, this.host.newLine), @@ -765,10 +787,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); } @@ -1662,15 +1694,21 @@ namespace ts.server { }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects); - if (!result.some(shouldRemapProjectFilesWithTSDiagnostics)) { + // Remapping of the result is needed if + // - there are project errors + // - options contain configFile - need to remove it from options before serializing + const shouldRemapProjectFilesWithTSDiagnostics = (p: ProjectFilesWithTSDiagnostics) => (p.projectErrors && !!p.projectErrors.length) || (p.info && !!p.info.options.configFile); + if (!some(result, shouldRemapProjectFilesWithTSDiagnostics)) { return this.requiredResponse(result); } const converted = map(result, p => { if (shouldRemapProjectFilesWithTSDiagnostics(p)) { + // Map the project errors const projectErrors = p.projectErrors && p.projectErrors.length ? - this.convertToCompilerOptionsDiagnosticsWithLinePosition(p.projectErrors) : + this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) : p.projectErrors; + // Remove the configFile in the options before serializing const info = p.info && !!p.info.options.configFile ? { projectName: p.info.projectName, From 55018920ac39eb8243a7968f9319ec2d32611b9b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 24 May 2017 12:56:44 -0700 Subject: [PATCH 12/15] Fix the build break of typings installer --- src/compiler/factory.ts | 8 -------- src/compiler/utilities.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 901c516cc7791..1f8aefea786a6 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2256,14 +2256,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. */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 3b6c46ba5fd8c..d95965d51ab46 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -321,6 +321,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. From 16fd947ac32ea9eae3c3bf459107c5908c9e617d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 26 May 2017 15:37:41 -0700 Subject: [PATCH 13/15] Reorganized code to keep createBundle and updateBundle in factory.ts --- src/compiler/emitter.ts | 64 ++++++++++++++++++++++++++++++++ src/compiler/factory.ts | 13 +++++++ src/compiler/utilities.ts | 78 +-------------------------------------- 3 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 1d2f75fcd6066..b7da48b5d0244 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) + ".d.ts" : ""; + 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): string { + if (options.jsx === JsxEmit.Preserve) { + if (isSourceFileJavaScript(sourceFile)) { + if (fileExtensionIs(sourceFile.fileName, ".jsx")) { + return ".jsx"; + } + } + else if (sourceFile.languageVariant === LanguageVariant.JSX) { + // TypeScript source file preserving JSX syntax + return ".jsx"; + } + } + return ".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 1f8aefea786a6..302e6d4154d4e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2119,6 +2119,19 @@ namespace ts { : node; } + export function createBundle(sourceFiles: SourceFile[]) { + const node = createNode(SyntaxKind.Bundle); + node.sourceFiles = sourceFiles; + return node; + } + + export function updateBundle(node: Bundle, sourceFiles: SourceFile[]) { + if (node.sourceFiles !== sourceFiles) { + return createBundle(sourceFiles); + } + return node; + } + // Compound nodes export function createComma(left: Expression, right: Expression) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d95965d51ab46..6d43092f8faf0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2129,27 +2129,7 @@ namespace ts { || positionIsSynthesized(node.end); } - export function createBundle(sourceFiles: SourceFile[]) { - const node = createNode(SyntaxKind.Bundle); - node.sourceFiles = sourceFiles; - return node; - } - - export function updateBundle(node: Bundle, sourceFiles: SourceFile[]) { - if (node.sourceFiles !== sourceFiles) { - return createBundle(sourceFiles); - } - return node; - } - - 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; } @@ -2664,62 +2644,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) + ".d.ts" : ""; - 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): string { - if (options.jsx === JsxEmit.Preserve) { - if (isSourceFileJavaScript(sourceFile)) { - if (fileExtensionIs(sourceFile.fileName, ".jsx")) { - return ".jsx"; - } - } - else if (sourceFile.languageVariant === LanguageVariant.JSX) { - // TypeScript source file preserving JSX syntax - return ".jsx"; - } - } - return ".js"; - } - export function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) { let sourceFilePath = getNormalizedAbsolutePath(sourceFile.fileName, host.getCurrentDirectory()); const commonSourceDirectory = host.getCommonSourceDirectory(); From 7bd9e092aca28d40cb30fc5f66f2f7390280db0b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 26 May 2017 17:06:15 -0700 Subject: [PATCH 14/15] Make configFile on compiler options as non enumerable --- src/compiler/commandLineParser.ts | 10 +++- src/compiler/types.ts | 2 +- src/harness/compilerRunner.ts | 2 +- src/harness/harness.ts | 2 +- src/harness/rwcRunner.ts | 3 +- .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/configurationExtension.ts | 7 +-- .../convertCompilerOptionsFromJson.ts | 2 +- src/server/project.ts | 2 +- src/server/session.ts | 52 ++++--------------- src/services/services.ts | 2 +- src/services/transpile.ts | 2 +- src/services/utilities.ts | 12 +++-- 13 files changed, 38 insertions(+), 62 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 6e2b1e97df7a2..675df9ed7dccd 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1380,6 +1380,13 @@ namespace ts { 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 @@ -1406,8 +1413,7 @@ namespace ts { const { raw } = parsedConfig; const options = extend(existingOptions, parsedConfig.options || {}); options.configFilePath = configFileName; - options.configFile = sourceFile; - + setConfigFileInOptions(options, sourceFile); const { fileNames, wildcardDirectories } = getFileNames(); return { options, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fe98196cd1b73..9d8991fe3d889 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3449,7 +3449,7 @@ namespace ts { charset?: string; checkJs?: boolean; /* @internal */ configFilePath?: string; - /* @internal */ configFile?: JsonSourceFile; + /* @internal */ readonly configFile?: JsonSourceFile; declaration?: boolean; declarationDir?: string; /* @internal */ diagnostics?: boolean; diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index 10f8ec66f3efd..9d697f8ffe74a 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -82,7 +82,7 @@ class CompilerBaselineRunner extends RunnerBase { 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 { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 8630ea52a348c..7926ca4510016 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1123,7 +1123,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; diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 6379f829d77b5..128acb1005d68 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -87,6 +87,7 @@ namespace RWC { 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 @@ -262,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/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index 1b87fc848b5e7..f5b54063df798 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 4429e9321e436..2d50d2cb2af97 100644 --- a/src/harness/unittests/configurationExtension.ts +++ b/src/harness/unittests/configurationExtension.ts @@ -131,14 +131,15 @@ namespace ts { it(name, () => { const parsed = getParseCommandLine(entry); assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n")); - assert.deepEqual(parsed.options, ts.extend(expected, { configFile: undefined })); + 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")); - assert.deepEqual(parsed.options, ts.extend(expected, { configFile: jsonSourceFile })); + assert.deepEqual(parsed.options, expected); + assert.equal(parsed.options.configFile, jsonSourceFile); assert.deepEqual(parsed.fileNames, expectedFiles); }); } @@ -208,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 00c56a73da531..d1bef4480bcb6 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -34,11 +34,11 @@ namespace ts { const host: ParseConfigHost = new Utils.MockParseConfigHost("/apath/", true, []); const { options: actualCompilerOptions, errors: actualParseErrors } = parseJsonSourceFileConfigFileContent(result, host, "/apath/", /*existingOptions*/ undefined, configFileName); expectedResult.compilerOptions["configFilePath"] = configFileName; - expectedResult.compilerOptions.configFile = result; 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; diff --git a/src/server/project.ts b/src/server/project.ts index 11a8af589374c..d7e1bb49a6cd1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -789,7 +789,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/session.ts b/src/server/session.ts index c6dd342c94ffe..feb8f7fd528ef 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -132,20 +132,6 @@ namespace ts.server { return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`; } - /** - * Get the compiler options without configFile key - * @param options - */ - function getCompilerOptionsWithoutConfigFile(options: CompilerOptions) { - const result: CompilerOptions = {}; - for (const option in options) { - if (option !== "configFile") { - result[option] = options[option]; - } - } - return result; - } - /** * Allows to schedule next step in multistep operation */ @@ -1670,39 +1656,19 @@ namespace ts.server { }, [CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => { const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects); - // Remapping of the result is needed if - // - there are project errors - // - options contain configFile - need to remove it from options before serializing - const shouldRemapProjectFilesWithTSDiagnostics = (p: ProjectFilesWithTSDiagnostics) => (p.projectErrors && !!p.projectErrors.length) || (p.info && !!p.info.options.configFile); - if (!some(result, shouldRemapProjectFilesWithTSDiagnostics)) { + if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) { return this.requiredResponse(result); } const converted = map(result, p => { - if (shouldRemapProjectFilesWithTSDiagnostics(p)) { - // Map the project errors - const projectErrors = p.projectErrors && p.projectErrors.length ? - this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) : - p.projectErrors; - - // Remove the configFile in the options before serializing - const info = p.info && !!p.info.options.configFile ? - { - projectName: p.info.projectName, - isInferred: p.info.isInferred, - version: p.info.version, - options: getCompilerOptionsWithoutConfigFile(p.info.options), - languageServiceDisabled: p.info.languageServiceDisabled - } : p.info; - - return { - info, - changes: p.changes, - files: p.files, - projectErrors - }; + if (!p.projectErrors || p.projectErrors.length === 0) { + return p; } - - return p; + return { + info: p.info, + changes: p.changes, + files: p.files, + projectErrors: this.convertToDiagnosticsWithLinePosition(p.projectErrors, /*scriptInfo*/ undefined) + }; }); return this.requiredResponse(converted); }, diff --git a/src/services/services.ts b/src/services/services.ts index 7d5541db12c78..34f1a4bfaca05 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1292,7 +1292,7 @@ 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 - if (!compareDataObjects(currentOptions, newOptions, "configFile")) { + if (!compareDataObjects(currentOptions, newOptions)) { return false; } 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/utilities.ts b/src/services/utilities.ts index 34b729142a311..8ac429a0af597 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -997,16 +997,18 @@ namespace ts { return false; } - export function compareDataObjects(dst: any, src: any, ignoreKey?: string): boolean { + 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; } for (const e in dst) { - if (ignoreKey && ignoreKey === e) { - continue; - } - if (typeof dst[e] === "object") { if (!compareDataObjects(dst[e], src[e])) { return false; From 7db76edbae807fd6758e911d956a404011c24333 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 26 May 2017 17:06:15 -0700 Subject: [PATCH 15/15] Always return empty object when converting the json sourcefile into actual json object, allowing to continue compilation even if there are errors in the tsconfig files --- src/compiler/commandLineParser.ts | 7 ++---- src/compiler/tsc.ts | 12 +++------ src/harness/projectsRunner.ts | 9 +------ src/harness/unittests/tsconfigParsing.ts | 2 +- src/services/jsTyping.ts | 31 ++++++++++-------------- src/services/shims.ts | 13 +--------- 6 files changed, 21 insertions(+), 53 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 675df9ed7dccd..14c7756c93393 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -874,7 +874,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); } @@ -1011,10 +1011,7 @@ namespace ts { knownRootOptions: Map | undefined, jsonConversionNotifier: JsonConversionNotifier | undefined): any { if (!sourceFile.jsonObject) { - if (sourceFile.endOfFileToken) { - return {}; - } - return undefined; + return {}; } return convertObjectLiteralExpressionToJson(sourceFile.jsonObject, knownRootOptions, diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index cb80d15176449..f78e3cc2405e3 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -225,17 +225,11 @@ namespace ts { const result = parseJsonText(configFileName, cachedConfigFileText); reportDiagnostics(result.parseDiagnostics, /* compilerHost */ undefined); - if (!result.endOfFileToken) { - sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; - } + const cwd = sys.getCurrentDirectory(); const configParseResult = parseJsonSourceFileConfigFileContent(result, 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; - } + 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/harness/projectsRunner.ts b/src/harness/projectsRunner.ts index 8f4c0954c7928..dc4b9685dd559 100644 --- a/src/harness/projectsRunner.ts +++ b/src/harness/projectsRunner.ts @@ -222,16 +222,9 @@ class ProjectRunner extends RunnerBase { readFile }; const configParseResult = ts.parseJsonSourceFileConfigFileContent(result, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions); - if (configParseResult.errors.length > 0) { - return { - configFileSourceFiles, - moduleKind, - errors: result.parseDiagnostics.concat(configParseResult.errors) - }; - } inputFiles = configParseResult.fileNames; compilerOptions = configParseResult.options; - errors = result.parseDiagnostics; + errors = result.parseDiagnostics.concat(configParseResult.errors); } const projectCompilerResult = compileProjectFiles(moduleKind, configFileSourceFiles, () => inputFiles, getSourceFileText, writeFile, compilerOptions); diff --git a/src/harness/unittests/tsconfigParsing.ts b/src/harness/unittests/tsconfigParsing.ts index 98ad67a8bb3ff..8d56360bba50b 100644 --- a/src/harness/unittests/tsconfigParsing.ts +++ b/src/harness/unittests/tsconfigParsing.ts @@ -10,7 +10,7 @@ namespace ts { 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); } diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index f601c9604de24..414c78feb7358 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/shims.ts b/src/services/shims.ts index 32db56fa0d9bc..91c9d5d598fc8 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1110,17 +1110,6 @@ namespace ts { const text = sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()); const result = parseJsonText(fileName, text); - - if (!result.endOfFileToken) { - return { - options: {}, - typeAcquisition: {}, - files: [], - raw: {}, - errors: realizeDiagnostics(result.parseDiagnostics, "\r\n") - }; - } - const normalizedFileName = normalizeSlashes(fileName); const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); @@ -1248,4 +1237,4 @@ namespace TypeScript.Services { // TODO: it should be moved into a namespace though. /* @internal */ -const toolsVersion = "2.4"; \ No newline at end of file +const toolsVersion = "2.4";