diff --git a/Jakefile.js b/Jakefile.js index 2ffdfc37807..a6570c3c710 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -150,7 +150,10 @@ var harnessSources = harnessCoreSources.concat([ "reuseProgramStructure.ts", "cachingInServerLSHost.ts", "moduleResolution.ts", - "tsconfigParsing.ts" + "tsconfigParsing.ts", + "commandLineParsing.ts", + "convertCompilerOptionsFromJson.ts", + "convertTypingOptionsFromJson.ts" ].map(function (f) { return path.join(unittestsDirectory, f); })).concat([ diff --git a/scripts/ior.ts b/scripts/ior.ts index f0c142a266c..eb67e62a275 100644 --- a/scripts/ior.ts +++ b/scripts/ior.ts @@ -76,8 +76,11 @@ module Commands { fs.mkdirSync(directoryPath); } } + function normalizeSlashes(path: string): string { + return path.replace(/\\/g, "/"); + } function transalatePath(outputFolder:string, path: string): string { - return outputFolder + directorySeparator + path.replace(":", ""); + return normalizeSlashes(outputFolder + directorySeparator + path.replace(":", "")); } function fileExists(path: string): boolean { return fs.existsSync(path); @@ -86,7 +89,7 @@ module Commands { var filename = transalatePath(outputFolder, f.path); ensureDirectoriesExist(getDirectoryPath(filename)); console.log("writing filename: " + filename); - fs.writeFile(filename, f.result.contents, (err) => { }); + fs.writeFileSync(filename, f.result.contents); }); console.log("Command: tsc "); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cbda24e6a6e..09672f5c09c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13965,7 +13965,7 @@ namespace ts { } } } - else if (compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { + else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { // The function has a return type, but the return statement doesn't have an expression. error(node, Diagnostics.Not_all_code_paths_return_a_value); } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 3b7f423bf86..34a84ac793f 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -58,12 +58,11 @@ namespace ts { }, paramType: Diagnostics.KIND, description: Diagnostics.Specify_JSX_code_generation_Colon_preserve_or_react, - error: Diagnostics.Argument_for_jsx_must_be_preserve_or_react }, { name: "reactNamespace", type: "string", - description: Diagnostics.Specifies_the_object_invoked_for_createElement_and_spread_when_targeting_react_JSX_emit + description: Diagnostics.Specify_the_object_invoked_for_createElement_and_spread_when_targeting_react_JSX_emit }, { name: "listFiles", @@ -77,7 +76,7 @@ namespace ts { name: "mapRoot", type: "string", isFilePath: true, - description: Diagnostics.Specifies_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations, paramType: Diagnostics.LOCATION, }, { @@ -94,7 +93,6 @@ namespace ts { }, description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015, paramType: Diagnostics.KIND, - error: Diagnostics.Argument_for_module_option_must_be_commonjs_amd_system_umd_es2015_or_none }, { name: "newLine", @@ -102,9 +100,8 @@ namespace ts { "crlf": NewLineKind.CarriageReturnLineFeed, "lf": NewLineKind.LineFeed }, - description: Diagnostics.Specifies_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, + description: Diagnostics.Specify_the_end_of_line_sequence_to_be_used_when_emitting_files_Colon_CRLF_dos_or_LF_unix, paramType: Diagnostics.NEWLINE, - error: Diagnostics.Argument_for_newLine_option_must_be_CRLF_or_LF }, { name: "noEmit", @@ -186,8 +183,8 @@ namespace ts { name: "rootDir", type: "string", isFilePath: true, - description: Diagnostics.Specifies_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir, paramType: Diagnostics.LOCATION, + description: Diagnostics.Specify_the_root_directory_of_input_files_Use_to_control_the_output_directory_structure_with_outDir, }, { name: "isolatedModules", @@ -202,7 +199,7 @@ namespace ts { name: "sourceRoot", type: "string", isFilePath: true, - description: Diagnostics.Specifies_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations, + description: Diagnostics.Specify_the_location_where_debugger_should_locate_TypeScript_files_instead_of_source_locations, paramType: Diagnostics.LOCATION, }, { @@ -231,9 +228,8 @@ namespace ts { "es6": ScriptTarget.ES6, "es2015": ScriptTarget.ES2015, }, - description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015_experimental, + description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_or_ES2015, paramType: Diagnostics.VERSION, - error: Diagnostics.Argument_for_target_option_must_be_ES3_ES5_or_ES2015 }, { name: "version", @@ -264,8 +260,7 @@ namespace ts { "node": ModuleResolutionKind.NodeJs, "classic": ModuleResolutionKind.Classic, }, - description: Diagnostics.Specifies_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, - error: Diagnostics.Argument_for_moduleResolution_option_must_be_node_or_classic, + description: Diagnostics.Specify_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6, }, { name: "allowUnusedLabels", @@ -309,9 +304,13 @@ namespace ts { // this option can only be specified in tsconfig.json // use type = object to copy the value as-is name: "rootDirs", - type: "object", + type: "list", isTSConfigOnly: true, - isFilePath: true + element: { + name: "rootDirs", + type: "string", + isFilePath: true + } }, { name: "traceModuleResolution", @@ -339,6 +338,30 @@ namespace ts { } ]; + /* @internal */ + export let typingOptionDeclarations: CommandLineOption[] = [ + { + name: "enableAutoDiscovery", + type: "boolean", + }, + { + name: "include", + type: "list", + element: { + name: "include", + type: "string" + } + }, + { + name: "exclude", + type: "list", + element: { + name: "exclude", + type: "string" + } + } + ]; + /* @internal */ export interface OptionNameMap { optionNameMap: Map; @@ -365,6 +388,16 @@ namespace ts { return optionNameMapCache; } + /* @internal */ + export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic { + const namesOfType: string[] = []; + forEachKey(opt.type, key => { + namesOfType.push(` '${key}'`); + }); + + return createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, `--${opt.name}`, namesOfType); + } + export function parseCommandLine(commandLine: string[], readFile?: (path: string) => string): ParsedCommandLine { const options: CompilerOptions = {}; const fileNames: string[] = []; @@ -418,17 +451,15 @@ namespace ts { options[opt.name] = args[i] || ""; i++; break; + case "list": + options[opt.name] = parseListTypeOption(opt, args[i]); + i++; + break; // If not a primitive, the possible types are specified in what is effectively a map of options. default: - let map = >opt.type; - let key = (args[i] || "").toLowerCase(); + options[opt.name] = parseCustomTypeOption(opt, args[i]); i++; - if (hasProperty(map, key)) { - options[opt.name] = map[key]; - } - else { - errors.push(createCompilerDiagnostic((opt).error)); - } + break; } } } @@ -439,6 +470,29 @@ namespace ts { else { fileNames.push(s); } + + function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string) { + const key = (value || "").trim().toLowerCase(); + const map = opt.type; + if (hasProperty(map, key)) { + return map[key]; + } + else { + errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); + } + } + + function parseListTypeOption(opt: CommandLineOptionOfListType, value: string): (string | number)[] { + const values = (value || "").trim().split(","); + switch (opt.element.type) { + case "number": + return ts.map(values, parseInt); + case "string": + return ts.map(values, v => v || ""); + default: + return filter(map(values, v => parseCustomTypeOption(opt.element, v)), v => !!v); + } + } } } @@ -506,7 +560,6 @@ namespace ts { } } - /** * Remove the comments from a json like text. * Comments can be single line comments (starting with # or //) or multiline comments using / * * / @@ -540,21 +593,24 @@ namespace ts { * file to. e.g. outDir */ export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions: CompilerOptions = {}, configFileName?: string): ParsedCommandLine { - const { options: optionsFromJsonConfigFile, errors } = convertCompilerOptionsFromJson(json["compilerOptions"], basePath, configFileName); + const errors: Diagnostic[] = []; + const compilerOptions: CompilerOptions = convertCompilerOptionsFromJson(optionDeclarations, json["compilerOptions"], basePath, errors, configFileName); + const options = extend(existingOptions, compilerOptions); + const typingOptions: TypingOptions = convertTypingOptionsFromJson(typingOptionDeclarations, json["typingOptions"], basePath, errors, configFileName); - const options = extend(existingOptions, optionsFromJsonConfigFile); + const fileNames = getFileNames(errors); return { options, - fileNames: getFileNames(), - typingOptions: getTypingOptions(), + fileNames, + typingOptions, errors }; - function getFileNames(): string[] { + function getFileNames(errors: Diagnostic[]): string[] { let fileNames: string[] = []; if (hasProperty(json, "files")) { - if (json["files"] instanceof Array) { + if (isArray(json["files"])) { fileNames = map(json["files"], s => combinePaths(basePath, s)); } else { @@ -565,7 +621,7 @@ namespace ts { const filesSeen: Map = {}; let exclude: string[] = []; - if (json["exclude"] instanceof Array) { + if (isArray(json["exclude"])) { exclude = json["exclude"]; } else { @@ -612,47 +668,33 @@ namespace ts { } return fileNames; } - - function getTypingOptions(): TypingOptions { - const options: TypingOptions = getBaseFileName(configFileName) === "jsconfig.json" - ? { enableAutoDiscovery: true, include: [], exclude: [] } - : { enableAutoDiscovery: false, include: [], exclude: [] }; - const jsonTypingOptions = json["typingOptions"]; - if (jsonTypingOptions) { - for (const id in jsonTypingOptions) { - if (id === "enableAutoDiscovery") { - if (typeof jsonTypingOptions[id] === "boolean") { - options.enableAutoDiscovery = jsonTypingOptions[id]; - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); - } - } - else if (id === "include") { - options.include = convertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); - } - else if (id === "exclude") { - options.exclude = convertJsonOptionToStringArray(id, jsonTypingOptions[id], errors); - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_typing_option_0, id)); - } - } - } - return options; - } } - export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string, configFileName?: string): { options: CompilerOptions, errors: Diagnostic[] } { - const options: CompilerOptions = {}; - const errors: Diagnostic[] = []; + /* @internal */ + export function convertCompilerOptionsFromJson(optionsDeclarations: CommandLineOption[], jsonOptions: any, + basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions { - if (configFileName && getBaseFileName(configFileName) === "jsconfig.json") { - options.allowJs = true; - } + const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json" ? { allowJs: true } : {}; + convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors); + return options; + } + + /* @internal */ + export function convertTypingOptionsFromJson(optionsDeclarations: CommandLineOption[], jsonOptions: any, + basePath: string, errors: Diagnostic[], configFileName?: string): TypingOptions { + + const options: TypingOptions = getBaseFileName(configFileName) === "jsconfig.json" + ? { enableAutoDiscovery: true, include: [], exclude: [] } + : { enableAutoDiscovery: false, include: [], exclude: [] }; + convertOptionsFromJson(typingOptionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_typing_option_0, errors); + return options; + } + + function convertOptionsFromJson(optionDeclarations: CommandLineOption[], jsonOptions: any, basePath: string, + defaultOptions: T, diagnosticMessage: DiagnosticMessage, errors: Diagnostic[]) { if (!jsonOptions) { - return { options, errors }; + return ; } const optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); @@ -660,69 +702,50 @@ namespace ts { for (const id in jsonOptions) { if (hasProperty(optionNameMap, id)) { const opt = optionNameMap[id]; - const optType = opt.type; - let value = jsonOptions[id]; - const expectedType = typeof optType === "string" ? optType : "string"; - if (typeof value === expectedType) { - if (typeof optType !== "string") { - const key = value.toLowerCase(); - if (hasProperty(optType, key)) { - value = optType[key]; - } - else { - errors.push(createCompilerDiagnostic((opt).error)); - value = 0; - } - } - if (opt.isFilePath) { - switch (typeof value) { - case "string": - value = normalizePath(combinePaths(basePath, value)); - break; - case "object": - // "object" options with 'isFilePath' = true expected to be string arrays - value = convertJsonOptionToStringArray(opt.name, value, errors, (element) => normalizePath(combinePaths(basePath, element))); - break; - } - if (value === "") { - value = "."; - } - } - options[opt.name] = value; - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, id, expectedType)); - } + defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors); } else { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_compiler_option_0, id)); + errors.push(createCompilerDiagnostic(diagnosticMessage, id)); } } - - return { options, errors }; } - function convertJsonOptionToStringArray(optionName: string, optionJson: any, errors: Diagnostic[], func?: (element: string) => string): string[] { - const items: string[] = []; - let invalidOptionType = false; - if (!isArray(optionJson)) { - invalidOptionType = true; + 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); + } + else { + if (opt.isFilePath) { + value = normalizePath(combinePaths(basePath, value)); + if (value === "") { + value = "."; + } + } + } + return value; } else { - for (const element of optionJson) { - if (typeof element === "string") { - const item = func ? func(element) : element; - items.push(item); - } - else { - invalidOptionType = true; - break; - } - } + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, opt.name, expectedType)); } - if (invalidOptionType) { - errors.push(createCompilerDiagnostic(Diagnostics.Option_0_should_have_array_of_strings_as_a_value, optionName)); + } + + function convertJsonOptionOfCustomType(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]) { + const key = value.toLowerCase(); + if (hasProperty(opt.type, key)) { + return opt.type[key]; } - return items; + else { + errors.push(createCompilerDiagnosticForInvalidCustomType(opt)); + } + } + + function convertJsonOptionOfListType(option: CommandLineOptionOfListType, values: any[], basePath: string, errors: Diagnostic[]): any[] { + return filter(map(values, v => convertJsonOption(option.element, v, basePath, errors)), v => !!v); } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f8a7b627a7c..cdbd1a6ebee 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2240,11 +2240,11 @@ "category": "Message", "code": 6002 }, - "Specifies the location where debugger should locate map files instead of generated locations.": { + "Specify the location where debugger should locate map files instead of generated locations.": { "category": "Message", "code": 6003 }, - "Specifies the location where debugger should locate TypeScript files instead of source locations.": { + "Specify the location where debugger should locate TypeScript files instead of source locations.": { "category": "Message", "code": 6004 }, @@ -2276,7 +2276,7 @@ "category": "Message", "code": 6011 }, - "Specify ECMAScript target version: 'ES3' (default), 'ES5', or 'ES2015' (experimental)": { + "Specify ECMAScript target version: 'ES3' (default), 'ES5', or 'ES2015'": { "category": "Message", "code": 6015 }, @@ -2364,14 +2364,10 @@ "category": "Error", "code": 6045 }, - "Argument for '--module' option must be 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'none'.": { + "Argument for '{0}' option must be: {1}": { "category": "Error", "code": 6046 }, - "Argument for '--target' option must be 'ES3', 'ES5', or 'ES2015'.": { - "category": "Error", - "code": 6047 - }, "Locale must be of the form or -. For example '{0}' or '{1}'.": { "category": "Error", "code": 6048 @@ -2408,7 +2404,7 @@ "category": "Message", "code": 6056 }, - "Specifies the root directory of input files. Use to control the output directory structure with --outDir.": { + "Specify the root directory of input files. Use to control the output directory structure with --outDir.": { "category": "Message", "code": 6058 }, @@ -2416,7 +2412,7 @@ "category": "Error", "code": 6059 }, - "Specifies the end of line sequence to be used when emitting files: 'CRLF' (dos) or 'LF' (unix).": { + "Specify the end of line sequence to be used when emitting files: 'CRLF' (dos) or 'LF' (unix).": { "category": "Message", "code": 6060 }, @@ -2424,14 +2420,6 @@ "category": "Message", "code": 6061 }, - "Argument for '--newLine' option must be 'CRLF' or 'LF'.": { - "category": "Error", - "code": 6062 - }, - "Argument for '--moduleResolution' option must be 'node' or 'classic'.": { - "category": "Error", - "code": 6063 - }, "Option '{0}' can only be specified in 'tsconfig.json' file.": { "category": "Error", "code": 6064 @@ -2448,7 +2436,7 @@ "category": "Message", "code": 6068 }, - "Specifies module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).": { + "Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).": { "category": "Message", "code": 6069 }, @@ -2492,10 +2480,6 @@ "category": "Message", "code": 6080 }, - "Argument for '--jsx' must be 'preserve' or 'react'.": { - "category": "Message", - "code": 6081 - }, "Only 'amd' and 'system' modules are supported alongside --{0}.": { "category": "Error", "code": 6082 @@ -2504,7 +2488,7 @@ "category": "Message", "code": 6083 }, - "Specifies the object invoked for createElement and __spread when targeting 'react' JSX emit": { + "Specify the object invoked for createElement and __spread when targeting 'react' JSX emit": { "category": "Message", "code": 6084 }, diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 14f0e5bdf94..e5498d24576 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -604,7 +604,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge write(`//# sourceMappingURL=${sourceMappingURL}`); } - writeEmittedFiles(writer.getText(), jsFilePath, sourceMapFilePath, /*writeByteOrderMark*/ compilerOptions.emitBOM); + writeEmittedFiles(writer.getText(), jsFilePath, sourceMapFilePath, /*writeByteOrderMark*/ compilerOptions.emitBOM, sourceFiles); // reset the state sourceMap.reset(); @@ -748,16 +748,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge } /** Write emitted output to disk */ - function writeEmittedFiles(emitOutput: string, jsFilePath: string, sourceMapFilePath: string, writeByteOrderMark: boolean) { + function writeEmittedFiles(emitOutput: string, jsFilePath: string, sourceMapFilePath: string, writeByteOrderMark: boolean, sourceFiles: SourceFile[]) { if (compilerOptions.sourceMap && !compilerOptions.inlineSourceMap) { - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), /*writeByteOrderMark*/ false); + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), /*writeByteOrderMark*/ false, sourceFiles); } if (sourceMapDataList) { sourceMapDataList.push(sourceMap.getSourceMapData()); } - writeFile(host, emitterDiagnostics, jsFilePath, emitOutput, writeByteOrderMark); + writeFile(host, emitterDiagnostics, jsFilePath, emitOutput, writeByteOrderMark, sourceFiles); } // Create a temporary variable with a unique unused name. diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8f2f95fa4fe..2da1fffdb8b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -933,7 +933,7 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFiles: program.getSourceFiles, writeFile: writeFileCallback || ( - (fileName, data, writeByteOrderMark, onError) => host.writeFile(fileName, data, writeByteOrderMark, onError)), + (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, }; } @@ -1773,24 +1773,7 @@ namespace ts { } } - if (options.noEmit) { - if (options.out) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", "out")); - } - - if (options.outFile) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", "outFile")); - } - - if (options.outDir) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", "outDir")); - } - - if (options.declaration) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noEmit", "declaration")); - } - } - else if (options.allowJs && options.declaration) { + if (!options.noEmit && options.allowJs && options.declaration) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration")); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 73dc959e163..95a1e32c401 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1584,7 +1584,7 @@ namespace ts { } export interface WriteFileCallback { - (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void; + (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: SourceFile[]): void; } export class OperationCanceledException { } @@ -2381,6 +2381,8 @@ namespace ts { export type PathSubstitutions = Map; export type TsConfigOnlyOptions = RootPaths | PathSubstitutions; + export type CompilerOptionsValue = string | number | boolean | (string | number)[] | TsConfigOnlyOptions; + export interface CompilerOptions { allowNonTsExtensions?: boolean; charset?: string; @@ -2438,6 +2440,7 @@ namespace ts { allowJs?: boolean; noImplicitUseStrict?: boolean; disableSizeLimit?: boolean; + lib?: string[]; /* @internal */ stripInternal?: boolean; // Skip checking lib.d.ts to help speed up tests. @@ -2445,7 +2448,9 @@ namespace ts { // Do not perform validation of output file name in transpile scenarios /* @internal */ suppressOutputPathCheck?: boolean; - [option: string]: string | number | boolean | TsConfigOnlyOptions; + list?: string[]; + + [option: string]: CompilerOptionsValue; } export interface TypingOptions { @@ -2530,7 +2535,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionBase { name: string; - type: "string" | "number" | "boolean" | "object" | Map; // a value of a primitive type, or an object literal mapping named values to actual values + type: "string" | "number" | "boolean" | "object" | "list" | Map; // a value of a primitive type, or an object literal mapping named values to actual values isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does @@ -2546,8 +2551,7 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfCustomType extends CommandLineOptionBase { - type: Map; // an object literal mapping named values to actual values - error: DiagnosticMessage; // The error given when the argument does not fit a customized 'type' + type: Map; // an object literal mapping named values to actual values } /* @internal */ @@ -2556,7 +2560,13 @@ namespace ts { } /* @internal */ - export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption; + export interface CommandLineOptionOfListType extends CommandLineOptionBase { + type: "list"; + element: CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType; + } + + /* @internal */ + export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption | CommandLineOptionOfListType; /* @internal */ export const enum CharacterCodes { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7974fb66cd6..68db246cb9a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2110,10 +2110,10 @@ namespace ts { return combinePaths(newDirPath, sourceFilePath); } - export function writeFile(host: EmitHost, diagnostics: DiagnosticCollection, fileName: string, data: string, writeByteOrderMark: boolean) { + export function writeFile(host: EmitHost, diagnostics: DiagnosticCollection, fileName: string, data: string, writeByteOrderMark: boolean, sourceFiles?: SourceFile[]) { host.writeFile(fileName, data, writeByteOrderMark, hostErrorMessage => { diagnostics.add(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_Colon_1, fileName, hostErrorMessage)); - }); + }, sourceFiles); } export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 4020bcd821b..ddaca642094 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -876,7 +876,7 @@ namespace Harness { useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getNewLine: () => newLine, fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined, - readFile: (fileName: string): string => { throw new Error("NotYetImplemented"); } + readFile: (fileName: string): string => { return Harness.IO.readFile(fileName); } }; } diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index ce570a7d6ad..11ed5e7a680 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -31,22 +31,20 @@ namespace RWC { let otherFiles: Harness.Compiler.TestFile[] = []; let compilerResult: Harness.Compiler.CompilerResult; let compilerOptions: ts.CompilerOptions; - let baselineOpts: Harness.Baseline.BaselineOptions = { + const baselineOpts: Harness.Baseline.BaselineOptions = { Subfolder: "rwc", Baselinefolder: "internal/baselines" }; - let baseName = /(.*)\/(.*).json/.exec(ts.normalizeSlashes(jsonPath))[2]; + const baseName = /(.*)\/(.*).json/.exec(ts.normalizeSlashes(jsonPath))[2]; let currentDirectory: string; let useCustomLibraryFile: boolean; after(() => { // Mocha holds onto the closure environment of the describe callback even after the test is done. // Therefore we have to clean out large objects after the test is done. - inputFiles = undefined; - otherFiles = undefined; + inputFiles = []; + otherFiles = []; compilerResult = undefined; compilerOptions = undefined; - baselineOpts = undefined; - baseName = undefined; currentDirectory = undefined; // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6f8006986ed..8c3429ac698 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1353,6 +1353,7 @@ namespace ts.server { else { const defaultOpts = ts.getDefaultCompilerOptions(); defaultOpts.allowNonTsExtensions = true; + defaultOpts.allowJs = true; this.setCompilerOptions(defaultOpts); } this.languageService = ts.createLanguageService(this.host, this.documentRegistry); diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f2e77fa69d3..effaa53bc10 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -3,6 +3,12 @@ /* @internal */ namespace ts.NavigationBar { export function getNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): ts.NavigationBarItem[] { + // TODO: Handle JS files differently in 'navbar' calls for now, but ideally we should unify + // the 'navbar' and 'navto' logic for TypeScript and JavaScript. + if (isSourceFileJavaScript(sourceFile)) { + return getJsNavigationBarItems(sourceFile, compilerOptions); + } + // If the source file has any child items, then it included in the tree // and takes lexical ownership of all other top-level items. let hasGlobalNode = false; @@ -130,7 +136,7 @@ namespace ts.NavigationBar { return topLevelNodes; } - + function sortNodes(nodes: Node[]): Node[] { return nodes.slice(0).sort((n1: Declaration, n2: Declaration) => { if (n1.name && n2.name) { @@ -147,7 +153,7 @@ namespace ts.NavigationBar { } }); } - + function addTopLevelNodes(nodes: Node[], topLevelNodes: Node[]): void { nodes = sortNodes(nodes); @@ -178,8 +184,8 @@ namespace ts.NavigationBar { function isTopLevelFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration) { if (functionDeclaration.kind === SyntaxKind.FunctionDeclaration) { - // A function declaration is 'top level' if it contains any function declarations - // within it. + // A function declaration is 'top level' if it contains any function declarations + // within it. if (functionDeclaration.body && functionDeclaration.body.kind === SyntaxKind.Block) { // Proper function declarations can only have identifier names if (forEach((functionDeclaration.body).statements, @@ -198,7 +204,7 @@ namespace ts.NavigationBar { return false; } - + function getItemsWorker(nodes: Node[], createItem: (n: Node) => ts.NavigationBarItem): ts.NavigationBarItem[] { let items: ts.NavigationBarItem[] = []; @@ -395,19 +401,19 @@ namespace ts.NavigationBar { let result: string[] = []; result.push(moduleDeclaration.name.text); - + while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { moduleDeclaration = moduleDeclaration.body; result.push(moduleDeclaration.name.text); - } + } return result.join("."); } function createModuleItem(node: ModuleDeclaration): NavigationBarItem { let moduleName = getModuleName(node); - + let childItems = getItemsWorker(getChildNodes((getInnermostModule(node).body).statements), createChildItem); return getNavigationBarItem(moduleName, @@ -534,4 +540,206 @@ namespace ts.NavigationBar { return getTextOfNodeFromSourceText(sourceFile.text, node); } } -} \ No newline at end of file + + export function getJsNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): NavigationBarItem[] { + const anonFnText = ""; + const anonClassText = ""; + let indent = 0; + + let rootName = isExternalModule(sourceFile) ? + "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName)))) + "\"" + : ""; + + let sourceFileItem = getNavBarItem(rootName, ScriptElementKind.moduleElement, [getNodeSpan(sourceFile)]); + let topItem = sourceFileItem; + + // Walk the whole file, because we want to also find function expressions - which may be in variable initializer, + // call arguments, expressions, etc... + forEachChild(sourceFile, visitNode); + + function visitNode(node: Node) { + const newItem = createNavBarItem(node); + + if (newItem) { + topItem.childItems.push(newItem); + } + + // Add a level if traversing into a container + if (newItem && (isFunctionLike(node) || isClassLike(node))) { + const lastTop = topItem; + indent++; + topItem = newItem; + forEachChild(node, visitNode); + topItem = lastTop; + indent--; + + // If the last item added was an anonymous function expression, and it had no children, discard it. + if (newItem && newItem.text === anonFnText && newItem.childItems.length === 0) { + topItem.childItems.pop(); + } + } + else { + forEachChild(node, visitNode); + } + } + + function createNavBarItem(node: Node) : NavigationBarItem { + switch (node.kind) { + case SyntaxKind.VariableDeclaration: + // Only add to the navbar if at the top-level of the file + // Note: "const" and "let" are also SyntaxKind.VariableDeclarations + if(node.parent/*VariableDeclarationList*/.parent/*VariableStatement*/ + .parent/*SourceFile*/.kind !== SyntaxKind.SourceFile) { + return undefined; + } + // If it is initialized with a function expression, handle it when we reach the function expression node + const varDecl = node as VariableDeclaration; + if (varDecl.initializer && (varDecl.initializer.kind === SyntaxKind.FunctionExpression || + varDecl.initializer.kind === SyntaxKind.ArrowFunction || + varDecl.initializer.kind === SyntaxKind.ClassExpression)) { + return undefined; + } + // Fall through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // "export default function().." looks just like a regular function/class declaration, except with the 'default' flag + const name = node.flags && (node.flags & NodeFlags.Default) && !(node as (Declaration)).name ? "default" : + node.kind === SyntaxKind.Constructor ? "constructor" : + declarationNameToString((node as (Declaration)).name); + return getNavBarItem(name, getScriptKindForElementKind(node.kind), [getNodeSpan(node)]); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getDefineModuleItem(node) || getFunctionOrClassExpressionItem(node); + case SyntaxKind.MethodDeclaration: + const methodDecl = node as MethodDeclaration; + return getNavBarItem(declarationNameToString(methodDecl.name), + ScriptElementKind.memberFunctionElement, + [getNodeSpan(node)]); + case SyntaxKind.ExportAssignment: + // e.g. "export default " + return getNavBarItem("default", ScriptElementKind.variableElement, [getNodeSpan(node)]); + case SyntaxKind.ImportClause: // e.g. 'def' in: import def from 'mod' (in ImportDeclaration) + if (!(node as ImportClause).name) { + // No default import (this node is still a parent of named & namespace imports, which are handled below) + return undefined; + } + // fall through + case SyntaxKind.ImportSpecifier: // e.g. 'id' in: import {id} from 'mod' (in NamedImports, in ImportClause) + case SyntaxKind.NamespaceImport: // e.g. '* as ns' in: import * as ns from 'mod' (in ImportClause) + case SyntaxKind.ExportSpecifier: // e.g. 'a' or 'b' in: export {a, foo as b} from 'mod' + // Export specifiers are only interesting if they are reexports from another module, or renamed, else they are already globals + if (node.kind === SyntaxKind.ExportSpecifier) { + if (!(node.parent.parent as ExportDeclaration).moduleSpecifier && !(node as ExportSpecifier).propertyName) { + return undefined; + } + } + const decl = node as (ImportSpecifier | ImportClause | NamespaceImport | ExportSpecifier); + if (!decl.name) { + return undefined; + } + const declName = declarationNameToString(decl.name); + return getNavBarItem(declName, ScriptElementKind.constElement, [getNodeSpan(node)]); + default: + return undefined; + } + } + + function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { + return { + text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false + } + } + + function getDefineModuleItem(node: Node): NavigationBarItem { + if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { + return undefined; + } + + // No match if this is not a call expression to an identifier named 'define' + if (node.parent.kind !== SyntaxKind.CallExpression) { + return undefined; + } + const callExpr = node.parent as CallExpression; + if (callExpr.expression.kind !== SyntaxKind.Identifier || callExpr.expression.getText() !== 'define') { + return undefined; + } + + // Return a module of either the given text in the first argument, or of the source file path + let defaultName = node.getSourceFile().fileName; + if (callExpr.arguments[0].kind === SyntaxKind.StringLiteral) { + defaultName = ((callExpr.arguments[0]) as StringLiteral).text; + } + return getNavBarItem(defaultName, ScriptElementKind.moduleElement, [getNodeSpan(node.parent)]); + } + + function getFunctionOrClassExpressionItem(node: Node): NavigationBarItem { + if (node.kind !== SyntaxKind.FunctionExpression && + node.kind !== SyntaxKind.ArrowFunction && + node.kind !== SyntaxKind.ClassExpression) { + return undefined; + } + + const fnExpr = node as FunctionExpression | ArrowFunction | ClassExpression; + let fnName: string; + if (fnExpr.name && getFullWidth(fnExpr.name) > 0) { + // The expression has an identifier, so use that as the name + fnName = declarationNameToString(fnExpr.name); + } + else { + // See if it is a var initializer. If so, use the var name. + if (fnExpr.parent.kind === SyntaxKind.VariableDeclaration) { + fnName = declarationNameToString((fnExpr.parent as VariableDeclaration).name); + } + // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. + else if (fnExpr.parent.kind === SyntaxKind.BinaryExpression && + (fnExpr.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + fnName = (fnExpr.parent as BinaryExpression).left.getText(); + if (fnName.length > 20) { + fnName = fnName.substring(0, 17) + "..."; + } + } + // See if it is a property assignment, and if so use the property name + else if (fnExpr.parent.kind === SyntaxKind.PropertyAssignment && + (fnExpr.parent as PropertyAssignment).name) { + fnName = (fnExpr.parent as PropertyAssignment).name.getText(); + } + else { + fnName = node.kind === SyntaxKind.ClassExpression ? anonClassText : anonFnText; + } + } + const scriptKind = node.kind === SyntaxKind.ClassExpression ? ScriptElementKind.classElement : ScriptElementKind.functionElement; + return getNavBarItem(fnName, scriptKind, [getNodeSpan(node)]); + } + + function getNodeSpan(node: Node) { + return node.kind === SyntaxKind.SourceFile + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); + } + + function getScriptKindForElementKind(kind: SyntaxKind) { + switch (kind) { + case SyntaxKind.VariableDeclaration: + return ScriptElementKind.variableElement; + case SyntaxKind.FunctionDeclaration: + return ScriptElementKind.functionElement; + case SyntaxKind.ClassDeclaration: + return ScriptElementKind.classElement; + case SyntaxKind.Constructor: + return ScriptElementKind.constructorImplementationElement; + case SyntaxKind.GetAccessor: + return ScriptElementKind.memberGetAccessorElement; + case SyntaxKind.SetAccessor: + return ScriptElementKind.memberSetAccessorElement; + default: + return "unknown"; + } + } + + return sourceFileItem.childItems; + } +} diff --git a/src/services/services.ts b/src/services/services.ts index e72c647db1b..2e8a71c8df7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2147,8 +2147,23 @@ namespace ts { export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo { const referencedFiles: FileReference[] = []; const importedFiles: FileReference[] = []; - let ambientExternalModules: string[]; + let ambientExternalModules: { ref: FileReference, depth: number }[]; let isNoDefaultLib = false; + let braceNesting = 0; + // assume that text represent an external module if it contains at least one top level import/export + // ambient modules that are found inside external modules are interpreted as module augmentations + let externalModule = false; + + function nextToken() { + const token = scanner.scan(); + if (token === SyntaxKind.OpenBraceToken) { + braceNesting++; + } + else if (token === SyntaxKind.CloseBraceToken) { + braceNesting--; + } + return token; + } function processTripleSlashDirectives(): void { const commentRanges = getLeadingCommentRanges(sourceText, 0); @@ -2165,21 +2180,33 @@ namespace ts { }); } + function getFileReference() { + const file = scanner.getTokenValue(); + const pos = scanner.getTokenPos(); + return { + fileName: file, + pos: pos, + end: pos + file.length + }; + } + function recordAmbientExternalModule(): void { if (!ambientExternalModules) { ambientExternalModules = []; } - ambientExternalModules.push(scanner.getTokenValue()); + ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting }); } function recordModuleName() { - const importPath = scanner.getTokenValue(); - const pos = scanner.getTokenPos(); - importedFiles.push({ - fileName: importPath, - pos: pos, - end: pos + importPath.length - }); + importedFiles.push(getFileReference()); + + markAsExternalModuleIfTopLevel(); + } + + function markAsExternalModuleIfTopLevel() { + if (braceNesting === 0) { + externalModule = true; + } } /** @@ -2189,9 +2216,9 @@ namespace ts { let token = scanner.getToken(); if (token === SyntaxKind.DeclareKeyword) { // declare module "mod" - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.ModuleKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { recordAmbientExternalModule(); } @@ -2208,7 +2235,8 @@ namespace ts { function tryConsumeImport(): boolean { let token = scanner.getToken(); if (token === SyntaxKind.ImportKeyword) { - token = scanner.scan(); + + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // import "mod"; recordModuleName(); @@ -2216,9 +2244,9 @@ namespace ts { } else { if (token === SyntaxKind.Identifier || isKeyword(token)) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.FromKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // import d from "mod"; recordModuleName(); @@ -2232,7 +2260,7 @@ namespace ts { } else if (token === SyntaxKind.CommaToken) { // consume comma and keep going - token = scanner.scan(); + token = nextToken(); } else { // unknown syntax @@ -2241,17 +2269,17 @@ namespace ts { } if (token === SyntaxKind.OpenBraceToken) { - token = scanner.scan(); + token = nextToken(); // consume "{ a as B, c, d as D}" clauses // make sure that it stops on EOF while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { - token = scanner.scan(); + token = nextToken(); } if (token === SyntaxKind.CloseBraceToken) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.FromKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // import {a as A} from "mod"; // import d, {a, b as B} from "mod" @@ -2261,13 +2289,13 @@ namespace ts { } } else if (token === SyntaxKind.AsteriskToken) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.AsKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.Identifier || isKeyword(token)) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.FromKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // import * as NS from "mod" // import d, * as NS from "mod" @@ -2288,19 +2316,20 @@ namespace ts { function tryConsumeExport(): boolean { let token = scanner.getToken(); if (token === SyntaxKind.ExportKeyword) { - token = scanner.scan(); + markAsExternalModuleIfTopLevel(); + token = nextToken(); if (token === SyntaxKind.OpenBraceToken) { - token = scanner.scan(); + token = nextToken(); // consume "{ a as B, c, d as D}" clauses // make sure it stops on EOF while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) { - token = scanner.scan(); + token = nextToken(); } if (token === SyntaxKind.CloseBraceToken) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.FromKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // export {a as A} from "mod"; // export {a, b as B} from "mod" @@ -2310,9 +2339,9 @@ namespace ts { } } else if (token === SyntaxKind.AsteriskToken) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.FromKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // export * from "mod" recordModuleName(); @@ -2320,9 +2349,9 @@ namespace ts { } } else if (token === SyntaxKind.ImportKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.Identifier || isKeyword(token)) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.EqualsToken) { if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) { return true; @@ -2338,11 +2367,11 @@ namespace ts { } function tryConsumeRequireCall(skipCurrentToken: boolean): boolean { - let token = skipCurrentToken ? scanner.scan() : scanner.getToken(); + let token = skipCurrentToken ? nextToken() : scanner.getToken(); if (token === SyntaxKind.RequireKeyword) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.OpenParenToken) { - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // require("mod"); recordModuleName(); @@ -2356,17 +2385,17 @@ namespace ts { function tryConsumeDefine(): boolean { let token = scanner.getToken(); if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") { - token = scanner.scan(); + token = nextToken(); if (token !== SyntaxKind.OpenParenToken) { return true; } - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.StringLiteral) { // looks like define ("modname", ... - skip string literal and comma - token = scanner.scan(); + token = nextToken(); if (token === SyntaxKind.CommaToken) { - token = scanner.scan(); + token = nextToken(); } else { // unexpected token @@ -2380,7 +2409,7 @@ namespace ts { } // skip open bracket - token = scanner.scan(); + token = nextToken(); let i = 0; // scan until ']' or EOF while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) { @@ -2390,7 +2419,7 @@ namespace ts { i++; } - token = scanner.scan(); + token = nextToken(); } return true; @@ -2400,7 +2429,7 @@ namespace ts { function processImports(): void { scanner.setText(sourceText); - scanner.scan(); + nextToken(); // Look for: // import "mod"; // import d from "mod" @@ -2427,7 +2456,7 @@ namespace ts { continue; } else { - scanner.scan(); + nextToken(); } } @@ -2438,7 +2467,34 @@ namespace ts { processImports(); } processTripleSlashDirectives(); - return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules }; + if (externalModule) { + // for external modules module all nested ambient modules are augmentations + if (ambientExternalModules) { + // move all detected ambient modules to imported files since they need to be resolved + for (const decl of ambientExternalModules) { + importedFiles.push(decl.ref); + } + } + return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined }; + } + else { + // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0 + let ambientModuleNames: string[]; + if (ambientExternalModules) { + for (const decl of ambientExternalModules) { + if (decl.depth === 0) { + if (!ambientModuleNames) { + ambientModuleNames = []; + } + ambientModuleNames.push(decl.ref.fileName); + } + else { + importedFiles.push(decl.ref); + } + } + } + return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames }; + } } /// Helpers diff --git a/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.symbols b/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.symbols new file mode 100644 index 00000000000..4448defa95e --- /dev/null +++ b/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.symbols @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : Symbol(c, Decl(a.ts, 0, 0)) +} + diff --git a/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.types b/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.types new file mode 100644 index 00000000000..6f5caf9d48b --- /dev/null +++ b/tests/baselines/reference/compilerOptionsDeclarationAndNoEmit.types @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : c +} + diff --git a/tests/baselines/reference/compilerOptionsOutAndNoEmit.symbols b/tests/baselines/reference/compilerOptionsOutAndNoEmit.symbols new file mode 100644 index 00000000000..4448defa95e --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutAndNoEmit.symbols @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : Symbol(c, Decl(a.ts, 0, 0)) +} + diff --git a/tests/baselines/reference/compilerOptionsOutAndNoEmit.types b/tests/baselines/reference/compilerOptionsOutAndNoEmit.types new file mode 100644 index 00000000000..6f5caf9d48b --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutAndNoEmit.types @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : c +} + diff --git a/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.symbols b/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.symbols new file mode 100644 index 00000000000..4448defa95e --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.symbols @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : Symbol(c, Decl(a.ts, 0, 0)) +} + diff --git a/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.types b/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.types new file mode 100644 index 00000000000..6f5caf9d48b --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutDirAndNoEmit.types @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : c +} + diff --git a/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.symbols b/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.symbols new file mode 100644 index 00000000000..4448defa95e --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.symbols @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : Symbol(c, Decl(a.ts, 0, 0)) +} + diff --git a/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.types b/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.types new file mode 100644 index 00000000000..6f5caf9d48b --- /dev/null +++ b/tests/baselines/reference/compilerOptionsOutFileAndNoEmit.types @@ -0,0 +1,6 @@ +=== tests/cases/compiler/a.ts === + +class c { +>c : c +} + diff --git a/tests/baselines/reference/noImplicitReturnInConstructors.js b/tests/baselines/reference/noImplicitReturnInConstructors.js new file mode 100644 index 00000000000..f1c4c8caa60 --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnInConstructors.js @@ -0,0 +1,14 @@ +//// [noImplicitReturnInConstructors.ts] +class C { + constructor() { + return; + } +} + +//// [noImplicitReturnInConstructors.js] +var C = (function () { + function C() { + return; + } + return C; +}()); diff --git a/tests/baselines/reference/noImplicitReturnInConstructors.symbols b/tests/baselines/reference/noImplicitReturnInConstructors.symbols new file mode 100644 index 00000000000..326a390ead8 --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnInConstructors.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/noImplicitReturnInConstructors.ts === +class C { +>C : Symbol(C, Decl(noImplicitReturnInConstructors.ts, 0, 0)) + + constructor() { + return; + } +} diff --git a/tests/baselines/reference/noImplicitReturnInConstructors.types b/tests/baselines/reference/noImplicitReturnInConstructors.types new file mode 100644 index 00000000000..304c7b5db50 --- /dev/null +++ b/tests/baselines/reference/noImplicitReturnInConstructors.types @@ -0,0 +1,8 @@ +=== tests/cases/compiler/noImplicitReturnInConstructors.ts === +class C { +>C : C + + constructor() { + return; + } +} diff --git a/tests/cases/compiler/compilerOptionsDeclarationAndNoEmit.ts b/tests/cases/compiler/compilerOptionsDeclarationAndNoEmit.ts new file mode 100644 index 00000000000..41a89011020 --- /dev/null +++ b/tests/cases/compiler/compilerOptionsDeclarationAndNoEmit.ts @@ -0,0 +1,6 @@ +// @declaration: true +// @noEmit: true +// @fileName: a.ts + +class c { +} diff --git a/tests/cases/compiler/compilerOptionsOutAndNoEmit.ts b/tests/cases/compiler/compilerOptionsOutAndNoEmit.ts new file mode 100644 index 00000000000..126043826bd --- /dev/null +++ b/tests/cases/compiler/compilerOptionsOutAndNoEmit.ts @@ -0,0 +1,6 @@ +// @out: outDir +// @noEmit: true +// @fileName: a.ts + +class c { +} diff --git a/tests/cases/compiler/compilerOptionsOutDirAndNoEmit.ts b/tests/cases/compiler/compilerOptionsOutDirAndNoEmit.ts new file mode 100644 index 00000000000..fb701a52661 --- /dev/null +++ b/tests/cases/compiler/compilerOptionsOutDirAndNoEmit.ts @@ -0,0 +1,6 @@ +// @outDir: outDir +// @noEmit: true +// @fileName: a.ts + +class c { +} diff --git a/tests/cases/compiler/compilerOptionsOutFileAndNoEmit.ts b/tests/cases/compiler/compilerOptionsOutFileAndNoEmit.ts new file mode 100644 index 00000000000..2e75c0a976f --- /dev/null +++ b/tests/cases/compiler/compilerOptionsOutFileAndNoEmit.ts @@ -0,0 +1,6 @@ +// @outFile: a.js +// @noEmit: true +// @fileName: a.ts + +class c { +} diff --git a/tests/cases/compiler/noImplicitReturnInConstructors.ts b/tests/cases/compiler/noImplicitReturnInConstructors.ts new file mode 100644 index 00000000000..ac6f5145318 --- /dev/null +++ b/tests/cases/compiler/noImplicitReturnInConstructors.ts @@ -0,0 +1,6 @@ +// @noImplicitReturns: true +class C { + constructor() { + return; + } +} \ No newline at end of file diff --git a/tests/cases/unittests/commandLineParsing.ts b/tests/cases/unittests/commandLineParsing.ts new file mode 100644 index 00000000000..2b6b470a3b3 --- /dev/null +++ b/tests/cases/unittests/commandLineParsing.ts @@ -0,0 +1,191 @@ +/// +/// + +namespace ts { + describe('parseCommandLine', () => { + + function assertParseResult(commandLine: string[], expectedParsedCommandLine: ts.ParsedCommandLine) { + const parsed = ts.parseCommandLine(commandLine); + const parsedCompilerOptions = JSON.stringify(parsed.options); + const expectedCompilerOptions = JSON.stringify(expectedParsedCommandLine.options); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + + const parsedErrors = parsed.errors; + const expectedErrors = expectedParsedCommandLine.errors; + assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`); + for (let i = 0; i < parsedErrors.length; ++i) { + const parsedError = parsedErrors[i]; + const expectedError = expectedErrors[i]; + assert.equal(parsedError.code, expectedError.code); + assert.equal(parsedError.category, expectedError.category); + assert.equal(parsedError.messageText, expectedError.messageText); + } + + const parsedFileNames = parsed.fileNames; + const expectedFileNames = expectedParsedCommandLine.fileNames; + assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`); + for (let i = 0; i < parsedFileNames.length; ++i) { + const parsedFileName = parsedFileNames[i]; + const expectedFileName = expectedFileNames[i]; + assert.equal(parsedFileName, expectedFileName); + } + } + + it("Parse empty options of --jsx ", () => { + // 0.ts --jsx + assertParseResult(["0.ts", "--jsx"], + { + errors: [{ + messageText: "Compiler option 'jsx' expects an argument.", + category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category, + code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code, + + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--jsx' option must be: 'preserve', 'react'", + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: {} + }); + }); + + it("Parse empty options of --module ", () => { + // 0.ts -- + assertParseResult(["0.ts", "--module"], + { + errors: [{ + messageText: "Compiler option 'module' expects an argument.", + category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category, + code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code, + + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015'", + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: {} + }); + }); + + it("Parse empty options of --newLine ", () => { + // 0.ts --newLine + assertParseResult(["0.ts", "--newLine"], + { + errors: [{ + messageText: "Compiler option 'newLine' expects an argument.", + category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category, + code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code, + + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'", + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: {} + }); + }); + + it("Parse empty options of --target ", () => { + // 0.ts --target + assertParseResult(["0.ts", "--target"], + { + errors: [{ + messageText: "Compiler option 'target' expects an argument.", + category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category, + code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code, + + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015'", + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: {} + }); + }); + + it("Parse empty options of --moduleResolution ", () => { + // 0.ts --moduleResolution + assertParseResult(["0.ts", "--moduleResolution"], + { + errors: [{ + messageText: "Compiler option 'moduleResolution' expects an argument.", + category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category, + code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code, + + file: undefined, + start: undefined, + length: undefined, + }, { + messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'", + category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category, + code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + + file: undefined, + start: undefined, + length: undefined, + }], + fileNames: ["0.ts"], + options: {} + }); + }); + + it("Parse multiple compiler flags with input files at the end", () => { + // --module commonjs --target es5 0.ts + assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts"], + { + errors: [], + fileNames: ["0.ts"], + options: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + } + }); + }); + + it("Parse multiple compiler flags with input files in the middle", () => { + // --module commonjs --target es5 0.ts --noImplicitAny + assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts", "--noImplicitAny"], + { + errors: [], + fileNames: ["0.ts"], + options: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES5, + noImplicitAny: true, + } + }); + }); + }); +} diff --git a/tests/cases/unittests/convertCompilerOptionsFromJson.ts b/tests/cases/unittests/convertCompilerOptionsFromJson.ts new file mode 100644 index 00000000000..4edc3bdd200 --- /dev/null +++ b/tests/cases/unittests/convertCompilerOptionsFromJson.ts @@ -0,0 +1,324 @@ +/// +/// + +namespace ts { + describe('convertCompilerOptionsFromJson', () => { + function assertCompilerOptions(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) { + const actualErrors: Diagnostic[] = []; + const actualCompilerOptions: CompilerOptions = convertCompilerOptionsFromJson(optionDeclarations, json["compilerOptions"], "/apath/", actualErrors, configFileName); + + const parsedCompilerOptions = JSON.stringify(actualCompilerOptions); + const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions); + assert.equal(parsedCompilerOptions, expectedCompilerOptions); + + 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); + assert.equal(actualError.category, expectedError.category); + assert.equal(actualError.messageText, expectedError.messageText); + } + } + + // tsconfig.json tests + it("Convert correctly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + } + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [] + } + ); + }); + + it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + "allowJs": false, + } + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + allowJs: false, + }, + errors: [] + } + ); + }); + + it("Convert incorrectly option of jsx to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + "jsx": "" + } + }, "tsconfig.json", + { + compilerOptions: { + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--jsx' option must be: 'preserve', 'react'", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); + + it("Convert incorrectly option of module to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + } + }, "tsconfig.json", + { + compilerOptions: { + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015'", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); + + it("Convert incorrectly option of newLine to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "newLine": "", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + } + }, "tsconfig.json", + { + compilerOptions: { + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); + + it("Convert incorrectly option of target to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "target": "", + "noImplicitAny": false, + "sourceMap": false, + } + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015'", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); + + it("Convert incorrectly option of module-resolution to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "moduleResolution": "", + "noImplicitAny": false, + "sourceMap": false, + } + }, "tsconfig.json", + { + compilerOptions: { + noImplicitAny: false, + sourceMap: false, + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'", + code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code, + category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category + }] + } + ); + }); + + it("Convert incorrectly format tsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "modu": "commonjs", + } + }, "tsconfig.json", + { + compilerOptions: {}, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: Diagnostics.Unknown_compiler_option_0.code, + category: Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); + + it("Convert default tsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "tsconfig.json", + { + compilerOptions: {} as CompilerOptions, + errors: [] + } + ); + }); + + // jsconfig.json + it("Convert correctly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + } + }, "jsconfig.json", + { + compilerOptions: { + allowJs: true, + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [] + } + ); + }); + + it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "noImplicitAny": false, + "sourceMap": false, + "allowJs": false, + } + }, "jsconfig.json", + { + compilerOptions: { + allowJs: false, + module: ModuleKind.CommonJS, + target: ScriptTarget.ES5, + noImplicitAny: false, + sourceMap: false, + }, + errors: [] + } + ); + }); + + it("Convert incorrectly format jsconfig.json to compiler-options ", () => { + assertCompilerOptions( + { + "compilerOptions": { + "modu": "commonjs", + } + }, "jsconfig.json", + { + compilerOptions: + { + allowJs: true + }, + errors: [{ + file: undefined, + start: 0, + length: 0, + messageText: "Unknown compiler option 'modu'.", + code: Diagnostics.Unknown_compiler_option_0.code, + category: Diagnostics.Unknown_compiler_option_0.category + }] + } + ); + }); + + it("Convert default jsconfig.json to compiler-options ", () => { + assertCompilerOptions({}, "jsconfig.json", + { + compilerOptions: + { + allowJs: true + }, + errors: [] + } + ); + }); + }); +} diff --git a/tests/cases/unittests/convertTypingOptionsFromJson.ts b/tests/cases/unittests/convertTypingOptionsFromJson.ts new file mode 100644 index 00000000000..92b450555a9 --- /dev/null +++ b/tests/cases/unittests/convertTypingOptionsFromJson.ts @@ -0,0 +1,188 @@ +/// +/// + +namespace ts { + describe('convertTypingOptionsFromJson', () => { + function assertTypingOptions(json: any, configFileName: string, expectedResult: { typingOptions: TypingOptions, errors: Diagnostic[] }) { + const actualErrors: Diagnostic[] = []; + const actualTypingOptions = convertTypingOptionsFromJson(typingOptionDeclarations, json["typingOptions"], "/apath/", actualErrors, configFileName); + const parsedTypingOptions = JSON.stringify(actualTypingOptions); + const expectedTypingOptions = JSON.stringify(expectedResult.typingOptions); + assert.equal(parsedTypingOptions, expectedTypingOptions); + + 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)}.`); + } + } + + // tsconfig.json + it("Convert correctly format tsconfig.json to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovery": true, + "include": ["0.d.ts", "1.d.ts"], + "exclude": ["0.js", "1.js"] + } + }, + "tsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: true, + include: ["0.d.ts", "1.d.ts"], + exclude: ["0.js", "1.js"] + }, + errors: [] + }); + }); + + it("Convert incorrect format tsconfig.json to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovy": true, + } + }, "tsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: false, + include: [], + exclude: [] + }, + errors: [ + { + category: Diagnostics.Unknown_typing_option_0.category, + code: Diagnostics.Unknown_typing_option_0.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined + } + ] + }); + }); + + it("Convert default tsconfig.json to typing-options ", () => { + assertTypingOptions({}, "tsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: false, + include: [], + exclude: [] + }, + errors: [] + }); + }); + + it("Convert tsconfig.json with only enableAutoDiscovery property to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovery": true + } + }, "tsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: true, + include: [], + exclude: [] + }, + errors: [] + }); + }); + + // jsconfig.json + it("Convert jsconfig.json to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovery": false, + "include": ["0.d.ts"], + "exclude": ["0.js"] + } + }, "jsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: false, + include: ["0.d.ts"], + exclude: ["0.js"] + }, + errors: [] + }); + }); + + it("Convert default jsconfig.json to typing-options ", () => { + assertTypingOptions({ }, "jsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: true, + include: [], + exclude: [] + }, + errors: [] + }); + }); + + it("Convert incorrect format jsconfig.json to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovy": true, + } + }, "jsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: true, + include: [], + exclude: [] + }, + errors: [ + { + category: Diagnostics.Unknown_compiler_option_0.category, + code: Diagnostics.Unknown_typing_option_0.code, + file: undefined, + start: 0, + length: 0, + messageText: undefined + } + ] + }); + }); + + it("Convert jsconfig.json with only enableAutoDiscovery property to typing-options ", () => { + assertTypingOptions( + { + "typingOptions": + { + "enableAutoDiscovery": false + } + }, "jsconfig.json", + { + typingOptions: + { + enableAutoDiscovery: false, + include: [], + exclude: [] + }, + errors: [] + }); + }); + }); +} diff --git a/tests/cases/unittests/services/preProcessFile.ts b/tests/cases/unittests/services/preProcessFile.ts index d9ddaf0f256..a648a3c4b26 100644 --- a/tests/cases/unittests/services/preProcessFile.ts +++ b/tests/cases/unittests/services/preProcessFile.ts @@ -1,6 +1,10 @@ /// /// +declare namespace chai.assert { + function deepEqual(actual: any, expected: any): void; +} + describe('PreProcessFile:', function () { function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void { var resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports); @@ -15,34 +19,30 @@ describe('PreProcessFile:', function () { assert.equal(resultIsLibFile, expectedIsLibFile, "Pre-processed file has different value for isLibFile. Expected: " + expectedPreProcess + ". Actual: " + resultIsLibFile); - assert.equal(resultImportedFiles.length, expectedImportedFiles.length, - "Array's length of imported files does not match expected. Expected: " + expectedImportedFiles.length + ". Actual: " + resultImportedFiles.length); + checkFileReferenceList("Imported files", expectedImportedFiles, resultImportedFiles); + checkFileReferenceList("Referenced files", expectedReferencedFiles, resultReferencedFiles); - assert.equal(resultReferencedFiles.length, expectedReferencedFiles.length, - "Array's length of referenced files does not match expected. Expected: " + expectedReferencedFiles.length + ". Actual: " + resultReferencedFiles.length); + assert.deepEqual(resultPreProcess.ambientExternalModules, expectedPreProcess.ambientExternalModules); + } - for (var i = 0; i < expectedImportedFiles.length; ++i) { - var resultImportedFile = resultImportedFiles[i]; - var expectedImportedFile = expectedImportedFiles[i]; - - assert.equal(resultImportedFile.fileName, expectedImportedFile.fileName, "Imported file path does not match expected. Expected: " + expectedImportedFile.fileName + ". Actual: " + resultImportedFile.fileName + "."); - - assert.equal(resultImportedFile.pos, expectedImportedFile.pos, "Imported file position does not match expected. Expected: " + expectedImportedFile.pos + ". Actual: " + resultImportedFile.pos + "."); - - assert.equal(resultImportedFile.end, expectedImportedFile.end, "Imported file length does not match expected. Expected: " + expectedImportedFile.end + ". Actual: " + resultImportedFile.end + "."); + function checkFileReferenceList(kind: string, expected: ts.FileReference[], actual: ts.FileReference[]) { + if (expected === actual) { + return; } + if (!expected) { + assert.isTrue(false, `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + } + assert.equal(actual.length, expected.length, `[${kind}] Actual array's length does not match expected length. Expected files: ${JSON.stringify(expected)}, actual files: ${JSON.stringify(actual)}`); - for (var i = 0; i < expectedReferencedFiles.length; ++i) { - var resultReferencedFile = resultReferencedFiles[i]; - var expectedReferencedFile = expectedReferencedFiles[i]; - - assert.equal(resultReferencedFile.fileName, expectedReferencedFile.fileName, "Referenced file path does not match expected. Expected: " + expectedReferencedFile.fileName + ". Actual: " + resultReferencedFile.fileName + "."); - - assert.equal(resultReferencedFile.pos, expectedReferencedFile.pos, "Referenced file position does not match expected. Expected: " + expectedReferencedFile.pos + ". Actual: " + resultReferencedFile.pos + "."); - - assert.equal(resultReferencedFile.end, expectedReferencedFile.end, "Referenced file length does not match expected. Expected: " + expectedReferencedFile.end + ". Actual: " + resultReferencedFile.end + "."); + for (var i = 0; i < expected.length; ++i) { + var actualReference = actual[i]; + var expectedReference = expected[i]; + assert.equal(actualReference.fileName, expectedReference.fileName, `[${kind}] actual file path does not match expected. Expected: "${expectedReference.fileName}". Actual: "${actualReference.fileName}".`); + assert.equal(actualReference.pos, expectedReference.pos, `[${kind}] actual file start position does not match expected. Expected: "${expectedReference.pos}". Actual: "${actualReference.pos}".`); + assert.equal(actualReference.end, expectedReference.end, `[${kind}] actual file end pos does not match expected. Expected: "${expectedReference.end}". Actual: "${actualReference.end}".`); } } + describe("Test preProcessFiles,", function () { it("Correctly return referenced files from triple slash", function () { test("///" + "\n" + "///" + "\n" + "///" + "\n" + "///", @@ -183,7 +183,7 @@ describe('PreProcessFile:', function () { function foo() { } `, - /* readImports */ false, + /* readImports */ true, /* detectJavaScriptImports */ false, { @@ -262,6 +262,165 @@ describe('PreProcessFile:', function () { isLibFile: false }) }); + it("correctly handles augmentations in external modules - 1", () => { + test(` + declare module "../Observable" { + interface I {} + } + + export {} + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it("correctly handles augmentations in external modules - 2", () => { + test(` + declare module "../Observable" { + interface I {} + } + + import * as x from "m"; + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "m", "pos": 135, "end": 136 }, + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it("correctly handles augmentations in external modules - 3", () => { + test(` + declare module "../Observable" { + interface I {} + } + + import m = require("m"); + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "m", "pos": 135, "end": 136 }, + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it("correctly handles augmentations in external modules - 4", () => { + test(` + declare module "../Observable" { + interface I {} + } + namespace N {} + export = N; + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it("correctly handles augmentations in external modules - 5", () => { + test(` + declare module "../Observable" { + interface I {} + } + namespace N {} + export import IN = N; + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it("correctly handles augmentations in external modules - 6", () => { + test(` + declare module "../Observable" { + interface I {} + } + export let x = 1; + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "../Observable", "pos": 28, "end": 41 } + ], + ambientExternalModules: undefined, + isLibFile: false + }) + }); + it ("correctly handles augmentations in ambient external modules - 1", () => { + test(` + declare module "m1" { + export * from "m2"; + declare module "augmentation" { + interface I {} + } + } + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "m2", "pos": 65, "end": 67 }, + { "fileName": "augmentation", "pos": 102, "end": 114 } + ], + ambientExternalModules: ["m1"], + isLibFile: false + }); + }); + it ("correctly handles augmentations in ambient external modules - 2", () => { + test(` + namespace M { var x; } + import IM = M; + declare module "m1" { + export * from "m2"; + declare module "augmentation" { + interface I {} + } + } + `, + /*readImportFile*/ true, + /*detectJavaScriptImports*/ false, + { + referencedFiles: [], + importedFiles: [ + { "fileName": "m2", "pos": 127, "end": 129 }, + { "fileName": "augmentation", "pos": 164, "end": 176 } + ], + ambientExternalModules: ["m1"], + isLibFile: false + }); + }); }); }); diff --git a/tests/cases/unittests/tsconfigParsing.ts b/tests/cases/unittests/tsconfigParsing.ts index 3603d22f314..622c3abfff2 100644 --- a/tests/cases/unittests/tsconfigParsing.ts +++ b/tests/cases/unittests/tsconfigParsing.ts @@ -82,5 +82,25 @@ namespace ts { it("returns object with error when json is invalid", () => { assertParseError("invalid"); }); + + it("returns object when users correctly specify library", () => { + assertParseResult( + `{ + "compilerOptions": { + "lib": "es5" + } + }`, { + config: { compilerOptions: { lib: "es5" } } + }); + + assertParseResult( + `{ + "compilerOptions": { + "lib": "es5,es6" + } + }`, { + config: { compilerOptions: { lib: "es5,es6" } } + }); + }); }); }