diff --git a/Gulpfile.js b/Gulpfile.js index 52744e12740..db7bb76d34b 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -12,6 +12,7 @@ const clone = require("gulp-clone"); const newer = require("gulp-newer"); const tsc = require("gulp-typescript"); const tsc_oop = require("./scripts/build/gulp-typescript-oop"); +const getDirSize = require("./scripts/build/getDirSize"); const insert = require("gulp-insert"); const sourcemaps = require("gulp-sourcemaps"); const Q = require("q"); @@ -588,7 +589,13 @@ gulp.task("VerifyLKG", /*help*/ false, [], () => { gulp.task("LKGInternal", /*help*/ false, ["lib", "local"]); gulp.task("LKG", "Makes a new LKG out of the built js files", ["clean", "dontUseDebugMode"], () => { - return runSequence("LKGInternal", "VerifyLKG"); + const sizeBefore = getDirSize(lkgDirectory); + const seq = runSequence("LKGInternal", "VerifyLKG"); + const sizeAfter = getDirSize(lkgDirectory); + if (sizeAfter > (sizeBefore * 1.10)) { + throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); + } + return seq; }); diff --git a/Jakefile.js b/Jakefile.js index 55729eecaad..a6483355c73 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -8,6 +8,7 @@ var path = require("path"); var child_process = require("child_process"); var fold = require("travis-fold"); var ts = require("./lib/typescript"); +const getDirSize = require("./scripts/build/getDirSize"); // Variables var compilerDirectory = "src/compiler/"; @@ -642,26 +643,24 @@ task("generate-spec", [specMd]); // Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory desc("Makes a new LKG out of the built js files"); -task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () { +task("LKG", ["clean", "release", "local"].concat(libraryTargets), () => { + const sizeBefore = getDirSize(LKGDirectory); var expectedFiles = [tscFile, servicesFile, serverFile, nodePackageFile, nodeDefinitionsFile, standaloneDefinitionsFile, tsserverLibraryFile, tsserverLibraryDefinitionFile, cancellationTokenFile, typingsInstallerFile, buildProtocolDts, watchGuardFile]. concat(libraryTargets). concat(localizationTargets); - var missingFiles = expectedFiles.filter(function (f) { - return !fs.existsSync(f); - }); + var missingFiles = expectedFiles.filter(f => !fs.existsSync(f)); if (missingFiles.length > 0) { fail(new Error("Cannot replace the LKG unless all built targets are present in directory " + builtLocalDirectory + ". The following files are missing:\n" + missingFiles.join("\n"))); } // Copy all the targets into the LKG directory jake.mkdirP(LKGDirectory); - for (i in expectedFiles) { - jake.cpR(expectedFiles[i], LKGDirectory); + expectedFiles.forEach(f => jake.cpR(f, LKGDirectory)); + + const sizeAfter = getDirSize(LKGDirectory); + if (sizeAfter > (sizeBefore * 1.10)) { + throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); } - //var resourceDirectories = fs.readdirSync(builtLocalResourcesDirectory).map(function(p) { return path.join(builtLocalResourcesDirectory, p); }); - //resourceDirectories.map(function(d) { - // jake.cpR(d, LKGResourcesDirectory); - //}); }); // Test directory diff --git a/ThirdPartyNoticeText.txt b/ThirdPartyNoticeText.txt index 5b3700bf382..acda89ef37a 100644 --- a/ThirdPartyNoticeText.txt +++ b/ThirdPartyNoticeText.txt @@ -1,12 +1,7 @@ /*!----------------- TypeScript ThirdPartyNotices ------------------------------------------------------- -The TypeScript software is based on or incorporates material and code from the projects listed below (collectively "Third Party Code"). Microsoft is not the original author of the Third Party Code. The original copyright notice and the license, under which Microsoft received such Third Party Code, are set forth below. Such license and notices are provided for informational purposes only. Microsoft licenses the Third Party Code to you under the terms of the Apache 2.0 License. -All Third Party Code licensed by Microsoft under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +The TypeScript software incorporates third party material from the projects listed below. The original copyright notice and the license under which Microsoft received such third party material are set forth below. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions and -limitations under the License. --------------------------------------------- Third Party Code Components -------------------------------------------- diff --git a/scripts/build/getDirSize.js b/scripts/build/getDirSize.js new file mode 100644 index 00000000000..278c4e7f009 --- /dev/null +++ b/scripts/build/getDirSize.js @@ -0,0 +1,30 @@ +// @ts-check +const { lstatSync, readdirSync } = require("fs"); +const { join } = require("path"); + +/** + * Find the size of a directory recursively. + * Symbolic links are counted once (same inode). + * @param {string} root + * @param {Set} seen + * @returns {number} bytes + */ +function getDirSize(root, seen = new Set()) { + const stats = lstatSync(root); + + if (seen.has(stats.ino)) { + return 0; + } + + seen.add(stats.ino); + + if (!stats.isDirectory()) { + return stats.size; + } + + return readdirSync(root) + .map(file => getDirSize(join(root, file), seen)) + .reduce((acc, num) => acc + num, 0); +} + +module.exports = getDirSize; diff --git a/scripts/build/gulp-typescript-oop.js b/scripts/build/gulp-typescript-oop.js index d4b494e6435..78d09c70874 100644 --- a/scripts/build/gulp-typescript-oop.js +++ b/scripts/build/gulp-typescript-oop.js @@ -31,7 +31,7 @@ function createProject(tsConfigFileName, settings, options) { read() {}, /** @param {*} file */ write(file, encoding, callback) { - proc.send({ method: "write", params: { path: file.path, cwd: file.cwd, base: file.base }}); + proc.send({ method: "write", params: { path: file.path, cwd: file.cwd, base: file.base, sourceMap: file.sourceMap }}); callback(); }, final(callback) { diff --git a/scripts/build/main.js b/scripts/build/main.js index 3dcec4880d7..70a46adca8e 100644 --- a/scripts/build/main.js +++ b/scripts/build/main.js @@ -72,6 +72,7 @@ process.on("message", ({ method, params }) => { base: params.base }); file.contents = fs.readFileSync(file.path); + if (params.sourceMap) file.sourceMap = params.sourceMap; inputStream.push(/** @type {*} */(file)); } else if (method === "final") { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe152f38556..1e38d2d645c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -282,6 +282,8 @@ namespace ts { createPromiseType, createArrayType, getBooleanType: () => booleanType, + getFalseType: () => falseType, + getTrueType: () => trueType, getVoidType: () => voidType, getUndefinedType: () => undefinedType, getNullType: () => nullType, @@ -374,9 +376,9 @@ namespace ts { const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsWideningType, "null"); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); - const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true"); const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false"); - const booleanType = createBooleanType([trueType, falseType]); + const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true"); + const booleanType = createBooleanType([falseType, trueType]); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); @@ -2900,7 +2902,7 @@ namespace ts { function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; - if (forEach(symbol.declarations, declaration => !getIsDeclarationVisible(declaration))) { + if (!every(symbol.declarations, getIsDeclarationVisible)) { return undefined; } return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; @@ -3531,9 +3533,13 @@ namespace ts { context.enclosingDeclaration = undefined; if (getCheckFlags(propertySymbol) & CheckFlags.Late) { const decl = first(propertySymbol.declarations); - const name = hasLateBindableName(decl) && resolveEntityName(decl.name.expression, SymbolFlags.Value); - if (name && context.tracker.trackSymbol) { - context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value); + if (context.tracker.trackSymbol && hasLateBindableName(decl)) { + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(decl.name.expression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value); + } } } const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); @@ -5494,7 +5500,7 @@ namespace ts { // object types. function isValidBaseType(type: Type): type is BaseType { return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)) && !isGenericMappedType(type) || - !!(type.flags & TypeFlags.Intersection) && !some((type).types, t => !isValidBaseType(t)); + !!(type.flags & TypeFlags.Intersection) && every((type).types, isValidBaseType); } function resolveBaseTypesOfInterface(type: InterfaceType): void { @@ -10294,7 +10300,7 @@ namespace ts { return type.flags & TypeFlags.Object ? isEmptyResolvedType(resolveStructuredTypeMembers(type)) : type.flags & TypeFlags.NonPrimitive ? true : type.flags & TypeFlags.Union ? some((type).types, isEmptyObjectType) : - type.flags & TypeFlags.Intersection ? !some((type).types, t => !isEmptyObjectType(t)) : + type.flags & TypeFlags.Intersection ? every((type).types, isEmptyObjectType) : false; } @@ -11955,7 +11961,7 @@ namespace ts { function isLiteralType(type: Type): boolean { return type.flags & TypeFlags.Boolean ? true : - type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : !forEach((type).types, t => !isUnitType(t)) : + type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type).types, isUnitType) : isUnitType(type); } @@ -16165,7 +16171,7 @@ namespace ts { return !!(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.NonPrimitive) || getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || type.flags & TypeFlags.Object && !isGenericMappedType(type) || - type.flags & TypeFlags.UnionOrIntersection && !forEach((type).types, t => !isValidSpreadType(t))); + type.flags & TypeFlags.UnionOrIntersection && every((type).types, isValidSpreadType)); } function checkJsxSelfClosingElement(node: JsxSelfClosingElement, checkMode: CheckMode | undefined): Type { @@ -18618,7 +18624,7 @@ namespace ts { if (node.expression.kind === SyntaxKind.SuperKeyword) { const superType = checkSuperExpression(node.expression); if (isTypeAny(superType)) { - forEach(node.arguments, checkExpression); // Still visit arguments so they get marked for visibility, etc + forEach(node.arguments, checkExpresionNoReturn); // Still visit arguments so they get marked for visibility, etc return anySignature; } if (superType !== errorType) { @@ -20443,7 +20449,7 @@ namespace ts { if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { const name = prop.escapedName; const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); - if (symbol && symbol.declarations.some(d => d.kind === SyntaxKind.JSDocTypedefTag)) { + if (symbol && symbol.declarations.some(isJSDocTypedefTag)) { grammarErrorOnNode(symbol.declarations[0], Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); return grammarErrorOnNode(prop.valueDeclaration, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name)); } @@ -20779,6 +20785,10 @@ namespace ts { return type; } + function checkExpresionNoReturn(node: Expression) { + checkExpression(node); + } + // Checks an expression and returns its type. The contextualMapper parameter serves two purposes: When // contextualMapper is not undefined and not equal to the identityMapper function object it indicates that the // expression is being inferentially typed (section 4.15.2 in spec) and provides the type mapper to use in diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b9149d76d7a..d7760d1f75f 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -109,6 +109,14 @@ namespace ts { paramType: Diagnostics.FILE_OR_DIRECTORY, description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, }, + { + name: "build", + type: "boolean", + shortName: "b", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date + }, { name: "pretty", type: "boolean", @@ -968,6 +976,125 @@ namespace ts { } + function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText; + } + + /* @internal */ + export function printVersion() { + sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine); + } + + /* @internal */ + export function printHelp(optionsList: CommandLineOption[], syntaxPrefix = "") { + const output: string[] = []; + + // We want to align our "syntax" and "examples" commands to a certain margin. + const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; + const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; + let marginLength = Math.max(syntaxLength, examplesLength); + + // Build up the syntactic skeleton. + let syntax = makePadding(marginLength - syntaxLength); + syntax += `tsc ${syntaxPrefix}[${getDiagnosticText(Diagnostics.options)}] [${getDiagnosticText(Diagnostics.file)}...]`; + + output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); + output.push(sys.newLine + sys.newLine); + + // Build up the list of examples. + const padding = makePadding(marginLength); + output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); + output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); + output.push(padding + "tsc @args.txt" + sys.newLine); + output.push(padding + "tsc --build tsconfig.json" + sys.newLine); + output.push(sys.newLine); + + output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); + + // We want our descriptions to align at the same column in our output, + // so we keep track of the longest option usage string. + marginLength = 0; + const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. + const descriptionColumn: string[] = []; + + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind + + for (const option of optionsList) { + // If an option lacks a description, + // it is not officially supported. + if (!option.description) { + continue; + } + + let usageText = " "; + if (option.shortName) { + usageText += "-" + option.shortName; + usageText += getParamType(option); + usageText += ", "; + } + + usageText += "--" + option.name; + usageText += getParamType(option); + + usageColumn.push(usageText); + let description: string; + + if (option.name === "lib") { + description = getDiagnosticText(option.description); + const element = (option).element; + const typeMap = >element.type; + optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); + } + else { + description = getDiagnosticText(option.description); + } + + descriptionColumn.push(description); + + // Set the new margin for the description column if necessary. + marginLength = Math.max(usageText.length, marginLength); + } + + // Special case that can't fit in the loop. + const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; + usageColumn.push(usageText); + descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); + marginLength = Math.max(usageText.length, marginLength); + + // Print out each row, aligning all the descriptions on the same column. + for (let i = 0; i < usageColumn.length; i++) { + const usage = usageColumn[i]; + const description = descriptionColumn[i]; + const kindsList = optionsDescriptionMap.get(description); + output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); + + if (kindsList) { + output.push(makePadding(marginLength + 4)); + for (const kind of kindsList) { + output.push(kind + " "); + } + output.push(sys.newLine); + } + } + + for (const line of output) { + sys.write(line); + } + return; + + function getParamType(option: CommandLineOption) { + if (option.paramType !== undefined) { + return " " + getDiagnosticText(option.paramType); + } + return ""; + } + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + } + export type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** * Reports config file diagnostics diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f2a10a08fde..668f3b5a6f0 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3620,6 +3620,95 @@ "category": "Error", "code": 6309 }, + "Project '{0}' is out of date because oldest output '{1}' is older than newest input '{2}'": { + "category": "Message", + "code": 6350 + }, + "Project '{0}' is up to date because newest input '{1}' is older than oldest output '{2}'": { + "category": "Message", + "code": 6351 + }, + "Project '{0}' is out of date because output file '{1}' does not exist": { + "category": "Message", + "code": 6352 + }, + "Project '{0}' is out of date because its dependency '{1}' is out of date": { + "category": "Message", + "code": 6353 + }, + + "Project '{0}' is up to date with .d.ts files from its dependencies": { + "category": "Message", + "code": 6354 + }, + "Projects in this build: {0}": { + "category": "Message", + "code": 6355 + }, + "A non-dry build would delete the following files: {0}": { + "category": "Message", + "code": 6356 + }, + "A non-dry build would build project '{0}'": { + "category": "Message", + "code": 6357 + }, + "Building project '{0}'...": { + "category": "Message", + "code": 6358 + }, + "Updating output timestamps of project '{0}'...": { + "category": "Message", + "code": 6359 + }, + "delete this - Project '{0}' is up to date because it was previously built": { + "category": "Message", + "code": 6360 + }, + "Project '{0}' is up to date": { + "category": "Message", + "code": 6361 + }, + "Skipping build of project '{0}' because its dependency '{1}' has errors": { + "category": "Message", + "code": 6362 + }, + "Project '{0}' can't be built because its dependency '{1}' has errors": { + "category": "Message", + "code": 6363 + }, + "Build one or more projects and their dependencies, if out of date": { + "category": "Message", + "code": 6364 + }, + "Delete the outputs of all projects": { + "category": "Message", + "code": 6365 + }, + "Enable verbose logging": { + "category": "Message", + "code": 6366 + }, + "Show what would be built (or deleted, if specified with '--clean')": { + "category": "Message", + "code": 6367 + }, + "Build all projects, including those that appear to be up to date": { + "category": "Message", + "code": 6368 + }, + "Option '--build' must be the first command line argument.": { + "category": "Error", + "code": 6369 + }, + "Options '{0}' and '{1}' cannot be combined.": { + "category": "Error", + "code": 6370 + }, + "Skipping clean because not all projects could be located": { + "category": "Error", + "code": 6371 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 90ec1c793a3..7269462578e 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1026,7 +1026,7 @@ namespace ts { // SyntaxKind.UnparsedSource function emitUnparsedSource(unparsed: UnparsedSource) { - write(unparsed.text); + writer.rawWrite(unparsed.text); } // diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 2e776134a72..1152e409e75 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2587,16 +2587,19 @@ namespace ts { return node; } - export function createUnparsedSourceFile(text: string): UnparsedSource { + export function createUnparsedSourceFile(text: string, map?: string): UnparsedSource { const node = createNode(SyntaxKind.UnparsedSource); node.text = text; + node.sourceMapText = map; return node; } - export function createInputFiles(javascript: string, declaration: string): InputFiles { + export function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles { const node = createNode(SyntaxKind.InputFiles); node.javascriptText = javascript; + node.javascriptMapText = javascriptMapText; node.declarationText = declaration; + node.declarationMapText = declarationMapText; return node; } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 50daa81d60c..6da0fc61e63 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -248,7 +248,7 @@ namespace ts.moduleSpecifiers { const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; if (mainFileRelative) { const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); - if (mainExportFile === getCanonicalFileName(path)) { + if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) { return packageRootPath; } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index cb6e1347868..b08f966864a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -189,7 +189,10 @@ namespace ts { getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "", getDirectories: (path: string) => sys.getDirectories(path), realpath, - readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth) + readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth), + getModifiedTime: sys.getModifiedTime && (path => sys.getModifiedTime!(path)), + setModifiedTime: sys.setModifiedTime && ((path, date) => sys.setModifiedTime!(path, date)), + deleteFile: sys.deleteFile && (path => sys.deleteFile!(path)) }; } @@ -615,25 +618,27 @@ namespace ts { // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files const resolvedProjectReferences: (ResolvedProjectReference | undefined)[] | undefined = projectReferences ? [] : undefined; const projectReferenceRedirects: Map = createMap(); - if (projectReferences) { - for (const ref of projectReferences) { - const parsedRef = parseProjectReferenceConfigFile(ref); - resolvedProjectReferences!.push(parsedRef); - if (parsedRef) { - if (parsedRef.commandLine.options.outFile) { - const dtsOutfile = changeExtension(parsedRef.commandLine.options.outFile, ".d.ts"); - processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); - } - addProjectReferenceRedirects(parsedRef.commandLine, projectReferenceRedirects); - } - } - } const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); const structuralIsReused = tryReuseStructureFromOldProgram(); if (structuralIsReused !== StructureIsReused.Completely) { processingDefaultLibFiles = []; processingOtherFiles = []; + + if (projectReferences) { + for (const ref of projectReferences) { + const parsedRef = parseProjectReferenceConfigFile(ref); + resolvedProjectReferences!.push(parsedRef); + if (parsedRef) { + if (parsedRef.commandLine.options.outFile) { + const dtsOutfile = changeExtension(parsedRef.commandLine.options.outFile, ".d.ts"); + processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined); + } + addProjectReferenceRedirects(parsedRef.commandLine, projectReferenceRedirects); + } + } + } + forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false)); // load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders @@ -1021,7 +1026,7 @@ namespace ts { for (const oldSourceFile of oldSourceFiles) { let newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.path, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile) + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath || oldSourceFile.path, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile) : host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 if (!newSourceFile) { @@ -1234,8 +1239,10 @@ namespace ts { const dtsFilename = changeExtension(resolvedRefOpts.options.outFile, ".d.ts"); const js = host.readFile(resolvedRefOpts.options.outFile) || `/* Input file ${resolvedRefOpts.options.outFile} was missing */\r\n`; + const jsMap = host.readFile(resolvedRefOpts.options.outFile + ".map"); // TODO: try to read sourceMappingUrl comment from the js file const dts = host.readFile(dtsFilename) || `/* Input file ${dtsFilename} was missing */\r\n`; - const node = createInputFiles(js, dts); + const dtsMap = host.readFile(dtsFilename + ".map"); + const node = createInputFiles(js, dts, jsMap, dtsMap); nodes.push(node); } } @@ -2047,6 +2054,7 @@ namespace ts { if (file) { sourceFilesFoundSearchingNodeModules.set(path, currentNodeModulesDepth > 0); file.path = path; + file.resolvedPath = toPath(fileName); if (host.useCaseSensitiveFileNames()) { const pathLowerCase = path.toLowerCase(); @@ -2781,7 +2789,7 @@ namespace ts { /** * Returns the target config filename of a project reference */ - function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined { + export function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined { if (!host.fileExists(ref.path)) { return combinePaths(ref.path, "tsconfig.json"); } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b4c428d5e5a..3f848cfbc29 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -349,8 +349,32 @@ namespace ts { return endsWith(dirPath, "/node_modules/@types"); } - function isDirectoryAtleastAtLevelFromFSRoot(dirPath: Path, minLevels: number) { - for (let searchIndex = getRootLength(dirPath); minLevels > 0; minLevels--) { + /** + * Filter out paths like + * "/", "/user", "/user/username", "/user/username/folderAtRoot", + * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" + * @param dirPath + */ + function canWatchDirectory(dirPath: Path) { + const rootLength = getRootLength(dirPath); + if (dirPath.length === rootLength) { + // Ignore "/", "c:/" + return false; + } + + const nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + if (nextDirectorySeparator === -1) { + // ignore "/user", "c:/users" or "c:/folderAtRoot" + return false; + } + + if (dirPath.charCodeAt(0) !== CharacterCodes.slash && + dirPath.substr(rootLength, nextDirectorySeparator).search(/users/i) === -1) { + // Paths like c:/folderAtRoot/subFolder are allowed + return true; + } + + for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; if (searchIndex === 0) { // Folder isnt at expected minimun levels @@ -360,15 +384,6 @@ namespace ts { return true; } - function canWatchDirectory(dirPath: Path) { - return isDirectoryAtleastAtLevelFromFSRoot(dirPath, - // When root is "/" do not watch directories like: - // "/", "/user", "/user/username", "/user/username/folderAtRoot" - // When root is "c:/" do not watch directories like: - // "c:/", "c:/folderAtRoot" - dirPath.charCodeAt(0) === CharacterCodes.slash ? 3 : 1); - } - function filterFSRootDirectoriesToWatch(watchPath: DirectoryOfFailedLookupWatch, dirPath: Path): DirectoryOfFailedLookupWatch { if (!canWatchDirectory(dirPath)) { watchPath.ignore = true; diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 9229e2daa0e..8ef71ecf955 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -99,6 +99,10 @@ namespace ts { let sourceMapDataList: SourceMapData[] | undefined; let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap); + let completedSections: SourceMapSectionDefinition[]; + let sectionStartLine: number; + let sectionStartColumn: number; + return { initialize, reset, @@ -146,6 +150,9 @@ namespace ts { lastEncodedNameIndex = 0; // Initialize source map data + completedSections = []; + sectionStartLine = 1; + sectionStartColumn = 1; sourceMapData = { sourceMapFilePath, jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined!, // TODO: GH#18217 @@ -214,6 +221,65 @@ namespace ts { lastEncodedNameIndex = undefined; sourceMapData = undefined!; sourceMapDataList = undefined!; + completedSections = undefined!; + sectionStartLine = undefined!; + sectionStartColumn = undefined!; + } + + interface SourceMapSection { + version: 3; + file: string; + sourceRoot?: string; + sources: string[]; + names?: string[]; + mappings: string; + sourcesContent?: string[]; + sections?: undefined; + } + + type SourceMapSectionDefinition = + | { offset: { line: number, column: number }, url: string } // Included for completeness + | { offset: { line: number, column: number }, map: SourceMap }; + + interface SectionalSourceMap { + version: 3; + file: string; + sections: SourceMapSectionDefinition[]; + } + + type SourceMap = SectionalSourceMap | SourceMapSection; + + function captureSection(): SourceMapSection { + return { + version: 3, + file: sourceMapData.sourceMapFile, + sourceRoot: sourceMapData.sourceMapSourceRoot, + sources: sourceMapData.sourceMapSources, + names: sourceMapData.sourceMapNames, + mappings: sourceMapData.sourceMapMappings, + sourcesContent: sourceMapData.sourceMapSourcesContent, + }; + } + + function resetSectionalData(): void { + sourceMapData.sourceMapSources = []; + sourceMapData.sourceMapNames = []; + sourceMapData.sourceMapMappings = ""; + sourceMapData.sourceMapSourcesContent = compilerOptions.inlineSources ? [] : undefined; + } + + function generateMap(): SourceMap { + if (completedSections.length) { + captureSectionalSpanIfNeeded(/*reset*/ false); + return { + version: 3, + file: sourceMapData.sourceMapFile, + sections: completedSections + }; + } + else { + return captureSection(); + } } // Encoding for sourcemap span @@ -284,8 +350,8 @@ namespace ts { sourceLinePos.line++; sourceLinePos.character++; - const emittedLine = writer.getLine(); - const emittedColumn = writer.getColumn(); + const emittedLine = writer.getLine() - sectionStartLine + 1; + const emittedColumn = emittedLine === 0 ? (writer.getColumn() - sectionStartColumn + 1) : writer.getColumn(); // If this location wasn't recorded or the location in source is going backwards, record the span if (!lastRecordedSourceMapSpan || @@ -320,6 +386,15 @@ namespace ts { } } + function captureSectionalSpanIfNeeded(reset: boolean) { + if (lastRecordedSourceMapSpan && lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) { // If we've recorded some spans, save them + completedSections.push({ offset: { line: sectionStartLine - 1, column: sectionStartColumn - 1 }, map: captureSection() }); + if (reset) { + resetSectionalData(); + } + } + } + /** * Emits a node with possible leading and trailing source maps. * @@ -333,6 +408,35 @@ namespace ts { } if (node) { + if (isUnparsedSource(node) && node.sourceMapText !== undefined) { + captureSectionalSpanIfNeeded(/*reset*/ true); + const text = node.sourceMapText; + let parsed: {} | undefined; + try { + parsed = JSON.parse(text); + } + catch { + // empty + } + const offset = { line: writer.getLine() - 1, column: writer.getColumn() - 1 }; + completedSections.push(parsed + ? { + offset, + map: parsed as SourceMap + } + : { + offset, + // This is just passes the buck on sourcemaps we don't really understand, instead of issuing an error (which would be difficult this late) + url: `data:application/json;charset=utf-8;base64,${base64encode(sys, text)}` + } + ); + const emitResult = emitCallback(hint, node); + sectionStartLine = writer.getLine(); + sectionStartColumn = writer.getColumn(); + lastRecordedSourceMapSpan = undefined!; + lastEncodedSourceMapSpan = defaultLastEncodedSourceMapSpan; + return emitResult; + } const emitNode = node.emitNode; const emitFlags = emitNode && emitNode.flags || EmitFlags.None; const range = emitNode && emitNode.sourceMapRange; @@ -460,15 +564,7 @@ namespace ts { encodeLastRecordedSourceMapSpan(); - return JSON.stringify({ - version: 3, - file: sourceMapData.sourceMapFile, - sourceRoot: sourceMapData.sourceMapSourceRoot, - sources: sourceMapData.sourceMapSources, - names: sourceMapData.sourceMapNames, - mappings: sourceMapData.sourceMapMappings, - sourcesContent: sourceMapData.sourceMapSourcesContent, - }); + return JSON.stringify(generateMap()); } /** diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 91e5a48dc05..ee6c9edc3bd 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -433,6 +433,7 @@ namespace ts { readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; + /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -448,6 +449,8 @@ namespace ts { getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; /** * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) */ @@ -592,6 +595,8 @@ namespace ts { }, readDirectory, getModifiedTime, + setModifiedTime, + deleteFile, createHash: _crypto ? createMD5HashUsingNativeCrypto : generateDjb2Hash, createSHA256Hash: _crypto ? createSHA256Hash : undefined, getMemoryUsage() { @@ -1069,6 +1074,24 @@ namespace ts { } } + function setModifiedTime(path: string, time: Date) { + try { + _fs.utimesSync(path, time, time); + } + catch (e) { + return; + } + } + + function deleteFile(path: string) { + try { + return _fs.unlinkSync(path); + } + catch (e) { + return; + } + } + /** * djb2 hashing algorithm * http://www.cse.yorku.ca/~oz/hash.html diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 007162ff13c..38ddd0067f0 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -180,7 +180,7 @@ namespace ts { } ), mapDefined(node.prepends, prepend => { if (prepend.kind === SyntaxKind.InputFiles) { - return createUnparsedSourceFile(prepend.declarationText); + return createUnparsedSourceFile(prepend.declarationText, prepend.declarationMapText); } })); bundle.syntheticFileReferences = []; diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index b05cabcd85a..e757c5d09d3 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1832,6 +1832,7 @@ namespace ts { let statementsLocation: TextRange; let closeBraceLocation: TextRange | undefined; + const leadingStatements: Statement[] = []; const statements: Statement[] = []; const body = node.body!; let statementOffset: number | undefined; @@ -1840,21 +1841,16 @@ namespace ts { if (isBlock(body)) { // ensureUseStrict is false because no new prologue-directive should be added. // addStandardPrologue will put already-existing directives at the beginning of the target statement-array - statementOffset = addStandardPrologue(statements, body.statements, /*ensureUseStrict*/ false); + statementOffset = addStandardPrologue(leadingStatements, body.statements, /*ensureUseStrict*/ false); } - addCaptureThisForNodeIfNeeded(statements, node); - addDefaultValueAssignmentsIfNeeded(statements, node); - addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); - - // If we added any generated statements, this must be a multi-line block. - if (!multiLine && statements.length > 0) { - multiLine = true; - } + addCaptureThisForNodeIfNeeded(leadingStatements, node); + addDefaultValueAssignmentsIfNeeded(leadingStatements, node); + addRestParameterIfNeeded(leadingStatements, node, /*inConstructorWithSynthesizedSuper*/ false); if (isBlock(body)) { // addCustomPrologue puts already-existing directives at the beginning of the target statement-array - statementOffset = addCustomPrologue(statements, body.statements, statementOffset, visitor); + statementOffset = addCustomPrologue(leadingStatements, body.statements, statementOffset, visitor); statementsLocation = body.statements; addRange(statements, visitNodes(body.statements, visitor, isStatement, statementOffset)); @@ -1897,15 +1893,14 @@ namespace ts { const lexicalEnvironment = context.endLexicalEnvironment(); prependStatements(statements, lexicalEnvironment); - prependCaptureNewTargetIfNeeded(statements, node, /*copyOnWrite*/ false); // If we added any final generated statements, this must be a multi-line block - if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) { + if (some(leadingStatements) || some(lexicalEnvironment)) { multiLine = true; } - const block = createBlock(setTextRange(createNodeArray(statements), statementsLocation), multiLine); + const block = createBlock(setTextRange(createNodeArray([...leadingStatements, ...statements]), statementsLocation), multiLine); setTextRange(block, node.body); if (!multiLine && singleLine) { setEmitFlags(block, EmitFlags.SingleLine); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 076645a14d8..b4421506357 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -100,7 +100,7 @@ namespace ts { function transformBundle(node: Bundle) { return createBundle(node.sourceFiles.map(transformSourceFile), mapDefined(node.prepends, prepend => { if (prepend.kind === SyntaxKind.InputFiles) { - return createUnparsedSourceFile(prepend.javascriptText); + return createUnparsedSourceFile(prepend.javascriptText, prepend.javascriptMapText); } return prepend; })); @@ -1912,6 +1912,7 @@ namespace ts { case SyntaxKind.AnyKeyword: case SyntaxKind.UnknownKeyword: case SyntaxKind.ThisType: + case SyntaxKind.ImportType: break; default: diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts new file mode 100644 index 00000000000..79306cc5d06 --- /dev/null +++ b/src/compiler/tsbuild.ts @@ -0,0 +1,1212 @@ +namespace ts { + /** + * Branded string for keeping track of when we've turned an ambiguous path + * specified like "./blah" to an absolute path to an actual + * tsconfig file, e.g. "/root/blah/tsconfig.json" + */ + export type ResolvedConfigFileName = string & { _isResolvedConfigFileName: never }; + + const minimumDate = new Date(-8640000000000000); + const maximumDate = new Date(8640000000000000); + + export interface BuildHost { + verbose(diag: DiagnosticMessage, ...args: string[]): void; + error(diag: DiagnosticMessage, ...args: string[]): void; + errorDiagnostic(diag: Diagnostic): void; + message(diag: DiagnosticMessage, ...args: string[]): void; + } + + /** + * A BuildContext tracks what's going on during the course of a build. + * + * Callers may invoke any number of build requests within the same context; + * until the context is reset, each project will only be built at most once. + * + * Example: In a standard setup where project B depends on project A, and both are out of date, + * a failed build of A will result in A remaining out of date. When we try to build + * B, we should immediately bail instead of recomputing A's up-to-date status again. + * + * This also matters for performing fast (i.e. fake) downstream builds of projects + * when their upstream .d.ts files haven't changed content (but have newer timestamps) + */ + export interface BuildContext { + options: BuildOptions; + /** + * Map from output file name to its pre-build timestamp + */ + unchangedOutputs: FileMap; + + /** + * Map from config file name to up-to-date status + */ + projectStatus: FileMap; + + invalidatedProjects: FileMap; + queuedProjects: FileMap; + missingRoots: Map; + } + + type Mapper = ReturnType; + interface DependencyGraph { + buildQueue: ResolvedConfigFileName[]; + dependencyMap: Mapper; + } + + interface BuildOptions { + dry: boolean; + force: boolean; + verbose: boolean; + } + + enum BuildResultFlags { + None = 0, + + /** + * No errors of any kind occurred during build + */ + Success = 1 << 0, + /** + * None of the .d.ts files emitted by this build were + * different from the existing files on disk + */ + DeclarationOutputUnchanged = 1 << 1, + + ConfigFileErrors = 1 << 2, + SyntaxErrors = 1 << 3, + TypeErrors = 1 << 4, + DeclarationEmitErrors = 1 << 5, + + AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors + } + + export enum UpToDateStatusType { + Unbuildable, + UpToDate, + /** + * The project appears out of date because its upstream inputs are newer than its outputs, + * but all of its outputs are actually newer than the previous identical outputs of its (.d.ts) inputs. + * This means we can Pseudo-build (just touch timestamps), as if we had actually built this project. + */ + UpToDateWithUpstreamTypes, + OutputMissing, + OutOfDateWithSelf, + OutOfDateWithUpstream, + UpstreamOutOfDate, + UpstreamBlocked, + + /** + * Projects with no outputs (i.e. "solution" files) + */ + ContainerOnly + } + + export type UpToDateStatus = + | Status.Unbuildable + | Status.UpToDate + | Status.OutputMissing + | Status.OutOfDateWithSelf + | Status.OutOfDateWithUpstream + | Status.UpstreamOutOfDate + | Status.UpstreamBlocked + | Status.ContainerOnly; + + export namespace Status { + /** + * The project can't be built at all in its current state. For example, + * its config file cannot be parsed, or it has a syntax error or missing file + */ + export interface Unbuildable { + type: UpToDateStatusType.Unbuildable; + reason: string; + } + + /** + * This project doesn't have any outputs, so "is it up to date" is a meaningless question. + */ + export interface ContainerOnly { + type: UpToDateStatusType.ContainerOnly; + } + + /** + * The project is up to date with respect to its inputs. + * We track what the newest input file is. + */ + export interface UpToDate { + type: UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes; + newestInputFileTime: Date; + newestInputFileName: string; + newestDeclarationFileContentChangedTime: Date; + newestOutputFileTime: Date; + newestOutputFileName: string; + oldestOutputFileName: string; + } + + /** + * One or more of the outputs of the project does not exist. + */ + export interface OutputMissing { + type: UpToDateStatusType.OutputMissing; + /** + * The name of the first output file that didn't exist + */ + missingOutputFileName: string; + } + + /** + * One or more of the project's outputs is older than its newest input. + */ + export interface OutOfDateWithSelf { + type: UpToDateStatusType.OutOfDateWithSelf; + outOfDateOutputFileName: string; + newerInputFileName: string; + } + + /** + * This project depends on an out-of-date project, so shouldn't be built yet + */ + export interface UpstreamOutOfDate { + type: UpToDateStatusType.UpstreamOutOfDate; + upstreamProjectName: string; + } + + /** + * This project depends an upstream project with build errors + */ + export interface UpstreamBlocked { + type: UpToDateStatusType.UpstreamBlocked; + upstreamProjectName: string; + } + + /** + * One or more of the project's outputs is older than the newest output of + * an upstream project. + */ + export interface OutOfDateWithUpstream { + type: UpToDateStatusType.OutOfDateWithUpstream; + outOfDateOutputFileName: string; + newerProjectName: string; + } + } + + interface FileMap { + setValue(fileName: string, value: T): void; + getValue(fileName: string): T | never; + getValueOrUndefined(fileName: string): T | undefined; + hasKey(fileName: string): boolean; + removeKey(fileName: string): void; + getKeys(): string[]; + } + + /** + * A FileMap maintains a normalized-key to value relationship + */ + function createFileMap(): FileMap { + // tslint:disable-next-line:no-null-keyword + const lookup = createMap(); + + return { + setValue, + getValue, + getValueOrUndefined, + removeKey, + getKeys, + hasKey + }; + + function getKeys(): string[] { + return Object.keys(lookup); + } + + function hasKey(fileName: string) { + return lookup.has(normalizePath(fileName)); + } + + function removeKey(fileName: string) { + lookup.delete(normalizePath(fileName)); + } + + function setValue(fileName: string, value: T) { + lookup.set(normalizePath(fileName), value); + } + + function getValue(fileName: string): T | never { + const f = normalizePath(fileName); + if (lookup.has(f)) { + return lookup.get(f)!; + } + else { + throw new Error(`No value corresponding to ${fileName} exists in this map`); + } + } + + function getValueOrUndefined(fileName: string): T | undefined { + const f = normalizePath(fileName); + return lookup.get(f); + } + } + + export function createDependencyMapper() { + const childToParents = createFileMap(); + const parentToChildren = createFileMap(); + const allKeys = createFileMap(); + + function addReference(childConfigFileName: ResolvedConfigFileName, parentConfigFileName: ResolvedConfigFileName): void { + addEntry(childToParents, childConfigFileName, parentConfigFileName); + addEntry(parentToChildren, parentConfigFileName, childConfigFileName); + } + + function getReferencesTo(parentConfigFileName: ResolvedConfigFileName): ResolvedConfigFileName[] { + return parentToChildren.getValueOrUndefined(parentConfigFileName) || []; + } + + function getReferencesOf(childConfigFileName: ResolvedConfigFileName): ResolvedConfigFileName[] { + return childToParents.getValueOrUndefined(childConfigFileName) || []; + } + + function getKeys(): ReadonlyArray { + return allKeys.getKeys() as ResolvedConfigFileName[]; + } + + function addEntry(mapToAddTo: typeof childToParents | typeof parentToChildren, key: ResolvedConfigFileName, element: ResolvedConfigFileName) { + key = normalizePath(key) as ResolvedConfigFileName; + element = normalizePath(element) as ResolvedConfigFileName; + let arr = mapToAddTo.getValueOrUndefined(key); + if (arr === undefined) { + mapToAddTo.setValue(key, arr = []); + } + if (arr.indexOf(element) < 0) { + arr.push(element); + } + allKeys.setValue(key, true); + allKeys.setValue(element, true); + } + + return { + addReference, + getReferencesTo, + getReferencesOf, + getKeys + }; + } + + function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) { + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); + const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); + return changeExtension(outputPath, Extension.Dts); + } + + function getOutputJavaScriptFileName(inputFileName: string, configFile: ParsedCommandLine) { + const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true); + const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath); + return changeExtension(outputPath, (fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve) ? Extension.Jsx : Extension.Js); + } + + function getOutputFileNames(inputFileName: string, configFile: ParsedCommandLine): ReadonlyArray { + if (configFile.options.outFile) { + return emptyArray; + } + + const outputs: string[] = []; + outputs.push(getOutputJavaScriptFileName(inputFileName, configFile)); + if (configFile.options.declaration) { + const dts = outputs.push(getOutputDeclarationFileName(inputFileName, configFile)); + if (configFile.options.declarationMap) { + outputs.push(dts + ".map"); + } + } + return outputs; + } + + function getOutFileOutputs(project: ParsedCommandLine): ReadonlyArray { + if (!project.options.outFile) { + return Debug.fail("outFile must be set"); + } + const outputs: string[] = []; + outputs.push(project.options.outFile); + if (project.options.declaration) { + const dts = changeExtension(project.options.outFile, Extension.Dts); + outputs.push(dts); + if (project.options.declarationMap) { + outputs.push(dts + ".map"); + } + } + return outputs; + } + + function rootDirOfOptions(opts: CompilerOptions, configFileName: string) { + return opts.rootDir || getDirectoryPath(configFileName); + } + + function createConfigFileCache(host: CompilerHost) { + const cache = createFileMap(); + const configParseHost = parseConfigHostFromCompilerHost(host); + + function parseConfigFile(configFilePath: ResolvedConfigFileName) { + const sourceFile = host.getSourceFile(configFilePath, ScriptTarget.JSON) as JsonSourceFile; + if (sourceFile === undefined) { + return undefined; + } + + const parsed = parseJsonSourceFileConfigFileContent(sourceFile, configParseHost, getDirectoryPath(configFilePath)); + parsed.options.configFilePath = configFilePath; + cache.setValue(configFilePath, parsed); + return parsed; + } + + function removeKey(configFilePath: ResolvedConfigFileName) { + cache.removeKey(configFilePath); + } + + return { + parseConfigFile, + removeKey + }; + } + + function newer(date1: Date, date2: Date): Date { + return date2 > date1 ? date2 : date1; + } + + function isDeclarationFile(fileName: string) { + return fileExtensionIs(fileName, Extension.Dts); + } + + export function createBuildContext(options: BuildOptions): BuildContext { + const invalidatedProjects = createFileMap(); + const queuedProjects = createFileMap(); + const missingRoots = createMap(); + + return { + options, + projectStatus: createFileMap(), + unchangedOutputs: createFileMap(), + invalidatedProjects, + missingRoots, + queuedProjects + }; + } + + const buildOpts: CommandLineOption[] = [ + { + name: "verbose", + shortName: "v", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Enable_verbose_logging, + type: "boolean" + }, + { + name: "dry", + shortName: "d", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, + type: "boolean" + }, + { + name: "force", + shortName: "f", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, + type: "boolean" + }, + { + name: "clean", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Delete_the_outputs_of_all_projects, + type: "boolean" + }, + { + name: "watch", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Watch_input_files, + type: "boolean" + } + ]; + + export function performBuild(args: string[], compilerHost: CompilerHost, buildHost: BuildHost, system?: System) { + let verbose = false; + let dry = false; + let force = false; + let clean = false; + let watch = false; + + const projects: string[] = []; + for (const arg of args) { + switch (arg.toLowerCase()) { + case "-v": + case "--verbose": + verbose = true; + continue; + case "-d": + case "--dry": + dry = true; + continue; + case "-f": + case "--force": + force = true; + continue; + case "--clean": + clean = true; + continue; + case "--watch": + case "-w": + watch = true; + continue; + + case "--?": + case "-?": + case "--help": + return printHelp(buildOpts, "--build "); + } + // Not a flag, parse as filename + addProject(arg); + } + + // Nonsensical combinations + if (clean && force) { + return buildHost.error(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"); + } + if (clean && verbose) { + return buildHost.error(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"); + } + if (clean && watch) { + return buildHost.error(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"); + } + if (watch && dry) { + return buildHost.error(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"); + } + + if (projects.length === 0) { + // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." + addProject("."); + } + + const builder = createSolutionBuilder(compilerHost, buildHost, projects, { dry, force, verbose }, system); + if (clean) { + builder.cleanAllProjects(); + } + else { + builder.buildAllProjects(); + } + + if (watch) { + return builder.startWatching(); + } + + function addProject(projectSpecification: string) { + const fileName = resolvePath(compilerHost.getCurrentDirectory(), projectSpecification); + const refPath = resolveProjectReferencePath(compilerHost, { path: fileName }); + if (!refPath) { + return buildHost.error(Diagnostics.File_0_does_not_exist, projectSpecification); + } + + if (!compilerHost.fileExists(refPath)) { + return buildHost.error(Diagnostics.File_0_does_not_exist, fileName); + } + projects.push(refPath); + + } + } + + /** + * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but + * can dynamically add/remove other projects based on changes on the rootNames' references + */ + export function createSolutionBuilder(compilerHost: CompilerHost, buildHost: BuildHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions, system?: System) { + if (!compilerHost.getModifiedTime || !compilerHost.setModifiedTime) { + throw new Error("Host must support timestamp APIs"); + } + + const configFileCache = createConfigFileCache(compilerHost); + let context = createBuildContext(defaultOptions); + + const existingWatchersForWildcards = createMap(); + + return { + buildAllProjects, + getUpToDateStatus, + getUpToDateStatusOfFile, + cleanAllProjects, + resetBuildContext, + getBuildGraph, + + invalidateProject, + buildInvalidatedProjects, + buildDependentInvalidatedProjects, + + resolveProjectName, + + startWatching + }; + + function startWatching() { + if (!system) throw new Error("System host must be provided if using --watch"); + if (!system.watchFile || !system.watchDirectory || !system.setTimeout) throw new Error("System host must support watchFile / watchDirectory / setTimeout if using --watch"); + + const graph = getGlobalDependencyGraph()!; + if (!graph.buildQueue) { + // Everything is broken - we don't even know what to watch. Give up. + return; + } + + for (const resolved of graph.buildQueue) { + const cfg = configFileCache.parseConfigFile(resolved); + if (cfg) { + // Watch this file + system.watchFile(resolved, () => { + configFileCache.removeKey(resolved); + invalidateProjectAndScheduleBuilds(resolved); + }); + + // Update watchers for wildcard directories + if (cfg.configFileSpecs) { + updateWatchingWildcardDirectories(existingWatchersForWildcards, createMapFromTemplate(cfg.configFileSpecs.wildcardDirectories), (dir, flags) => { + return system.watchDirectory!(dir, () => { + invalidateProjectAndScheduleBuilds(resolved); + }, !!(flags & WatchDirectoryFlags.Recursive)); + }); + } + + // Watch input files + for (const input of cfg.fileNames) { + system.watchFile(input, () => { + invalidateProjectAndScheduleBuilds(resolved); + }); + } + } + } + + function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName) { + invalidateProject(resolved); + system!.setTimeout!(buildInvalidatedProjects, 100); + system!.setTimeout!(buildDependentInvalidatedProjects, 3000); + } + } + + function resetBuildContext(opts = defaultOptions) { + context = createBuildContext(opts); + } + + function getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus { + return getUpToDateStatus(configFileCache.parseConfigFile(configFileName)); + } + + function getBuildGraph(configFileNames: ReadonlyArray) { + const resolvedNames: ResolvedConfigFileName[] | undefined = resolveProjectNames(configFileNames); + if (resolvedNames === undefined) return undefined; + + return createDependencyGraph(resolvedNames); + } + + function getGlobalDependencyGraph() { + return getBuildGraph(rootNames); + } + + function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { + if (project === undefined) { + return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; + } + + const prior = context.projectStatus.getValueOrUndefined(project.options.configFilePath!); + if (prior !== undefined) { + return prior; + } + const actual = getUpToDateStatusWorker(project); + context.projectStatus.setValue(project.options.configFilePath!, actual); + return actual; + } + + function invalidateProject(configFileName: string) { + const resolved = resolveProjectName(configFileName); + if (resolved === undefined) { + // If this was a rootName, we need to track it as missing. + // Otherwise we can just ignore it and have it possibly surface as an error in any downstream projects, + // if they exist + + // TODO: do those things + return; + } + + configFileCache.removeKey(resolved); + context.invalidatedProjects.setValue(resolved, true); + context.projectStatus.removeKey(resolved); + + const graph = getGlobalDependencyGraph()!; + if (graph) { + queueBuildForDownstreamReferences(resolved); + } + + // Mark all downstream projects of this one needing to be built "later" + function queueBuildForDownstreamReferences(root: ResolvedConfigFileName) { + debugger; + const deps = graph.dependencyMap.getReferencesTo(root); + for (const ref of deps) { + // Can skip circular references + if (!context.queuedProjects.hasKey(ref)) { + context.queuedProjects.setValue(ref, true); + queueBuildForDownstreamReferences(ref); + } + } + } + } + + function buildInvalidatedProjects() { + buildSomeProjects(p => context.invalidatedProjects.hasKey(p)); + } + + function buildDependentInvalidatedProjects() { + buildSomeProjects(p => context.queuedProjects.hasKey(p)); + } + + function buildSomeProjects(predicate: (projName: ResolvedConfigFileName) => boolean) { + const resolvedNames: ResolvedConfigFileName[] | undefined = resolveProjectNames(rootNames); + if (resolvedNames === undefined) return; + + const graph = createDependencyGraph(resolvedNames)!; + for (const next of graph.buildQueue) { + if (!predicate(next)) continue; + + const resolved = resolveProjectName(next); + if (!resolved) continue; // ?? + const proj = configFileCache.parseConfigFile(resolved); + if (!proj) continue; // ? + + const status = getUpToDateStatus(proj); + verboseReportProjectStatus(next, status); + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + if (context.options.verbose) buildHost.verbose(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, resolved, status.upstreamProjectName); + continue; + } + + buildSingleProject(next); + } + } + + function getAllProjectOutputs(project: ParsedCommandLine): ReadonlyArray { + if (project.options.outFile) { + return getOutFileOutputs(project); + } + else { + const outputs: string[] = []; + for (const inputFile of project.fileNames) { + outputs.push(...getOutputFileNames(inputFile, project)); + } + return outputs; + } + } + + function getUpToDateStatusWorker(project: ParsedCommandLine): UpToDateStatus { + let newestInputFileName: string = undefined!; + let newestInputFileTime = minimumDate; + // Get timestamps of input files + for (const inputFile of project.fileNames) { + if (!compilerHost.fileExists(inputFile)) { + return { + type: UpToDateStatusType.Unbuildable, + reason: `${inputFile} does not exist` + }; + } + + const inputTime = compilerHost.getModifiedTime!(inputFile); + if (inputTime > newestInputFileTime) { + newestInputFileName = inputFile; + newestInputFileTime = inputTime; + } + } + + // Collect the expected outputs of this project + const outputs = getAllProjectOutputs(project); + + if (outputs.length === 0) { + return { + type: UpToDateStatusType.ContainerOnly + }; + } + + // Now see if all outputs are newer than the newest input + let oldestOutputFileName = "(none)"; + let oldestOutputFileTime = maximumDate; + let newestOutputFileName = "(none)"; + let newestOutputFileTime = minimumDate; + let missingOutputFileName: string | undefined; + let newestDeclarationFileContentChangedTime = minimumDate; + let isOutOfDateWithInputs = false; + for (const output of outputs) { + // Output is missing; can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (!compilerHost.fileExists(output)) { + missingOutputFileName = output; + break; + } + + const outputTime = compilerHost.getModifiedTime!(output); + if (outputTime < oldestOutputFileTime) { + oldestOutputFileTime = outputTime; + oldestOutputFileName = output; + } + + // If an output is older than the newest input, we can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (outputTime < newestInputFileTime) { + isOutOfDateWithInputs = true; + break; + } + + if (outputTime > newestOutputFileTime) { + newestOutputFileTime = outputTime; + newestOutputFileName = output; + } + + // Keep track of when the most recent time a .d.ts file was changed. + // In addition to file timestamps, we also keep track of when a .d.ts file + // had its file touched but not had its contents changed - this allows us + // to skip a downstream typecheck + if (isDeclarationFile(output)) { + const unchangedTime = context.unchangedOutputs.getValueOrUndefined(output); + if (unchangedTime !== undefined) { + newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime); + } + else { + newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, compilerHost.getModifiedTime!(output)); + } + } + } + + let pseudoUpToDate = false; + if (project.projectReferences) { + for (const ref of project.projectReferences) { + const resolvedRef = resolveProjectReferencePath(compilerHost, ref) as ResolvedConfigFileName; + const refStatus = getUpToDateStatus(configFileCache.parseConfigFile(resolvedRef)); + + // An upstream project is blocked + if (refStatus.type === UpToDateStatusType.Unbuildable) { + return { + type: UpToDateStatusType.UpstreamBlocked, + upstreamProjectName: ref.path + }; + } + + // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) + if (refStatus.type !== UpToDateStatusType.UpToDate) { + return { + type: UpToDateStatusType.UpstreamOutOfDate, + upstreamProjectName: ref.path + }; + } + + // If the upstream project's newest file is older than our oldest output, we + // can't be out of date because of it + if (refStatus.newestInputFileTime <= oldestOutputFileTime) { + continue; + } + + // If the upstream project has only change .d.ts files, and we've built + // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild + if (refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { + pseudoUpToDate = true; + continue; + } + + // We have an output older than an upstream output - we are out of date + Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); + return { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: oldestOutputFileName, + newerProjectName: ref.path + }; + } + } + + if (missingOutputFileName !== undefined) { + return { + type: UpToDateStatusType.OutputMissing, + missingOutputFileName + }; + } + + if (isOutOfDateWithInputs) { + return { + type: UpToDateStatusType.OutOfDateWithSelf, + outOfDateOutputFileName: oldestOutputFileName, + newerInputFileName: newestInputFileName + }; + } + + // Up to date + return { + type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime, + newestInputFileTime, + newestOutputFileTime, + newestInputFileName, + newestOutputFileName, + oldestOutputFileName + }; + } + + function createDependencyGraph(roots: ResolvedConfigFileName[]): DependencyGraph | undefined { + const temporaryMarks: { [path: string]: true } = {}; + const permanentMarks: { [path: string]: true } = {}; + const circularityReportStack: string[] = []; + const buildOrder: ResolvedConfigFileName[] = []; + const graph = createDependencyMapper(); + + let hadError = false; + + for (const root of roots) { + visit(root); + } + + if (hadError) { + return undefined; + } + + return { + buildQueue: buildOrder, + dependencyMap: graph + }; + + function visit(projPath: ResolvedConfigFileName, inCircularContext = false) { + // Already visited + if (permanentMarks[projPath]) return; + // Circular + if (temporaryMarks[projPath]) { + if (!inCircularContext) { + hadError = true; + buildHost.error(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n")); + return; + } + } + + temporaryMarks[projPath] = true; + circularityReportStack.push(projPath); + const parsed = configFileCache.parseConfigFile(projPath); + if (parsed === undefined) { + hadError = true; + return; + } + if (parsed.projectReferences) { + for (const ref of parsed.projectReferences) { + const resolvedRefPath = resolveProjectName(ref.path); + if (resolvedRefPath === undefined) { + hadError = true; + break; + } + visit(resolvedRefPath, inCircularContext || ref.circular); + graph.addReference(projPath, resolvedRefPath); + } + } + + circularityReportStack.pop(); + permanentMarks[projPath] = true; + buildOrder.push(projPath); + } + } + + function buildSingleProject(proj: ResolvedConfigFileName): BuildResultFlags { + if (context.options.dry) { + buildHost.message(Diagnostics.A_non_dry_build_would_build_project_0, proj); + return BuildResultFlags.Success; + } + + if (context.options.verbose) buildHost.verbose(Diagnostics.Building_project_0, proj); + + let resultFlags = BuildResultFlags.None; + resultFlags |= BuildResultFlags.DeclarationOutputUnchanged; + + const configFile = configFileCache.parseConfigFile(proj); + if (!configFile) { + // Failed to read the config file + resultFlags |= BuildResultFlags.ConfigFileErrors; + context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); + return resultFlags; + } + + if (configFile.fileNames.length === 0) { + // Nothing to build - must be a solution file, basically + return BuildResultFlags.None; + } + + const programOptions: CreateProgramOptions = { + projectReferences: configFile.projectReferences, + host: compilerHost, + rootNames: configFile.fileNames, + options: configFile.options + }; + const program = createProgram(programOptions); + + // Don't emit anything in the presence of syntactic errors or options diagnostics + const syntaxDiagnostics = [...program.getOptionsDiagnostics(), ...program.getSyntacticDiagnostics()]; + if (syntaxDiagnostics.length) { + resultFlags |= BuildResultFlags.SyntaxErrors; + for (const diag of syntaxDiagnostics) { + buildHost.errorDiagnostic(diag); + } + context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" }); + return resultFlags; + } + + // Don't emit .d.ts if there are decl file errors + if (program.getCompilerOptions().declaration) { + const declDiagnostics = program.getDeclarationDiagnostics(); + if (declDiagnostics.length) { + resultFlags |= BuildResultFlags.DeclarationEmitErrors; + for (const diag of declDiagnostics) { + buildHost.errorDiagnostic(diag); + } + context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" }); + return resultFlags; + } + } + + // Same as above but now for semantic diagnostics + const semanticDiagnostics = program.getSemanticDiagnostics(); + if (semanticDiagnostics.length) { + resultFlags |= BuildResultFlags.TypeErrors; + for (const diag of semanticDiagnostics) { + buildHost.errorDiagnostic(diag); + } + context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" }); + return resultFlags; + } + + let newestDeclarationFileContentChangedTime = minimumDate; + program.emit(/*targetSourceFile*/ undefined, (fileName, content, writeBom, onError) => { + let priorChangeTime: Date | undefined; + + if (isDeclarationFile(fileName) && compilerHost.fileExists(fileName)) { + if (compilerHost.readFile(fileName) === content) { + // Check for unchanged .d.ts files + resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; + priorChangeTime = compilerHost.getModifiedTime && compilerHost.getModifiedTime(fileName); + } + } + + compilerHost.writeFile(fileName, content, writeBom, onError, emptyArray); + if (priorChangeTime !== undefined) { + newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); + context.unchangedOutputs.setValue(fileName, priorChangeTime); + } + }); + + context.projectStatus.setValue(proj, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime } as UpToDateStatus); + return resultFlags; + } + + function updateOutputTimestamps(proj: ParsedCommandLine) { + if (context.options.dry) { + return buildHost.message(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!); + } + + if (context.options.verbose) buildHost.verbose(Diagnostics.Updating_output_timestamps_of_project_0, proj.options.configFilePath!); + const now = new Date(); + const outputs = getAllProjectOutputs(proj); + let priorNewestUpdateTime = minimumDate; + for (const file of outputs) { + if (isDeclarationFile(file)) { + priorNewestUpdateTime = newer(priorNewestUpdateTime, compilerHost.getModifiedTime!(file)); + } + compilerHost.setModifiedTime!(file, now); + } + + context.projectStatus.setValue(proj.options.configFilePath!, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus); + } + + function getFilesToClean(configFileNames: ReadonlyArray): string[] | undefined { + const resolvedNames: ResolvedConfigFileName[] | undefined = resolveProjectNames(configFileNames); + if (resolvedNames === undefined) return undefined; + + // Get the same graph for cleaning we'd use for building + const graph = createDependencyGraph(resolvedNames); + if (graph === undefined) return undefined; + + const filesToDelete: string[] = []; + for (const proj of graph.buildQueue) { + const parsed = configFileCache.parseConfigFile(proj); + if (parsed === undefined) { + // File has gone missing; fine to ignore here + continue; + } + const outputs = getAllProjectOutputs(parsed); + for (const output of outputs) { + if (compilerHost.fileExists(output)) { + filesToDelete.push(output); + } + } + } + return filesToDelete; + } + + function getAllProjectsInScope(): ReadonlyArray | undefined { + const resolvedNames = resolveProjectNames(rootNames); + if (resolvedNames === undefined) return undefined; + const graph = createDependencyGraph(resolvedNames); + if (graph === undefined) return undefined; + return graph.buildQueue; + } + + function cleanAllProjects() { + const resolvedNames: ReadonlyArray | undefined = getAllProjectsInScope(); + if (resolvedNames === undefined) { + return buildHost.message(Diagnostics.Skipping_clean_because_not_all_projects_could_be_located); + } + + const filesToDelete = getFilesToClean(resolvedNames); + if (filesToDelete === undefined) { + return buildHost.message(Diagnostics.Skipping_clean_because_not_all_projects_could_be_located); + } + + if (context.options.dry) { + return buildHost.message(Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); + } + + // Do this check later to allow --clean --dry to function even if the host can't delete files + if (!compilerHost.deleteFile) { + throw new Error("Host does not support deleting files"); + } + + for (const output of filesToDelete) { + compilerHost.deleteFile(output); + } + } + + function resolveProjectName(name: string): ResolvedConfigFileName | undefined { + const fullPath = resolvePath(compilerHost.getCurrentDirectory(), name); + if (compilerHost.fileExists(fullPath)) { + return fullPath as ResolvedConfigFileName; + } + const fullPathWithTsconfig = combinePaths(fullPath, "tsconfig.json"); + if (compilerHost.fileExists(fullPathWithTsconfig)) { + return fullPathWithTsconfig as ResolvedConfigFileName; + } + buildHost.error(Diagnostics.File_0_not_found, relName(fullPath)); + return undefined; + } + + function resolveProjectNames(configFileNames: ReadonlyArray): ResolvedConfigFileName[] | undefined { + const resolvedNames: ResolvedConfigFileName[] = []; + for (const name of configFileNames) { + const resolved = resolveProjectName(name); + if (resolved === undefined) { + return undefined; + } + resolvedNames.push(resolved); + } + return resolvedNames; + } + + function buildAllProjects() { + const graph = getGlobalDependencyGraph(); + if (graph === undefined) return; + + const queue = graph.buildQueue; + reportBuildQueue(graph); + + for (const next of queue) { + const proj = configFileCache.parseConfigFile(next); + if (proj === undefined) { + break; + } + const status = getUpToDateStatus(proj); + verboseReportProjectStatus(next, status); + + const projName = proj.options.configFilePath!; + if (status.type === UpToDateStatusType.UpToDate && !context.options.force) { + // Up to date, skip + if (defaultOptions.dry) { + // In a dry build, inform the user of this fact + buildHost.message(Diagnostics.Project_0_is_up_to_date, projName); + } + continue; + } + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !context.options.force) { + // Fake build + updateOutputTimestamps(proj); + continue; + } + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + if (context.options.verbose) buildHost.verbose(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName); + continue; + } + + if (status.type === UpToDateStatusType.ContainerOnly) { + // Do nothing + continue; + } + + buildSingleProject(next); + } + } + + /** + * Report the build ordering inferred from the current project graph if we're in verbose mode + */ + function reportBuildQueue(graph: DependencyGraph) { + if (!context.options.verbose) return; + + const names: string[] = []; + for (const name of graph.buildQueue) { + names.push(name); + } + if (context.options.verbose) buildHost.verbose(Diagnostics.Projects_in_this_build_Colon_0, names.map(s => "\r\n * " + relName(s)).join("")); + } + + function relName(path: string): string { + return convertToRelativePath(path, compilerHost.getCurrentDirectory(), f => compilerHost.getCanonicalFileName(f)); + } + + /** + * Report the up-to-date status of a project if we're in verbose mode + */ + function verboseReportProjectStatus(configFileName: string, status: UpToDateStatus) { + if (!context.options.verbose) return; + switch (status.type) { + case UpToDateStatusType.OutOfDateWithSelf: + return buildHost.verbose(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(configFileName), + relName(status.outOfDateOutputFileName), + relName(status.newerInputFileName)); + case UpToDateStatusType.OutOfDateWithUpstream: + return buildHost.verbose(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(configFileName), + relName(status.outOfDateOutputFileName), + relName(status.newerProjectName)); + case UpToDateStatusType.OutputMissing: + return buildHost.verbose(Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + relName(configFileName), + relName(status.missingOutputFileName)); + case UpToDateStatusType.UpToDate: + if (status.newestInputFileTime !== undefined) { + return buildHost.verbose(Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + relName(configFileName), + relName(status.newestInputFileName), + relName(status.oldestOutputFileName)); + } + // Don't report anything for "up to date because it was already built" -- too verbose + break; + case UpToDateStatusType.UpToDateWithUpstreamTypes: + return buildHost.verbose(Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + relName(configFileName)); + case UpToDateStatusType.UpstreamOutOfDate: + return buildHost.verbose(Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, + relName(configFileName), + relName(status.upstreamProjectName)); + case UpToDateStatusType.UpstreamBlocked: + return buildHost.verbose(Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, + relName(configFileName), + relName(status.upstreamProjectName)); + case UpToDateStatusType.Unbuildable: + return buildHost.verbose(Diagnostics.Failed_to_parse_file_0_Colon_1, + relName(configFileName), + status.reason); + case UpToDateStatusType.ContainerOnly: + // Don't report status on "solution" projects + break; + default: + assertTypeIsNever(status); + } + } + } +} diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index e3e0ea08f53..93834a6ea54 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -12,11 +12,6 @@ namespace ts { return count; } - function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText; - } - let reportDiagnostic = createDiagnosticReporter(sys); function updateReportDiagnostic(options: CompilerOptions) { if (shouldBePretty(options)) { @@ -46,9 +41,33 @@ namespace ts { return s; } + function getOptionsForHelp(commandLine: ParsedCommandLine) { + // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") + return !!commandLine.options.all ? + sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) : + filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView); + } + export function executeCommandLine(args: string[]): void { + if (args.length > 0 && ((args[0].toLowerCase() === "--build") || (args[0].toLowerCase() === "-b"))) { + const reportDiag = createDiagnosticReporter(sys, /*pretty*/ true); + const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args)); + const buildHost: BuildHost = { + error: report, + verbose: report, + message: report, + errorDiagnostic: d => reportDiag(d) + }; + return performBuild(args.slice(1), createCompilerHost({}), buildHost, sys); + } + const commandLine = parseCommandLine(args); + if (commandLine.options.build) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_build_must_be_the_first_command_line_argument)); + return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + // Configuration file name (if any) let configFileName: string | undefined; if (commandLine.options.locale) { @@ -74,7 +93,7 @@ namespace ts { if (commandLine.options.help || commandLine.options.all) { printVersion(); - printHelp(!!commandLine.options.all); + printHelp(getOptionsForHelp(commandLine)); return sys.exit(ExitStatus.Success); } @@ -107,7 +126,7 @@ namespace ts { if (commandLine.fileNames.length === 0 && !configFileName) { printVersion(); - printHelp(!!commandLine.options.all); + printHelp(getOptionsForHelp(commandLine)); return sys.exit(ExitStatus.Success); } @@ -271,122 +290,6 @@ namespace ts { } } - function printVersion() { - sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine); - } - - function printHelp(showAllOptions: boolean) { - const output: string[] = []; - - // We want to align our "syntax" and "examples" commands to a certain margin. - const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; - const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; - let marginLength = Math.max(syntaxLength, examplesLength); - - // Build up the syntactic skeleton. - let syntax = makePadding(marginLength - syntaxLength); - syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; - - output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); - output.push(sys.newLine + sys.newLine); - - // Build up the list of examples. - const padding = makePadding(marginLength); - output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); - output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); - output.push(padding + "tsc @args.txt" + sys.newLine); - output.push(sys.newLine); - - output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); - - // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") - const optsList = showAllOptions ? - sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) : - filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView); - - // We want our descriptions to align at the same column in our output, - // so we keep track of the longest option usage string. - marginLength = 0; - const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. - const descriptionColumn: string[] = []; - - const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind - - for (const option of optsList) { - // If an option lacks a description, - // it is not officially supported. - if (!option.description) { - continue; - } - - let usageText = " "; - if (option.shortName) { - usageText += "-" + option.shortName; - usageText += getParamType(option); - usageText += ", "; - } - - usageText += "--" + option.name; - usageText += getParamType(option); - - usageColumn.push(usageText); - let description: string; - - if (option.name === "lib") { - description = getDiagnosticText(option.description); - const element = (option).element; - const typeMap = >element.type; - optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); - } - else { - description = getDiagnosticText(option.description); - } - - descriptionColumn.push(description); - - // Set the new margin for the description column if necessary. - marginLength = Math.max(usageText.length, marginLength); - } - - // Special case that can't fit in the loop. - const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; - usageColumn.push(usageText); - descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); - marginLength = Math.max(usageText.length, marginLength); - - // Print out each row, aligning all the descriptions on the same column. - for (let i = 0; i < usageColumn.length; i++) { - const usage = usageColumn[i]; - const description = descriptionColumn[i]; - const kindsList = optionsDescriptionMap.get(description); - output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); - - if (kindsList) { - output.push(makePadding(marginLength + 4)); - for (const kind of kindsList) { - output.push(kind + " "); - } - output.push(sys.newLine); - } - } - - for (const line of output) { - sys.write(line); - } - return; - - function getParamType(option: CommandLineOption) { - if (option.paramType !== undefined) { - return " " + getDiagnosticText(option.paramType); - } - return ""; - } - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - } - function writeConfigFile(options: CompilerOptions, fileNames: string[]) { const currentDirectory = sys.getCurrentDirectory(); const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 5716a2a417d..b2ed458b8b4 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -47,6 +47,7 @@ "moduleSpecifiers.ts", "watch.ts", "commandLineParser.ts", - "tsc.ts" + "tsbuild.ts", + "tsc.ts", ] } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2b493921f89..1378be2a724 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2558,6 +2558,7 @@ namespace ts { fileName: string; /* @internal */ path: Path; text: string; + /* @internal */ resolvedPath: Path; /** * If two source files are for the same version of the same package, one will redirect to the other. @@ -2658,12 +2659,15 @@ namespace ts { export interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } export interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } export interface JsonSourceFile extends SourceFile { @@ -3016,6 +3020,8 @@ namespace ts { /* @internal */ getStringType(): Type; /* @internal */ getNumberType(): Type; /* @internal */ getBooleanType(): Type; + /* @internal */ getFalseType(): Type; + /* @internal */ getTrueType(): Type; /* @internal */ getVoidType(): Type; /* @internal */ getUndefinedType(): Type; /* @internal */ getNullType(): Type; @@ -3054,12 +3060,12 @@ namespace ts { /* @internal */ getSymbolCount(): number; /* @internal */ getTypeCount(): number; + /* @internal */ isArrayLikeType(type: Type): boolean; /** * For a union, will include a property if it's defined in *any* of the member types. * So for `{ a } | { b }`, this will include both `a` and `b`. * Does not include properties of primitive types. */ - /* @internal */ isArrayLikeType(type: Type): boolean; /* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray): Symbol[]; /* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(location?: Node): string; @@ -4295,6 +4301,9 @@ namespace ts { allowUnusedLabels?: boolean; alwaysStrict?: boolean; // Always combine with strict property baseUrl?: string; + /** An error if set - this should only go through the -b pipeline and not actually be observed */ + /*@internal*/ + build?: boolean; charset?: string; checkJs?: boolean; /* @internal */ configFilePath?: string; @@ -4817,6 +4826,10 @@ namespace ts { /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; + + getModifiedTime?(fileName: string): Date; + setModifiedTime?(fileName: string, date: Date): void; + deleteFile?(fileName: string): void; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8081f964ec4..156555124bd 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2861,13 +2861,26 @@ namespace ts { let lineCount: number; let linePos: number; + function updateLineCountAndPosFor(s: string) { + const lineStartsOfS = computeLineStarts(s); + if (lineStartsOfS.length > 1) { + lineCount = lineCount + lineStartsOfS.length - 1; + linePos = output.length - s.length + last(lineStartsOfS); + lineStart = (linePos - output.length) === 0; + } + else { + lineStart = false; + } + } + function write(s: string) { if (s && s.length) { if (lineStart) { - output += getIndentString(indent); + s = getIndentString(indent) + s; lineStart = false; } output += s; + updateLineCountAndPosFor(s); } } @@ -2881,21 +2894,14 @@ namespace ts { function rawWrite(s: string) { if (s !== undefined) { - if (lineStart) { - lineStart = false; - } output += s; + updateLineCountAndPosFor(s); } } function writeLiteral(s: string) { if (s && s.length) { write(s); - const lineStartsOfS = computeLineStarts(s); - if (lineStartsOfS.length > 1) { - lineCount = lineCount + lineStartsOfS.length - 1; - linePos = output.length - s.length + last(lineStartsOfS); - } } } @@ -2909,7 +2915,9 @@ namespace ts { } function writeTextOfNode(text: string, node: Node) { - write(getTextOfNodeFromSourceText(text, node)); + const s = getTextOfNodeFromSourceText(text, node); + write(s); + updateLineCountAndPosFor(s); } reset(); @@ -5487,6 +5495,10 @@ namespace ts { return node.kind === SyntaxKind.Bundle; } + export function isUnparsedSource(node: Node): node is UnparsedSource { + return node.kind === SyntaxKind.UnparsedSource; + } + // JSDoc export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 84d9f1013f2..1bb358698a2 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -51,6 +51,10 @@ namespace fakes { this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data); } + public deleteFile(path: string) { + this.vfs.unlinkSync(path); + } + public fileExists(path: string) { const stats = this._getStats(path); return stats ? stats.isFile() : false; @@ -131,6 +135,10 @@ namespace fakes { return stats ? stats.mtime : undefined!; // TODO: GH#18217 } + public setModifiedTime(path: string, time: Date) { + this.vfs.utimesSync(path, time, time); + } + public createHash(data: string): string { return data; } @@ -244,6 +252,10 @@ namespace fakes { return this.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(); } + public deleteFile(fileName: string) { + this.sys.deleteFile(fileName); + } + public fileExists(fileName: string): boolean { return this.sys.fileExists(fileName); } @@ -252,6 +264,14 @@ namespace fakes { return this.sys.directoryExists(directoryName); } + public getModifiedTime(fileName: string) { + return this.sys.getModifiedTime(fileName); + } + + public setModifiedTime(fileName: string, time: Date) { + return this.sys.setModifiedTime(fileName, time); + } + public getDirectories(path: string): string[] { return this.sys.getDirectories(path); } @@ -312,7 +332,7 @@ namespace fakes { if (cacheKey) { const meta = this.vfs.filemeta(canonicalFileName); const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined; - if (sourceFileFromMetadata) { + if (sourceFileFromMetadata && sourceFileFromMetadata.getFullText() === content) { this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata); return sourceFileFromMetadata; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2ace3df3b42..532ce5e0488 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2869,6 +2869,7 @@ Actual: ${stringify(fullActual)}`); function replacer(key: string, value: any) { switch (key) { case "spans": + case "nameSpan": return options && options.checkSpans ? value : undefined; case "start": case "length": diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index c401b40a339..a62975b0300 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -53,6 +53,7 @@ "../compiler/resolutionCache.ts", "../compiler/moduleSpecifiers.ts", "../compiler/watch.ts", + "../compiler/tsbuild.ts", "../compiler/commandLineParser.ts", "../services/types.ts", diff --git a/src/harness/unittests/evaluation/asyncArrow.ts b/src/harness/unittests/evaluation/asyncArrow.ts new file mode 100644 index 00000000000..994fe8a84be --- /dev/null +++ b/src/harness/unittests/evaluation/asyncArrow.ts @@ -0,0 +1,18 @@ +describe("asyncArrowEvaluation", () => { + // https://github.com/Microsoft/TypeScript/issues/24722 + it("this capture (es5)", async () => { + const result = evaluator.evaluateTypeScript(` + export class A { + b = async (...args: any[]) => { + await Promise.resolve(); + output.push({ ["a"]: () => this }); // computed property name after 'await' triggers case + }; + } + export const output: any[] = []; + export async function main() { + await new A().b(); + }`); + await result.main(); + assert.instanceOf(result.output[0].a(), result.A); + }); +}); \ No newline at end of file diff --git a/src/harness/unittests/tsbuild.ts b/src/harness/unittests/tsbuild.ts new file mode 100644 index 00000000000..9d093f61e09 --- /dev/null +++ b/src/harness/unittests/tsbuild.ts @@ -0,0 +1,425 @@ +namespace ts { + let currentTime = 100; + let lastDiagnostics: Diagnostic[] = []; + const reportDiagnostic: DiagnosticReporter = diagnostic => lastDiagnostics.push(diagnostic); + const report = (message: DiagnosticMessage, ...args: string[]) => reportDiagnostic(createCompilerDiagnostic(message, ...args)); + const buildHost: BuildHost = { + error: report, + verbose: report, + message: report, + errorDiagnostic: d => reportDiagnostic(d) + }; + + export namespace Sample1 { + tick(); + const projFs = loadProjectFromDisk("../../tests/projects/sample1"); + + const allExpectedOutputs = ["/src/tests/index.js", + "/src/core/index.js", "/src/core/index.d.ts", + "/src/logic/index.js", "/src/logic/index.d.ts"]; + + describe("tsbuild - sanity check of clean build of 'sample1' project", () => { + it("can build the sample project 'sample1' without error", () => { + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false }); + + clearDiagnostics(); + builder.buildAllProjects(); + assertDiagnosticMessages(/*empty*/); + + // Check for outputs. Not an exhaustive list + for (const output of allExpectedOutputs) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + }); + }); + + describe("tsbuild - dry builds", () => { + it("doesn't write any files in a dry build", () => { + clearDiagnostics(); + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false }); + builder.buildAllProjects(); + assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0); + + // Check for outputs to not be written. Not an exhaustive list + for (const output of allExpectedOutputs) { + assert(!fs.existsSync(output), `Expect file ${output} to not exist`); + } + }); + + it("indicates that it would skip builds during a dry build", () => { + clearDiagnostics(); + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + + let builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false }); + builder.buildAllProjects(); + tick(); + + clearDiagnostics(); + builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false }); + builder.buildAllProjects(); + assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date); + }); + }); + + describe("tsbuild - clean builds", () => { + it("removes all files it built", () => { + clearDiagnostics(); + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false }); + builder.buildAllProjects(); + // Verify they exist + for (const output of allExpectedOutputs) { + assert(fs.existsSync(output), `Expect file ${output} to exist`); + } + builder.cleanAllProjects(); + // Verify they are gone + for (const output of allExpectedOutputs) { + assert(!fs.existsSync(output), `Expect file ${output} to not exist`); + } + // Subsequent clean shouldn't throw / etc + builder.cleanAllProjects(); + }); + }); + + describe("tsbuild - force builds", () => { + it("always builds under --force", () => { + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: true, verbose: false }); + builder.buildAllProjects(); + let currentTime = time(); + checkOutputTimestamps(currentTime); + + tick(); + Debug.assert(time() !== currentTime, "Time moves on"); + currentTime = time(); + builder.buildAllProjects(); + checkOutputTimestamps(currentTime); + + function checkOutputTimestamps(expected: number) { + // Check timestamps + for (const output of allExpectedOutputs) { + const actual = fs.statSync(output).mtimeMs; + assert(actual === expected, `File ${output} has timestamp ${actual}, expected ${expected}`); + } + } + }); + }); + + describe("tsbuild - can detect when and what to rebuild", () => { + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true }); + + it("Builds the project", () => { + clearDiagnostics(); + builder.resetBuildContext(); + builder.buildAllProjects(); + assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0); + tick(); + }); + + // All three projects are up to date + it("Detects that all projects are up to date", () => { + clearDiagnostics(); + builder.resetBuildContext(); + builder.buildAllProjects(); + assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2); + tick(); + }); + + // Update a file in the leaf node (tests), only it should rebuild the last one + it("Only builds the leaf node project", () => { + clearDiagnostics(); + fs.writeFileSync("/src/tests/index.ts", "const m = 10;"); + builder.resetBuildContext(); + builder.buildAllProjects(); + + assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + Diagnostics.Building_project_0); + tick(); + }); + + // Update a file in the parent (without affecting types), should get fast downstream builds + it("Detects type-only changes in upstream projects", () => { + clearDiagnostics(); + replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"); + builder.resetBuildContext(); + builder.buildAllProjects(); + + assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + Diagnostics.Updating_output_timestamps_of_project_0, + Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + Diagnostics.Updating_output_timestamps_of_project_0); + }); + }); + + describe("tsbuild - downstream-blocked compilations", () => { + it("won't build downstream projects if upstream projects have errors", () => { + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true }); + + clearDiagnostics(); + + // Induce an error in the middle project + replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`); + builder.buildAllProjects(); + assertDiagnosticMessages( + Diagnostics.Projects_in_this_build_Colon_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Property_0_does_not_exist_on_type_1, + Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, + Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors + ); + }); + }); + + describe("tsbuild - project invalidation", () => { + it("invalidates projects correctly", () => { + const fs = projFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false }); + + clearDiagnostics(); + builder.buildAllProjects(); + assertDiagnosticMessages(/*empty*/); + + // Update a timestamp in the middle project + tick(); + touch(fs, "/src/logic/index.ts"); + // Because we haven't reset the build context, the builder should assume there's nothing to do right now + const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic")!); + assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date"); + + // Rebuild this project + tick(); + builder.invalidateProject("/src/logic"); + builder.buildInvalidatedProjects(); + // The file should be updated + assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt"); + assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt"); + + // Build downstream projects should update 'tests', but not 'core' + tick(); + builder.buildDependentInvalidatedProjects(); + assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt"); + assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt"); + }); + }); + } + + export namespace OutFile { + const outFileFs = loadProjectFromDisk("../../tests/projects/outfile-concat"); + + describe("tsbuild - baseline sectioned sourcemaps", () => { + const fs = outFileFs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false }); + clearDiagnostics(); + builder.buildAllProjects(); + assertDiagnosticMessages(/*none*/); + + const files = [ + "/src/third/thirdjs/output/third-output.js", + "/src/third/thirdjs/output/third-output.js.map" + ]; + + for (const file of files) { + it(`Generates files matching the baseline - ${file}`, () => { + Harness.Baseline.runBaseline(getBaseFileName(file), () => { + return fs.readFileSync(file, "utf-8"); + }); + }); + } + + it(`Generates files matching the baseline - file listing for outFile-concat`, () => { + Harness.Baseline.runBaseline("outfile-concat-fileListing.txt", () => { + return fs.getFileListing(); + }); + }); + }); + } + + describe("tsbuild - graph-ordering", () => { + const fs = new vfs.FileSystem(false); + const host = new fakes.CompilerHost(fs); + const deps: [string, string][] = [ + ["A", "B"], + ["B", "C"], + ["A", "C"], + ["B", "D"], + ["C", "D"], + ["C", "E"], + ["F", "E"] + ]; + + writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps); + + it("orders the graph correctly - specify two roots", () => { + checkGraphOrdering(["A", "G"], ["A", "B", "C", "D", "E", "G"]); + }); + + it("orders the graph correctly - multiple parts of the same graph in various orders", () => { + checkGraphOrdering(["A"], ["A", "B", "C", "D", "E"]); + checkGraphOrdering(["A", "C", "D"], ["A", "B", "C", "D", "E"]); + checkGraphOrdering(["D", "C", "A"], ["A", "B", "C", "D", "E"]); + }); + + it("orders the graph correctly - other orderings", () => { + checkGraphOrdering(["F"], ["F", "E"]); + checkGraphOrdering(["E"], ["E"]); + checkGraphOrdering(["F", "C", "A"], ["A", "B", "C", "D", "E", "F"]); + }); + + function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) { + const builder = createSolutionBuilder(host, buildHost, rootNames, { dry: true, force: false, verbose: false }); + + const projFileNames = rootNames.map(getProjectFileName); + const graph = builder.getBuildGraph(projFileNames); + if (graph === undefined) throw new Error("Graph shouldn't be undefined"); + + assert.sameMembers(graph.buildQueue, expectedBuildSet.map(getProjectFileName)); + + for (const dep of deps) { + const child = getProjectFileName(dep[0]); + if (graph.buildQueue.indexOf(child) < 0) continue; + const parent = getProjectFileName(dep[1]); + assert.isAbove(graph.buildQueue.indexOf(child), graph.buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`); + } + } + + function getProjectFileName(proj: string) { + return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName; + } + + function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] { + const projFileNames: string[] = []; + for (const dep of deps) { + if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`); + if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`); + } + for (const proj of projectNames) { + fileSystem.mkdirpSync(`/project/${proj}`); + fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}"); + const configFileName = getProjectFileName(proj); + const configContent = JSON.stringify({ + compilerOptions: { composite: true }, + files: [`./${proj}.ts`], + references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` })) + }, undefined, 2); + fileSystem.writeFileSync(configFileName, configContent); + projFileNames.push(configFileName); + } + return projFileNames; + } + }); + + + function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); + } + const old = fs.readFileSync(path, "utf-8"); + if (old.indexOf(oldText) < 0) { + throw new Error(`Text "${oldText}" does not exist in file ${path}`); + } + const newContent = old.replace(oldText, newText); + fs.writeFileSync(path, newContent, "utf-8"); + } + + function assertDiagnosticMessages(...expected: DiagnosticMessage[]) { + const actual = lastDiagnostics.slice(); + if (actual.length !== expected.length) { + assert.fail(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`); + } + for (let i = 0; i < actual.length; i++) { + if (actual[i].code !== expected[i].code) { + assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`); + } + } + } + + function clearDiagnostics() { + lastDiagnostics = []; + } + + export function printDiagnostics(header = "== Diagnostics ==") { + const out = createDiagnosticReporter(sys); + sys.write(header + "\r\n"); + for (const d of lastDiagnostics) { + out(d); + } + } + + function tick() { + currentTime += 60_000; + } + + function time() { + return currentTime; + } + + function touch(fs: vfs.FileSystem, path: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); + } + fs.utimesSync(path, new Date(time()), new Date(time())); + } + + function loadProjectFromDisk(root: string): vfs.FileSystem { + const fs = new vfs.FileSystem(/*ignoreCase*/ false, { time }); + const rootPath = resolvePath(__dirname, root); + loadFsMirror(fs, rootPath, "/src"); + fs.mkdirpSync("/lib"); + const libs = ["es5", "dom", "webworker.importscripts", "scripthost"]; + for (const lib of libs) { + const content = Harness.IO.readFile(combinePaths(Harness.libFolder, `lib.${lib}.d.ts`)); + if (content === undefined) { + throw new Error(`Failed to read lib ${lib}`); + } + fs.writeFileSync(`/lib/lib.${lib}.d.ts`, content); + } + fs.writeFileSync("/lib/lib.d.ts", Harness.IO.readFile(combinePaths(Harness.libFolder, "lib.d.ts"))!); + fs.meta.set("defaultLibLocation", "/lib"); + fs.makeReadonly(); + return fs; + } + + function loadFsMirror(vfs: vfs.FileSystem, localRoot: string, virtualRoot: string) { + vfs.mkdirpSync(virtualRoot); + for (const path of Harness.IO.readDirectory(localRoot)) { + const file = getBaseFileName(path); + vfs.writeFileSync(virtualRoot + "/" + file, Harness.IO.readFile(localRoot + "/" + file)!); + } + for (const dir of Harness.IO.getDirectories(localRoot)) { + loadFsMirror(vfs, localRoot + "/" + dir, virtualRoot + "/" + dir); + } + } +} \ No newline at end of file diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index aa1f4143fc8..70341b18368 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -466,7 +466,7 @@ namespace ts.projectSystem { return newRequest; } - export function openFilesForSession(files: File[], session: server.Session) { + export function openFilesForSession(files: ReadonlyArray, session: server.Session) { for (const file of files) { const request = makeSessionRequest(CommandNames.Open, { file: file.path }); session.executeCommand(request); @@ -6192,6 +6192,69 @@ namespace ts.projectSystem { renameLocation: { line: 2, offset: 3 }, }); }); + + it("handles text changes in tsconfig.json", () => { + const aTs = { + path: "/a.ts", + content: "export const a = 0;", + }; + const tsconfig = { + path: "/tsconfig.json", + content: '{ "files": ["./a.ts"] }', + }; + + const session = createSession(createServerHost([aTs, tsconfig])); + openFilesForSession([aTs], session); + + const response1 = session.executeCommandSeq({ + command: server.protocol.CommandTypes.GetEditsForRefactor, + arguments: { + refactor: "Move to a new file", + action: "Move to a new file", + file: "/a.ts", + startLine: 1, + startOffset: 1, + endLine: 1, + endOffset: 20, + }, + }).response; + assert.deepEqual(response1, { + edits: [ + { + fileName: "/a.ts", + textChanges: [ + { + start: { line: 1, offset: 1 }, + end: { line: 1, offset: 20 }, + newText: "", + }, + ], + }, + { + fileName: "/tsconfig.json", + textChanges: [ + { + start: { line: 1, offset: 21 }, + end: { line: 1, offset: 21 }, + newText: ", \"./a.1.ts\"", + }, + ], + }, + { + fileName: "/a.1.ts", + textChanges: [ + { + start: { line: 0, offset: 0 }, + end: { line: 0, offset: 0 }, + newText: "export const a = 0;", + }, + ], + } + ], + renameFilename: undefined, + renameLocation: undefined, + }); + }); }); describe("tsserverProjectSystem CachingFileSystemInformation", () => { @@ -7501,8 +7564,8 @@ namespace ts.projectSystem { }); describe("tsserverProjectSystem Watched recursive directories with windows style file system", () => { - function verifyWatchedDirectories(useProjectAtRoot: boolean) { - const root = useProjectAtRoot ? "c:/" : "c:/myfolder/allproject/"; + function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { + const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; const configFile: File = { path: root + "project/tsconfig.json", content: "{}" @@ -7531,12 +7594,22 @@ namespace ts.projectSystem { ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); } - it("When project is in rootFolder", () => { - verifyWatchedDirectories(/*useProjectAtRoot*/ true); + function verifyRootedDirectoryWatch(rootedPath: string) { + it("When project is in rootFolder of style c:/", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); + }); + + it("When files at some folder other than root", () => { + verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); + }); + } + + describe("for rootFolder of style c:/", () => { + verifyRootedDirectoryWatch("c:/"); }); - it("When files at some folder other than root", () => { - verifyWatchedDirectories(/*useProjectAtRoot*/ false); + describe("for rootFolder of style c:/users/username", () => { + verifyRootedDirectoryWatch("c:/users/username/"); }); }); diff --git a/src/harness/vfs.ts b/src/harness/vfs.ts index 2bae5303996..2c1c6f2dd54 100644 --- a/src/harness/vfs.ts +++ b/src/harness/vfs.ts @@ -5,6 +5,11 @@ namespace vfs { */ export const builtFolder = "/.ts"; + /** + * Posix-style path to additional mountable folders (./tests/projects in this repo) + */ + export const projectsFolder = "/.projects"; + /** * Posix-style path to additional test libraries */ @@ -348,10 +353,7 @@ namespace vfs { if (!result.node) this._mkdir(result); } - /** - * Print diagnostic information about the structure of the file system to the console. - */ - public debugPrint(): void { + public getFileListing(): string { let result = ""; const printLinks = (dirname: string | undefined, links: collections.SortedMap) => { const iterator = collections.getIterator(links); @@ -379,7 +381,14 @@ namespace vfs { } }; printLinks(/*dirname*/ undefined, this._getRootLinks()); - console.log(result); + return result; + } + + /** + * Print diagnostic information about the structure of the file system to the console. + */ + public debugPrint(): void { + console.log(this.getFileListing()); } // POSIX API (aligns with NodeJS "fs" module API) @@ -404,7 +413,25 @@ namespace vfs { } /** - * Get file status. + * Change file access times + * + * NOTE: do not rename this method as it is intended to align with the same named export of the "fs" module. + */ + public utimesSync(path: string, atime: Date, mtime: Date) { + if (this.isReadonly) throw createIOError("EROFS"); + if (!isFinite(+atime) || !isFinite(+mtime)) throw createIOError("EINVAL"); + + const entry = this._walk(this._resolve(path)); + if (!entry || !entry.node) { + throw createIOError("ENOENT"); + } + entry.node.atimeMs = +atime; + entry.node.mtimeMs = +mtime; + entry.node.ctimeMs = this.time(); + } + + /** + * Get file status. If `path` is a symbolic link, it is dereferenced. * * @link http://pubs.opengroup.org/onlinepubs/9699919799/functions/lstat.html * @@ -414,9 +441,10 @@ namespace vfs { return this._stat(this._walk(this._resolve(path), /*noFollow*/ true)); } + private _stat(entry: WalkResult) { const node = entry.node; - if (!node) throw createIOError("ENOENT"); + if (!node) throw createIOError(`ENOENT`, entry.realpath); return new Stats( node.dev, node.ino, @@ -1127,8 +1155,8 @@ namespace vfs { EROFS: "file system is read-only" }); - export function createIOError(code: keyof typeof IOErrorMessages) { - const err: NodeJS.ErrnoException = new Error(`${code}: ${IOErrorMessages[code]}`); + export function createIOError(code: keyof typeof IOErrorMessages, details = "") { + const err: NodeJS.ErrnoException = new Error(`${code}: ${IOErrorMessages[code]} ${details}`); err.code = code; if (Error.captureStackTrace) Error.captureStackTrace(err, createIOError); return err; @@ -1282,6 +1310,7 @@ namespace vfs { files: { [builtFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "built/local"), resolver), [testLibFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/lib"), resolver), + [projectsFolder]: new Mount(vpath.resolve(host.getWorkspaceRoot(), "tests/projects"), resolver), [srcFolder]: {} }, cwd: srcFolder, diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl index d2c010d954d..1d250c68944 100644 --- a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1947,12 +1947,18 @@ + + + + + + diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index 01118e162e7..d13446474d0 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1947,6 +1947,18 @@ + + + + + + + + + + + + @@ -8721,6 +8733,15 @@ + + + + + + + + + diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index f542f06a4e4..5c8c80e51d2 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1935,6 +1935,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 4352021fac7..803851db20c 100644 --- a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1935,6 +1935,18 @@ + + + + + + + + + + + + @@ -8712,6 +8724,9 @@ + + + diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index b0d68c1af91..591661b36f2 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1935,6 +1935,18 @@ + + + + + + + + + + + + @@ -8709,6 +8721,15 @@ + + + + + + + + + diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl index 5c86135cbf9..bdabbd3cf7d 100644 --- a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1928,6 +1928,18 @@ + + + + + + + + + + + + @@ -8699,6 +8711,15 @@ + + + + + + + + + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index 9e0ffcb8f8f..282a3593711 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -1934,6 +1934,18 @@ + + + + + + + + + + + + @@ -8708,6 +8720,15 @@ + + + + + + + + + diff --git a/src/server/client.ts b/src/server/client.ts index e797cadb8ca..4cd29266027 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -451,6 +451,7 @@ namespace ts.server { kind: tree.kind, kindModifiers: tree.kindModifiers, spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)), + nameSpan: tree.nameSpan && this.decodeSpan(tree.nameSpan, fileName, lineMap), childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap)) }; } diff --git a/src/server/project.ts b/src/server/project.ts index f6f68f66387..343c4a2e97d 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -550,6 +550,12 @@ namespace ts.server { return this.program.getSourceFileByPath(path); } + /* @internal */ + getSourceFileOrConfigFile(path: Path): SourceFile | undefined { + const options = this.program.getCompilerOptions(); + return path === options.configFilePath ? options.configFile : this.getSourceFile(path); + } + close() { if (this.program) { // if we have a program - release all files that are enlisted in program but arent root @@ -629,8 +635,8 @@ namespace ts.server { return this.rootFiles; } return map(this.program.getSourceFiles(), sourceFile => { - const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path); - Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' is missing.`); + const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.resolvedPath || sourceFile.path); + Debug.assert(!!scriptInfo, "getScriptInfo", () => `scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' / '${sourceFile.resolvedPath}' is missing.`); return scriptInfo!; }); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index be1a6247fb0..3082dbae6aa 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2555,6 +2555,7 @@ namespace ts.server.protocol { kind: ScriptElementKind; kindModifiers: string; spans: TextSpan[]; + nameSpan: TextSpan | undefined; childItems?: NavigationTree[]; } diff --git a/src/server/session.ts b/src/server/session.ts index 3a368587d4b..d24e2addd33 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1507,6 +1507,7 @@ namespace ts.server { kind: tree.kind, kindModifiers: tree.kindModifiers, spans: tree.spans.map(span => this.toLocationTextSpan(span, scriptInfo)), + nameSpan: tree.nameSpan && this.toLocationTextSpan(tree.nameSpan, scriptInfo), childItems: map(tree.childItems, item => this.toLocationNavigationTree(item, scriptInfo)) }; } @@ -1770,20 +1771,10 @@ namespace ts.server { } private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray): protocol.FileCodeEdits[] { - return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName))!)); - } - - private mapTextChangesToCodeEditsUsingScriptinfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo | undefined): protocol.FileCodeEdits { - Debug.assert(!!textChanges.isNewFile === !scriptInfo); - if (scriptInfo) { - return { - fileName: textChanges.fileName, - textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)) - }; - } - else { - return this.convertNewFileTextChangeToCodeEdit(textChanges); - } + return textChanges.map(change => { + const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName)); + return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path)); + }); } private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit { @@ -1794,13 +1785,6 @@ namespace ts.server { }; } - private convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { - Debug.assert(textChanges.textChanges.length === 1); - const change = first(textChanges.textChanges); - Debug.assert(change.span.start === 0 && change.span.length === 0); - return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] }; - } - private getBraceMatching(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.TextSpan[] | TextSpan[] | undefined { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; @@ -2279,6 +2263,34 @@ namespace ts.server { } } + function mapTextChangesToCodeEdits(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits { + Debug.assert(!!textChanges.isNewFile === !sourceFile); + if (sourceFile) { + return { + fileName: textChanges.fileName, + textChanges: textChanges.textChanges.map(textChange => convertTextChangeToCodeEdit(textChange, sourceFile)), + }; + } + else { + return convertNewFileTextChangeToCodeEdit(textChanges); + } + } + + function convertTextChangeToCodeEdit(change: TextChange, sourceFile: SourceFile): protocol.CodeEdit { + return { + start: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start)), + end: convertToLocation(sourceFile.getLineAndCharacterOfPosition(change.span.start + change.span.length)), + newText: change.newText ? change.newText : "", + }; + } + + function convertNewFileTextChangeToCodeEdit(textChanges: FileTextChanges): protocol.FileCodeEdits { + Debug.assert(textChanges.textChanges.length === 1); + const change = first(textChanges.textChanges); + Debug.assert(change.span.start === 0 && change.span.length === 0); + return { fileName: textChanges.fileName, textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: change.newText }] }; + } + export interface HandlerResponse { response?: {}; responseRequired?: boolean; diff --git a/src/services/codefixes/convertToEs6Module.ts b/src/services/codefixes/convertToEs6Module.ts index c763d4da0c8..bd1e02845cd 100644 --- a/src/services/codefixes/convertToEs6Module.ts +++ b/src/services/codefixes/convertToEs6Module.ts @@ -5,10 +5,10 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile, program, preferences } = context; const changes = textChanges.ChangeTracker.with(context, changes => { - const moduleExportsChangedToDefault = convertFileToEs6Module(sourceFile, program.getTypeChecker(), changes, program.getCompilerOptions().target!, preferences); + const moduleExportsChangedToDefault = convertFileToEs6Module(sourceFile, program.getTypeChecker(), changes, program.getCompilerOptions().target!, getQuotePreference(sourceFile, preferences)); if (moduleExportsChangedToDefault) { for (const importingFile of program.getSourceFiles()) { - fixImportOfModuleExports(importingFile, sourceFile, changes, preferences); + fixImportOfModuleExports(importingFile, sourceFile, changes, getQuotePreference(importingFile, preferences)); } } }); @@ -17,7 +17,7 @@ namespace ts.codefix { }, }); - function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker, preferences: UserPreferences) { + function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker, quotePreference: QuotePreference) { for (const moduleSpecifier of importingFile.imports) { const imported = getResolvedModule(importingFile, moduleSpecifier.text); if (!imported || imported.resolvedFileName !== exportingFile.fileName) { @@ -27,7 +27,7 @@ namespace ts.codefix { const importNode = importFromModuleSpecifier(moduleSpecifier); switch (importNode.kind) { case SyntaxKind.ImportEqualsDeclaration: - changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, moduleSpecifier, preferences)); + changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, moduleSpecifier, quotePreference)); break; case SyntaxKind.CallExpression: if (isRequireCall(importNode, /*checkArgumentIsStringLiteralLike*/ false)) { @@ -39,13 +39,13 @@ namespace ts.codefix { } /** @returns Whether we converted a `module.exports =` to a default export. */ - function convertFileToEs6Module(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, target: ScriptTarget, preferences: UserPreferences): ModuleExportsChanged { + function convertFileToEs6Module(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, target: ScriptTarget, quotePreference: QuotePreference): ModuleExportsChanged { const identifiers: Identifiers = { original: collectFreeIdentifiers(sourceFile), additional: createMap() }; const exports = collectExportRenames(sourceFile, checker, identifiers); convertExportsAccesses(sourceFile, exports, changes); let moduleExportsChangedToDefault = false; for (const statement of sourceFile.statements) { - const moduleExportsChanged = convertStatement(sourceFile, statement, checker, changes, identifiers, target, exports, preferences); + const moduleExportsChanged = convertStatement(sourceFile, statement, checker, changes, identifiers, target, exports, quotePreference); moduleExportsChangedToDefault = moduleExportsChangedToDefault || moduleExportsChanged; } return moduleExportsChangedToDefault; @@ -98,10 +98,10 @@ namespace ts.codefix { /** Whether `module.exports =` was changed to `export default` */ type ModuleExportsChanged = boolean; - function convertStatement(sourceFile: SourceFile, statement: Statement, checker: TypeChecker, changes: textChanges.ChangeTracker, identifiers: Identifiers, target: ScriptTarget, exports: ExportRenames, preferences: UserPreferences): ModuleExportsChanged { + function convertStatement(sourceFile: SourceFile, statement: Statement, checker: TypeChecker, changes: textChanges.ChangeTracker, identifiers: Identifiers, target: ScriptTarget, exports: ExportRenames, quotePreference: QuotePreference): ModuleExportsChanged { switch (statement.kind) { case SyntaxKind.VariableStatement: - convertVariableStatement(sourceFile, statement as VariableStatement, changes, checker, identifiers, target, preferences); + convertVariableStatement(sourceFile, statement as VariableStatement, changes, checker, identifiers, target, quotePreference); return false; case SyntaxKind.ExpressionStatement: { const { expression } = statement as ExpressionStatement; @@ -109,7 +109,7 @@ namespace ts.codefix { case SyntaxKind.CallExpression: { if (isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true)) { // For side-effecting require() call, just make a side-effecting import. - changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0], preferences)); + changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0], quotePreference)); } return false; } @@ -125,7 +125,15 @@ namespace ts.codefix { } } - function convertVariableStatement(sourceFile: SourceFile, statement: VariableStatement, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, target: ScriptTarget, preferences: UserPreferences): void { + function convertVariableStatement( + sourceFile: SourceFile, + statement: VariableStatement, + changes: textChanges.ChangeTracker, + checker: TypeChecker, + identifiers: Identifiers, + target: ScriptTarget, + quotePreference: QuotePreference, + ): void { const { declarationList } = statement; let foundImport = false; const newNodes = flatMap(declarationList.declarations, decl => { @@ -138,11 +146,11 @@ namespace ts.codefix { } else if (isRequireCall(initializer, /*checkArgumentIsStringLiteralLike*/ true)) { foundImport = true; - return convertSingleImport(sourceFile, name, initializer.arguments[0], changes, checker, identifiers, target, preferences); + return convertSingleImport(sourceFile, name, initializer.arguments[0], changes, checker, identifiers, target, quotePreference); } else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteralLike*/ true)) { foundImport = true; - return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0], identifiers, preferences); + return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0], identifiers, quotePreference); } } // Move it out to its own variable statement. (This will not be used if `!foundImport`) @@ -155,20 +163,20 @@ namespace ts.codefix { } /** Converts `const name = require("moduleSpecifier").propertyName` */ - function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: StringLiteralLike, identifiers: Identifiers, preferences: UserPreferences): ReadonlyArray { + function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: StringLiteralLike, identifiers: Identifiers, quotePreference: QuotePreference): ReadonlyArray { switch (name.kind) { case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: { // `const [a, b] = require("c").d` --> `import { d } from "c"; const [a, b] = d;` const tmp = makeUniqueName(propertyName, identifiers); return [ - makeSingleImport(tmp, propertyName, moduleSpecifier, preferences), + makeSingleImport(tmp, propertyName, moduleSpecifier, quotePreference), makeConst(/*modifiers*/ undefined, name, createIdentifier(tmp)), ]; } case SyntaxKind.Identifier: // `const a = require("b").c` --> `import { c as a } from "./b"; - return [makeSingleImport(name.text, propertyName, moduleSpecifier, preferences)]; + return [makeSingleImport(name.text, propertyName, moduleSpecifier, quotePreference)]; default: return Debug.assertNever(name); } @@ -340,7 +348,7 @@ namespace ts.codefix { checker: TypeChecker, identifiers: Identifiers, target: ScriptTarget, - preferences: UserPreferences, + quotePreference: QuotePreference, ): ReadonlyArray { switch (name.kind) { case SyntaxKind.ObjectBindingPattern: { @@ -349,7 +357,7 @@ namespace ts.codefix { ? undefined : makeImportSpecifier(e.propertyName && (e.propertyName as Identifier).text, e.name.text)); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217) if (importSpecifiers) { - return [makeImport(/*name*/ undefined, importSpecifiers, moduleSpecifier, preferences)]; + return [makeImport(/*name*/ undefined, importSpecifiers, moduleSpecifier, quotePreference)]; } } // falls through -- object destructuring has an interesting pattern and must be a variable declaration @@ -360,12 +368,12 @@ namespace ts.codefix { */ const tmp = makeUniqueName(moduleSpecifierToValidIdentifier(moduleSpecifier.text, target), identifiers); return [ - makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier, preferences), + makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier, quotePreference), makeConst(/*modifiers*/ undefined, getSynthesizedDeepClone(name), createIdentifier(tmp)), ]; } case SyntaxKind.Identifier: - return convertSingleIdentifierImport(file, name, moduleSpecifier, changes, checker, identifiers, preferences); + return convertSingleIdentifierImport(file, name, moduleSpecifier, changes, checker, identifiers, quotePreference); default: return Debug.assertNever(name); } @@ -375,7 +383,7 @@ namespace ts.codefix { * Convert `import x = require("x").` * Also converts uses like `x.y()` to `y()` and uses a named import. */ - function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, preferences: UserPreferences): ReadonlyArray { + function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, quotePreference: QuotePreference): ReadonlyArray { const nameSymbol = checker.getSymbolAtLocation(name); // Maps from module property name to name actually used. (The same if there isn't shadowing.) const namedBindingsNames = createMap(); @@ -410,7 +418,7 @@ namespace ts.codefix { // If it was unused, ensure that we at least import *something*. needDefaultImport = true; } - return [makeImport(needDefaultImport ? getSynthesizedDeepClone(name) : undefined, namedBindings, moduleSpecifier, preferences)]; + return [makeImport(needDefaultImport ? getSynthesizedDeepClone(name) : undefined, namedBindings, moduleSpecifier, quotePreference)]; } // Identifiers helpers @@ -488,10 +496,10 @@ namespace ts.codefix { getSynthesizedDeepClones(cls.members)); } - function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: StringLiteralLike, preferences: UserPreferences): ImportDeclaration { + function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: StringLiteralLike, quotePreference: QuotePreference): ImportDeclaration { return propertyName === "default" - ? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier, preferences) - : makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier, preferences); + ? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier, quotePreference) + : makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier, quotePreference); } function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier { diff --git a/src/services/codefixes/fixInvalidImportSyntax.ts b/src/services/codefixes/fixInvalidImportSyntax.ts index 728a8b5663c..cfdc19e257d 100644 --- a/src/services/codefixes/fixInvalidImportSyntax.ts +++ b/src/services/codefixes/fixInvalidImportSyntax.ts @@ -28,7 +28,7 @@ namespace ts.codefix { const variations: CodeFixAction[] = []; // import Bluebird from "bluebird"; - variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, context.preferences))); + variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, getQuotePreference(sourceFile, context.preferences)))); if (getEmitModuleKind(opts) === ModuleKind.CommonJS) { // import Bluebird = require("bluebird"); diff --git a/src/services/codefixes/fixStrictClassInitialization.ts b/src/services/codefixes/fixStrictClassInitialization.ts index 40a26da204a..f52ebadc851 100644 --- a/src/services/codefixes/fixStrictClassInitialization.ts +++ b/src/services/codefixes/fixStrictClassInitialization.ts @@ -109,14 +109,8 @@ namespace ts.codefix { } function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined { - if (type.flags & TypeFlags.String) { - return createLiteral(""); - } - else if (type.flags & TypeFlags.Number) { - return createNumericLiteral("0"); - } - else if (type.flags & TypeFlags.Boolean) { - return createFalse(); + if (type.flags & TypeFlags.BooleanLiteral) { + return type === checker.getFalseType() ? createFalse() : createTrue(); } else if (type.isLiteral()) { return createLiteral(type.value); @@ -133,6 +127,9 @@ namespace ts.codefix { return createNew(createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined); } + else if (checker.isArrayLikeType(type)) { + return createArrayLiteral(); + } return undefined; } } diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index c97902e7713..719e3a316f7 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -152,6 +152,7 @@ namespace ts.codefix { switch (token.kind) { case SyntaxKind.Identifier: tryDeleteIdentifier(changes, sourceFile, token, deletedAncestors, checker, isFixAll); + deleteAssignments(changes, sourceFile, token as Identifier, checker); break; case SyntaxKind.PropertyDeclaration: case SyntaxKind.NamespaceImport: @@ -163,6 +164,15 @@ namespace ts.codefix { } } + function deleteAssignments(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Identifier, checker: TypeChecker) { + FindAllReferences.Core.eachSymbolReferenceInFile(token, checker, sourceFile, (ref: Node) => { + if (ref.parent.kind === SyntaxKind.PropertyAccessExpression) ref = ref.parent; + if (ref.parent.kind === SyntaxKind.BinaryExpression && ref.parent.parent.kind === SyntaxKind.ExpressionStatement) { + changes.deleteNode(sourceFile, ref.parent.parent); + } + }); + } + function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, deletedAncestors: NodeSet | undefined): void { if (isDeclarationName(token)) { if (deletedAncestors) deletedAncestors.add(token.parent); @@ -228,15 +238,12 @@ namespace ts.codefix { case SyntaxKind.BindingElement: { const pattern = (parent as BindingElement).parent; - switch (pattern.kind) { - case SyntaxKind.ArrayBindingPattern: - changes.deleteNode(sourceFile, parent); // Don't delete ',' - break; - case SyntaxKind.ObjectBindingPattern: - changes.deleteNodeInList(sourceFile, parent); - break; - default: - return Debug.assertNever(pattern); + const preserveComma = pattern.kind === SyntaxKind.ArrayBindingPattern && parent !== last(pattern.elements); + if (preserveComma) { + changes.deleteNode(sourceFile, parent); + } + else { + changes.deleteNodeInList(sourceFile, parent); } break; } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index ee4339f3e19..5f26336a117 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -112,7 +112,7 @@ namespace ts.codefix { export function createMethodFromCallExpression( context: CodeFixContextBase, - { typeArguments, arguments: args }: CallExpression, + { typeArguments, arguments: args, parent: parent }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean, @@ -135,7 +135,7 @@ namespace ts.codefix { return createMethod( /*decorators*/ undefined, /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, - /*asteriskToken*/ undefined, + /*asteriskToken*/ isYieldExpression(parent) ? createToken(SyntaxKind.AsteriskToken) : undefined, methodName, /*questionToken*/ undefined, /*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) => diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index ecd2393418b..66402068fa4 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -197,7 +197,7 @@ namespace ts.codefix { const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax); const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier); - const quotedModuleSpecifier = createLiteral(moduleSpecifierWithoutQuotes, shouldUseSingleQuote(sourceFile, preferences)); + const quotedModuleSpecifier = makeStringLiteral(moduleSpecifierWithoutQuotes, getQuotePreference(sourceFile, preferences)); const importDecl = importKind !== ImportKind.Equals ? createImportDeclaration( /*decorators*/ undefined, @@ -225,16 +225,6 @@ namespace ts.codefix { return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes); } - function shouldUseSingleQuote(sourceFile: SourceFile, preferences: UserPreferences): boolean { - if (preferences.quotePreference) { - return preferences.quotePreference === "single"; - } - else { - const firstModuleSpecifier = firstOrUndefined(sourceFile.imports); - return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile); - } - } - function createImportClauseOfKind(kind: ImportKind.Default | ImportKind.Named | ImportKind.Namespace, symbolName: string) { const id = createIdentifier(symbolName); switch (kind) { diff --git a/src/services/codefixes/useDefaultImport.ts b/src/services/codefixes/useDefaultImport.ts index 34e3d40e514..36aa0bb1697 100644 --- a/src/services/codefixes/useDefaultImport.ts +++ b/src/services/codefixes/useDefaultImport.ts @@ -37,6 +37,6 @@ namespace ts.codefix { } function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info, preferences: UserPreferences): void { - changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, preferences)); + changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, getQuotePreference(sourceFile, preferences))); } } diff --git a/src/services/completions.ts b/src/services/completions.ts index d83e86ab4ad..e31ac4e81f3 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -101,7 +101,7 @@ namespace ts.Completions { } function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined { - const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData; + const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData; if (sourceFile.languageVariant === LanguageVariant.JSX && location && location.parent && isJsxClosingElement(location.parent)) { // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag, @@ -143,6 +143,10 @@ namespace ts.Completions { addRange(entries, getKeywordCompletions(keywordFilters)); } + for (const literal of literals) { + entries.push(createCompletionEntryForLiteral(literal)); + } + return { isGlobalCompletion: isInSnippetScope, isMemberCompletion, isNewIdentifierLocation, entries }; } @@ -184,6 +188,11 @@ namespace ts.Completions { }); } + const completionNameForLiteral = JSON.stringify; + function createCompletionEntryForLiteral(literal: string | number): CompletionEntry { + return { name: completionNameForLiteral(literal), kind: ScriptElementKind.string, kindModifiers: ScriptElementKindModifier.none, sortText: "0" }; + } + function createCompletionEntry( symbol: Symbol, location: Node | undefined, @@ -372,7 +381,7 @@ namespace ts.Completions { case SyntaxKind.LiteralType: switch (node.parent.parent.kind) { case SyntaxKind.TypeReference: - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode), typeChecker), isNewIdentifier: false }; + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent as LiteralTypeNode)), isNewIdentifier: false }; case SyntaxKind.IndexedAccessType: // Get all apparent property names // i.e. interface Foo { @@ -448,7 +457,7 @@ namespace ts.Completions { function fromContextualType(): StringLiteralCompletion { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" - return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker), typeChecker), isNewIdentifier: false }; + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker)), isNewIdentifier: false }; } } @@ -462,7 +471,7 @@ namespace ts.Completions { if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return; const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); - return getStringLiteralTypes(type, checker, uniques); + return getStringLiteralTypes(type, uniques); }); return { kind: StringLiteralCompletionKind.Types, types, isNewIdentifier }; @@ -472,11 +481,11 @@ namespace ts.Completions { return type && { kind: StringLiteralCompletionKind.Properties, symbols: type.getApparentProperties(), hasIndexSignature: hasIndexSignature(type) }; } - function getStringLiteralTypes(type: Type | undefined, typeChecker: TypeChecker, uniques = createMap()): ReadonlyArray { + function getStringLiteralTypes(type: Type | undefined, uniques = createMap()): ReadonlyArray { if (!type) return emptyArray; type = skipConstraint(type); return type.isUnion() - ? flatMap(type.types, t => getStringLiteralTypes(t, typeChecker, uniques)) + ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : type.isStringLiteral() && !(type.flags & TypeFlags.EnumLiteral) && addToSeen(uniques, type.value) ? [type] : emptyArray; @@ -491,7 +500,7 @@ namespace ts.Completions { readonly isJsxInitializer: IsJsxInitializer; } function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, - ): SymbolCompletion | { type: "request", request: Request } | { type: "none" } { + ): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number } | { type: "none" } { const compilerOptions = program.getCompilerOptions(); const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId); if (!completionData) { @@ -501,7 +510,10 @@ namespace ts.Completions { return { type: "request", request: completionData }; } - const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData; + const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData; + + const literal = find(literals, l => completionNameForLiteral(l) === entryId.name); + if (literal !== undefined) return { type: "literal", literal }; // Find the symbol with the matching entry name. // We don't need to perform character checks here because we're only comparing the @@ -574,12 +586,22 @@ namespace ts.Completions { const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, program.getSourceFiles(), preferences); return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217 } + case "literal": { + const { literal } = symbolCompletion; + return createSimpleDetails(completionNameForLiteral(literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral); + } case "none": // Didn't find a symbol with this name. See if we can find a keyword instead. - return allKeywordsCompletions().some(c => c.name === name) ? createCompletionDetails(name, ScriptElementKindModifier.none, ScriptElementKind.keyword, [displayPart(name, SymbolDisplayPartKind.keyword)]) : undefined; + return allKeywordsCompletions().some(c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined; + default: + Debug.assertNever(symbolCompletion); } } + function createSimpleDetails(name: string, kind: ScriptElementKind, kind2: SymbolDisplayPartKind): CompletionEntryDetails { + return createCompletionDetails(name, ScriptElementKindModifier.none, kind, [displayPart(name, kind2)]); + } + function createCompletionDetailsForSymbol(symbol: Symbol, checker: TypeChecker, sourceFile: SourceFile, location: Node, cancellationToken: CancellationToken, codeActions?: CodeAction[], sourceDisplay?: SymbolDisplayPart[]): CompletionEntryDetails { const { displayParts, documentation, symbolKind, tags } = checker.runWithCancellationToken(cancellationToken, checker => @@ -669,6 +691,7 @@ namespace ts.Completions { readonly isNewIdentifierLocation: boolean; readonly location: Node | undefined; readonly keywordFilters: KeywordCompletionFilters; + readonly literals: ReadonlyArray; readonly symbolToOriginInfoMap: SymbolOriginInfoMap; readonly recommendedCompletion: Symbol | undefined; readonly previousToken: Node | undefined; @@ -685,23 +708,22 @@ namespace ts.Completions { None, } - function getRecommendedCompletion(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Symbol | undefined { - const contextualType = getContextualType(currentToken, position, sourceFile, checker); + function getRecommendedCompletion(previousToken: Node, contextualType: Type, checker: TypeChecker): Symbol | undefined { // For a union, return the first one with a recommended completion. return firstDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), type => { const symbol = type && type.symbol; // Don't include make a recommended completion for an abstract class return symbol && (symbol.flags & (SymbolFlags.EnumMember | SymbolFlags.Enum | SymbolFlags.Class) && !isAbstractConstructorSymbol(symbol)) - ? getFirstSymbolInChain(symbol, currentToken, checker) + ? getFirstSymbolInChain(symbol, previousToken, checker) : undefined; }); } - function getContextualType(currentToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { - const { parent } = currentToken; - switch (currentToken.kind) { + function getContextualType(previousToken: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): Type | undefined { + const { parent } = previousToken; + switch (previousToken.kind) { case SyntaxKind.Identifier: - return getContextualTypeFromParent(currentToken as Identifier, checker); + return getContextualTypeFromParent(previousToken as Identifier, checker); case SyntaxKind.EqualsToken: switch (parent.kind) { case SyntaxKind.VariableDeclaration: @@ -720,14 +742,14 @@ namespace ts.Completions { case SyntaxKind.OpenBraceToken: return isJsxExpression(parent) && parent.parent.kind !== SyntaxKind.JsxElement ? checker.getContextualTypeForJsxAttribute(parent.parent) : undefined; default: - const argInfo = SignatureHelp.getArgumentInfoForCompletions(currentToken, position, sourceFile); + const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile); return argInfo // At `,`, treat this as the next argument after the comma. - ? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (currentToken.kind === SyntaxKind.CommaToken ? 1 : 0)) - : isEqualityOperatorKind(currentToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) + ? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) + : isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) // completion at `x ===/**/` should be for the right side ? checker.getTypeAtLocation(parent.left) - : checker.getContextualType(currentToken as Expression); + : checker.getContextualType(previousToken as Expression); } } @@ -1005,8 +1027,11 @@ namespace ts.Completions { log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - const recommendedCompletion = previousToken && getRecommendedCompletion(previousToken, position, sourceFile, typeChecker); - return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; + const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); + const literals = mapDefined(contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), t => t.isLiteral() ? t.value : undefined); + + const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); + return { kind: CompletionDataKind.Data, symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer }; type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag; @@ -1074,7 +1099,7 @@ namespace ts.Completions { } function addTypeProperties(type: Type): void { - isNewIdentifierLocation = hasIndexSignature(type); + isNewIdentifierLocation = !!type.getStringIndexType(); if (isUncheckedFile) { // In javascript files, for union types, we don't just get the members that diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 0ce64d786e1..f381be9b7d1 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -36,6 +36,7 @@ namespace ts.NavigationBar { */ interface NavigationBarNode { node: Node; + name: DeclarationName | undefined; additionalNodes: Node[] | undefined; parent: NavigationBarNode | undefined; // Present for all but root node children: NavigationBarNode[] | undefined; @@ -91,7 +92,7 @@ namespace ts.NavigationBar { function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { Debug.assert(!parentsStack.length); - const root: NavigationBarNode = { node: sourceFile, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; + const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; parent = root; for (const statement of sourceFile.statements) { addChildrenRecursively(statement); @@ -108,6 +109,7 @@ namespace ts.NavigationBar { function emptyNavigationBarNode(node: Node): NavigationBarNode { return { node, + name: isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined, additionalNodes: undefined, parent, children: undefined, @@ -420,12 +422,11 @@ namespace ts.NavigationBar { } } - function getItemName(node: Node): string { + function getItemName(node: Node, name: Node | undefined): string { if (node.kind === SyntaxKind.ModuleDeclaration) { return getModuleName(node); } - const name = getNameOfDeclaration(node); if (name) { const text = nodeText(name); if (text.length > 0) { @@ -534,17 +535,18 @@ namespace ts.NavigationBar { function convertToTree(n: NavigationBarNode): NavigationTree { return { - text: getItemName(n.node), + text: getItemName(n.node, n.name), kind: getNodeKind(n.node), kindModifiers: getModifiers(n.node), spans: getSpans(n), + nameSpan: n.name && getNodeSpan(n.name), childItems: map(n.children, convertToTree) }; } function convertToTopLevelItem(n: NavigationBarNode): NavigationBarItem { return { - text: getItemName(n.node), + text: getItemName(n.node, n.name), kind: getNodeKind(n.node), kindModifiers: getModifiers(n.node), spans: getSpans(n), @@ -556,7 +558,7 @@ namespace ts.NavigationBar { function convertToChildItem(n: NavigationBarNode): NavigationBarItem { return { - text: getItemName(n.node), + text: getItemName(n.node, n.name), kind: getNodeKind(n.node), kindModifiers: getNodeModifiers(n.node), spans: getSpans(n), diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index c1f1ec1fd5e..0c6aaa61b65 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -118,7 +118,8 @@ namespace ts.refactor { } const useEs6ModuleSyntax = !!oldFile.externalModuleIndicator; - const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEs6ModuleSyntax, preferences); + const quotePreference = getQuotePreference(oldFile, preferences); + const importsFromNewFile = createOldFileImportsFromNewFile(usage.oldFileImportsFromNewFile, newModuleName, useEs6ModuleSyntax, quotePreference); if (importsFromNewFile) { changes.insertNodeBefore(oldFile, oldFile.statements[0], importsFromNewFile, /*blankLineBetween*/ true); } @@ -129,7 +130,7 @@ namespace ts.refactor { updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName); return [ - ...getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEs6ModuleSyntax, preferences), + ...getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEs6ModuleSyntax, quotePreference), ...addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEs6ModuleSyntax), ]; } @@ -268,7 +269,7 @@ namespace ts.refactor { | ImportEqualsDeclaration | VariableStatement; - function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, preferences: UserPreferences): Statement | undefined { + function createOldFileImportsFromNewFile(newFileNeedExport: ReadonlySymbolSet, newFileNameWithExtension: string, useEs6Imports: boolean, quotePreference: QuotePreference): Statement | undefined { let defaultImport: Identifier | undefined; const imports: string[] = []; newFileNeedExport.forEach(symbol => { @@ -279,14 +280,14 @@ namespace ts.refactor { imports.push(symbol.name); } }); - return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, preferences); + return makeImportOrRequire(defaultImport, imports, newFileNameWithExtension, useEs6Imports, quotePreference); } - function makeImportOrRequire(defaultImport: Identifier | undefined, imports: ReadonlyArray, path: string, useEs6Imports: boolean, preferences: UserPreferences): Statement | undefined { + function makeImportOrRequire(defaultImport: Identifier | undefined, imports: ReadonlyArray, path: string, useEs6Imports: boolean, quotePreference: QuotePreference): Statement | undefined { path = ensurePathIsNonModuleName(path); if (useEs6Imports) { const specifiers = imports.map(i => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(i))); - return makeImportIfNecessary(defaultImport, specifiers, path, preferences); + return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference); } else { Debug.assert(!defaultImport); // If there's a default export, it should have been an es6 module. @@ -392,7 +393,7 @@ namespace ts.refactor { changes: textChanges.ChangeTracker, checker: TypeChecker, useEs6ModuleSyntax: boolean, - preferences: UserPreferences, + quotePreference: QuotePreference, ): ReadonlyArray { const copiedOldImports: SupportedImportStatement[] = []; for (const oldStatement of oldFile.statements) { @@ -424,7 +425,7 @@ namespace ts.refactor { } }); - append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEs6ModuleSyntax, preferences)); + append(copiedOldImports, makeImportOrRequire(oldFileDefault, oldFileNamedImports, removeFileExtension(getBaseFileName(oldFile.fileName)), useEs6ModuleSyntax, quotePreference)); return copiedOldImports; } diff --git a/src/services/services.ts b/src/services/services.ts index 0f05a542885..db0213ed88a 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -426,7 +426,7 @@ namespace ts { return !!(this.flags & TypeFlags.UnionOrIntersection); } isLiteral(): this is LiteralType { - return !!(this.flags & TypeFlags.Literal); + return !!(this.flags & TypeFlags.StringOrNumberLiteral); } isStringLiteral(): this is StringLiteralType { return !!(this.flags & TypeFlags.StringLiteral); @@ -540,6 +540,7 @@ namespace ts { public _declarationBrand: any; public fileName: string; public path: Path; + public resolvedPath: Path; public text: string; public scriptSnapshot: IScriptSnapshot; public lineMap: ReadonlyArray; diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 02c29b0568d..c37969f19c2 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -735,7 +735,7 @@ namespace ts.textChanges { export function newFileChanges(oldFile: SourceFile, fileName: string, statements: ReadonlyArray, newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges { // TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this const nonFormattedText = statements.map(s => getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter); - const sourceFile = createSourceFile(fileName, nonFormattedText, ScriptTarget.ESNext); + const sourceFile = createSourceFile(fileName, nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true); const changes = formatting.formatDocument(sourceFile, formatContext); const text = applyChanges(nonFormattedText, changes); return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true }; diff --git a/src/services/types.ts b/src/services/types.ts index 4ac439b56cc..9ae51a4ea61 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -425,6 +425,7 @@ namespace ts { * There will be more than one if this is the result of merging. */ spans: TextSpan[]; + nameSpan: TextSpan | undefined; /** Present if non-empty */ childItems?: NavigationTree[]; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 786de97f20e..efc1488d6a4 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1257,18 +1257,34 @@ namespace ts { return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)); } - export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: ReadonlyArray | undefined, moduleSpecifier: string, preferences: UserPreferences): ImportDeclaration | undefined { - return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, preferences) : undefined; + export function makeImportIfNecessary(defaultImport: Identifier | undefined, namedImports: ReadonlyArray | undefined, moduleSpecifier: string, quotePreference: QuotePreference): ImportDeclaration | undefined { + return defaultImport || namedImports && namedImports.length ? makeImport(defaultImport, namedImports, moduleSpecifier, quotePreference) : undefined; } - export function makeImport(defaultImport: Identifier | undefined, namedImports: ReadonlyArray | undefined, moduleSpecifier: string | Expression, preferences: UserPreferences): ImportDeclaration { + export function makeImport(defaultImport: Identifier | undefined, namedImports: ReadonlyArray | undefined, moduleSpecifier: string | Expression, quotePreference: QuotePreference): ImportDeclaration { return createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, defaultImport || namedImports ? createImportClause(defaultImport, namedImports && namedImports.length ? createNamedImports(namedImports) : undefined) : undefined, - typeof moduleSpecifier === "string" ? createLiteral(moduleSpecifier, preferences.quotePreference === "single") : moduleSpecifier); + typeof moduleSpecifier === "string" ? makeStringLiteral(moduleSpecifier, quotePreference) : moduleSpecifier); + } + + export function makeStringLiteral(text: string, quotePreference: QuotePreference): StringLiteral { + return createLiteral(text, quotePreference === QuotePreference.Single); + } + + export const enum QuotePreference { Single, Double } + + export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference { + if (preferences.quotePreference) { + return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double; + } + else { + const firstModuleSpecifier = firstOrUndefined(sourceFile.imports); + return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile) ? QuotePreference.Single : QuotePreference.Double; + } } export function symbolNameNoDefault(symbol: Symbol): string | undefined { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 45e32195e79..2cfb99444e5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1674,11 +1674,14 @@ declare namespace ts { interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } interface JsonSourceFile extends SourceFile { statements: NodeArray; @@ -2661,6 +2664,9 @@ declare namespace ts { resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string | undefined; createHash?(data: string): string; + getModifiedTime?(fileName: string): Date; + setModifiedTime?(fileName: string, date: Date): void; + deleteFile?(fileName: string): void; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -3015,6 +3021,8 @@ declare namespace ts { getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; /** * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) */ @@ -3380,6 +3388,7 @@ declare namespace ts { function isEnumMember(node: Node): node is EnumMember; function isSourceFile(node: Node): node is SourceFile; function isBundle(node: Node): node is Bundle; + function isUnparsedSource(node: Node): node is UnparsedSource; function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; function isJSDocUnknownType(node: Node): node is JSDocUnknownType; @@ -3831,8 +3840,8 @@ declare namespace ts { function createCommaList(elements: ReadonlyArray): CommaListExpression; function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; function createBundle(sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; - function createUnparsedSourceFile(text: string): UnparsedSource; - function createInputFiles(javascript: string, declaration: string): InputFiles; + function createUnparsedSourceFile(text: string, map?: string): UnparsedSource; + function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles; function updateBundle(node: Bundle, sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray): CallExpression; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray, param: ParameterDeclaration, paramValue: Expression): CallExpression; @@ -4053,6 +4062,10 @@ declare namespace ts { * @returns A 'Program' object. */ function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: ReadonlyArray): Program; + /** + * Returns the target config filename of a project reference + */ + function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined; } declare namespace ts { interface EmitOutput { @@ -4657,6 +4670,7 @@ declare namespace ts { * There will be more than one if this is the result of merging. */ spans: TextSpan[]; + nameSpan: TextSpan | undefined; /** Present if non-empty */ childItems?: NavigationTree[]; } @@ -7532,6 +7546,7 @@ declare namespace ts.server.protocol { kind: ScriptElementKind; kindModifiers: string; spans: TextSpan[]; + nameSpan: TextSpan | undefined; childItems?: NavigationTree[]; } type TelemetryEventName = "telemetry"; @@ -8562,9 +8577,7 @@ declare namespace ts.server { private mapCodeAction; private mapCodeFixAction; private mapTextChangesToCodeEdits; - private mapTextChangesToCodeEditsUsingScriptinfo; private convertTextChangeToCodeEdit; - private convertNewFileTextChangeToCodeEdit; private getBraceMatching; private getDiagnosticsForProject; getCanonicalFileName(fileName: string): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ac272a91094..acbcabf5bef 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1674,11 +1674,14 @@ declare namespace ts { interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } interface JsonSourceFile extends SourceFile { statements: NodeArray; @@ -2661,6 +2664,9 @@ declare namespace ts { resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string | undefined; createHash?(data: string): string; + getModifiedTime?(fileName: string): Date; + setModifiedTime?(fileName: string, date: Date): void; + deleteFile?(fileName: string): void; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -3015,6 +3021,8 @@ declare namespace ts { getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; + setModifiedTime?(path: string, time: Date): void; + deleteFile?(path: string): void; /** * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) */ @@ -3380,6 +3388,7 @@ declare namespace ts { function isEnumMember(node: Node): node is EnumMember; function isSourceFile(node: Node): node is SourceFile; function isBundle(node: Node): node is Bundle; + function isUnparsedSource(node: Node): node is UnparsedSource; function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; function isJSDocUnknownType(node: Node): node is JSDocUnknownType; @@ -3831,8 +3840,8 @@ declare namespace ts { function createCommaList(elements: ReadonlyArray): CommaListExpression; function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; function createBundle(sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; - function createUnparsedSourceFile(text: string): UnparsedSource; - function createInputFiles(javascript: string, declaration: string): InputFiles; + function createUnparsedSourceFile(text: string, map?: string): UnparsedSource; + function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles; function updateBundle(node: Bundle, sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray): CallExpression; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray, param: ParameterDeclaration, paramValue: Expression): CallExpression; @@ -4053,6 +4062,10 @@ declare namespace ts { * @returns A 'Program' object. */ function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: ReadonlyArray): Program; + /** + * Returns the target config filename of a project reference + */ + function resolveProjectReferencePath(host: CompilerHost, ref: ProjectReference): string | undefined; } declare namespace ts { interface EmitOutput { @@ -4657,6 +4670,7 @@ declare namespace ts { * There will be more than one if this is the result of merging. */ spans: TextSpan[]; + nameSpan: TextSpan | undefined; /** Present if non-empty */ childItems?: NavigationTree[]; } diff --git a/tests/baselines/reference/asyncArrowFunction11_es5.js b/tests/baselines/reference/asyncArrowFunction11_es5.js new file mode 100644 index 00000000000..013941c96ca --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction11_es5.js @@ -0,0 +1,71 @@ +//// [asyncArrowFunction11_es5.ts] +// https://github.com/Microsoft/TypeScript/issues/24722 +class A { + b = async (...args: any[]) => { + await Promise.resolve(); + const obj = { ["a"]: () => this }; // computed property name after `await` triggers case + }; +} + +//// [asyncArrowFunction11_es5.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +// https://github.com/Microsoft/TypeScript/issues/24722 +var A = /** @class */ (function () { + function A() { + var _this = this; + this.b = function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return __awaiter(_this, void 0, void 0, function () { + var _a, obj; + var _this = this; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, Promise.resolve()]; + case 1: + _b.sent(); + obj = (_a = {}, _a["a"] = function () { return _this; }, _a); + return [2 /*return*/]; + } + }); + }); + }; + } + return A; +}()); diff --git a/tests/baselines/reference/asyncArrowFunction11_es5.symbols b/tests/baselines/reference/asyncArrowFunction11_es5.symbols new file mode 100644 index 00000000000..aff484d5927 --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction11_es5.symbols @@ -0,0 +1,22 @@ +=== tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction11_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/24722 +class A { +>A : Symbol(A, Decl(asyncArrowFunction11_es5.ts, 0, 0)) + + b = async (...args: any[]) => { +>b : Symbol(A.b, Decl(asyncArrowFunction11_es5.ts, 1, 9)) +>args : Symbol(args, Decl(asyncArrowFunction11_es5.ts, 2, 15)) + + await Promise.resolve(); +>Promise.resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>resolve : Symbol(PromiseConstructor.resolve, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --)) + + const obj = { ["a"]: () => this }; // computed property name after `await` triggers case +>obj : Symbol(obj, Decl(asyncArrowFunction11_es5.ts, 4, 13)) +>["a"] : Symbol(["a"], Decl(asyncArrowFunction11_es5.ts, 4, 21)) +>"a" : Symbol(["a"], Decl(asyncArrowFunction11_es5.ts, 4, 21)) +>this : Symbol(A, Decl(asyncArrowFunction11_es5.ts, 0, 0)) + + }; +} diff --git a/tests/baselines/reference/asyncArrowFunction11_es5.types b/tests/baselines/reference/asyncArrowFunction11_es5.types new file mode 100644 index 00000000000..70eea5e2f2a --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction11_es5.types @@ -0,0 +1,27 @@ +=== tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction11_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/24722 +class A { +>A : A + + b = async (...args: any[]) => { +>b : (...args: any[]) => Promise +>async (...args: any[]) => { await Promise.resolve(); const obj = { ["a"]: () => this }; // computed property name after `await` triggers case } : (...args: any[]) => Promise +>args : any[] + + await Promise.resolve(); +>await Promise.resolve() : void +>Promise.resolve() : Promise +>Promise.resolve : { (value: T | PromiseLike): Promise; (): Promise; } +>Promise : PromiseConstructor +>resolve : { (value: T | PromiseLike): Promise; (): Promise; } + + const obj = { ["a"]: () => this }; // computed property name after `await` triggers case +>obj : { ["a"]: () => this; } +>{ ["a"]: () => this } : { ["a"]: () => this; } +>["a"] : () => this +>"a" : "a" +>() => this : () => this +>this : this + + }; +} diff --git a/tests/baselines/reference/checkJsxChildrenProperty3.types b/tests/baselines/reference/checkJsxChildrenProperty3.types index 7fbb91bee00..84764f07b7a 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty3.types +++ b/tests/baselines/reference/checkJsxChildrenProperty3.types @@ -38,11 +38,11 @@ class FetchUser extends React.Component { ? this.props.children(this.state.result) >this.props.children(this.state.result) : JSX.Element ->this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) +>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.props : IFetchUserProps & { children?: React.ReactNode; } >this : this >props : IFetchUserProps & { children?: React.ReactNode; } ->children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) +>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.state.result : any >this.state : any >this : this diff --git a/tests/baselines/reference/checkJsxChildrenProperty4.types b/tests/baselines/reference/checkJsxChildrenProperty4.types index 6e4c04aa231..6e2ff7fe121 100644 --- a/tests/baselines/reference/checkJsxChildrenProperty4.types +++ b/tests/baselines/reference/checkJsxChildrenProperty4.types @@ -38,11 +38,11 @@ class FetchUser extends React.Component { ? this.props.children(this.state.result) >this.props.children(this.state.result) : JSX.Element ->this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) +>this.props.children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.props : IFetchUserProps & { children?: React.ReactNode; } >this : this >props : IFetchUserProps & { children?: React.ReactNode; } ->children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) +>children : ((user: IUser) => JSX.Element) | (((user: IUser) => JSX.Element) & string) | (((user: IUser) => JSX.Element) & number) | (((user: IUser) => JSX.Element) & false) | (((user: IUser) => JSX.Element) & true) | (((user: IUser) => JSX.Element) & React.ReactElement) | (((user: IUser) => JSX.Element) & (string | number | boolean | any[] | React.ReactElement)[]) >this.state.result : any >this.state : any >this : this diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.js b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.js new file mode 100644 index 00000000000..ae45c09438f --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/declarationEmitWithDefaultAsComputedName.ts] //// + +//// [other.ts] +type Experiment = { + name: Name; +}; +declare const createExperiment: ( + options: Experiment +) => Experiment; +export default createExperiment({ + name: "foo" +}); + +//// [main.ts] +import other from "./other"; +export const obj = { + [other.name]: 1, +}; + +//// [other.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = createExperiment({ + name: "foo" +}); +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var _a; +var other_1 = require("./other"); +exports.obj = (_a = {}, + _a[other_1.default.name] = 1, + _a); + + +//// [other.d.ts] +declare type Experiment = { + name: Name; +}; +declare const _default: Experiment<"foo">; +export default _default; +//// [main.d.ts] +import other from "./other"; +export declare const obj: { + [other.name]: number; +}; diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.symbols b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.symbols new file mode 100644 index 00000000000..50d91d4c603 --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.symbols @@ -0,0 +1,45 @@ +=== tests/cases/compiler/other.ts === +type Experiment = { +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 0, 16)) + + name: Name; +>name : Symbol(name, Decl(other.ts, 0, 25)) +>Name : Symbol(Name, Decl(other.ts, 0, 16)) + +}; +declare const createExperiment: ( +>createExperiment : Symbol(createExperiment, Decl(other.ts, 3, 13)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + + options: Experiment +>options : Symbol(options, Decl(other.ts, 3, 54)) +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + +) => Experiment; +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + +export default createExperiment({ +>createExperiment : Symbol(createExperiment, Decl(other.ts, 3, 13)) + + name: "foo" +>name : Symbol(name, Decl(other.ts, 6, 33)) + +}); + +=== tests/cases/compiler/main.ts === +import other from "./other"; +>other : Symbol(other, Decl(main.ts, 0, 6)) + +export const obj = { +>obj : Symbol(obj, Decl(main.ts, 1, 12)) + + [other.name]: 1, +>[other.name] : Symbol([other.name], Decl(main.ts, 1, 20)) +>other.name : Symbol(name, Decl(other.ts, 0, 25)) +>other : Symbol(other, Decl(main.ts, 0, 6)) +>name : Symbol(name, Decl(other.ts, 0, 25)) + +}; diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.types b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.types new file mode 100644 index 00000000000..ef11f7c2f59 --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/other.ts === +type Experiment = { +>Experiment : Experiment +>Name : Name + + name: Name; +>name : Name +>Name : Name + +}; +declare const createExperiment: ( +>createExperiment : (options: Experiment) => Experiment +>Name : Name + + options: Experiment +>options : Experiment +>Experiment : Experiment +>Name : Name + +) => Experiment; +>Experiment : Experiment +>Name : Name + +export default createExperiment({ +>createExperiment({ name: "foo"}) : Experiment<"foo"> +>createExperiment : (options: Experiment) => Experiment +>{ name: "foo"} : { name: "foo"; } + + name: "foo" +>name : "foo" +>"foo" : "foo" + +}); + +=== tests/cases/compiler/main.ts === +import other from "./other"; +>other : { name: "foo"; } + +export const obj = { +>obj : { [other.name]: number; } +>{ [other.name]: 1,} : { [other.name]: number; } + + [other.name]: 1, +>[other.name] : number +>other.name : "foo" +>other : { name: "foo"; } +>name : "foo" +>1 : 1 + +}; diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.js b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.js new file mode 100644 index 00000000000..ddf89f2d57d --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/declarationEmitWithDefaultAsComputedName2.ts] //// + +//// [other.ts] +type Experiment = { + name: Name; +}; +declare const createExperiment: ( + options: Experiment +) => Experiment; +export default createExperiment({ + name: "foo" +}); + +//// [main.ts] +import * as other2 from "./other"; +export const obj = { + [other2.default.name]: 1 +}; + +//// [other.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = createExperiment({ + name: "foo" +}); +//// [main.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var _a; +var other2 = require("./other"); +exports.obj = (_a = {}, + _a[other2.default.name] = 1, + _a); + + +//// [other.d.ts] +declare type Experiment = { + name: Name; +}; +declare const _default: Experiment<"foo">; +export default _default; +//// [main.d.ts] +import * as other2 from "./other"; +export declare const obj: { + [other2.default.name]: number; +}; diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.symbols b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.symbols new file mode 100644 index 00000000000..3f4be4d8b43 --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.symbols @@ -0,0 +1,47 @@ +=== tests/cases/compiler/other.ts === +type Experiment = { +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 0, 16)) + + name: Name; +>name : Symbol(name, Decl(other.ts, 0, 25)) +>Name : Symbol(Name, Decl(other.ts, 0, 16)) + +}; +declare const createExperiment: ( +>createExperiment : Symbol(createExperiment, Decl(other.ts, 3, 13)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + + options: Experiment +>options : Symbol(options, Decl(other.ts, 3, 54)) +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + +) => Experiment; +>Experiment : Symbol(Experiment, Decl(other.ts, 0, 0)) +>Name : Symbol(Name, Decl(other.ts, 3, 33)) + +export default createExperiment({ +>createExperiment : Symbol(createExperiment, Decl(other.ts, 3, 13)) + + name: "foo" +>name : Symbol(name, Decl(other.ts, 6, 33)) + +}); + +=== tests/cases/compiler/main.ts === +import * as other2 from "./other"; +>other2 : Symbol(other2, Decl(main.ts, 0, 6)) + +export const obj = { +>obj : Symbol(obj, Decl(main.ts, 1, 12)) + + [other2.default.name]: 1 +>[other2.default.name] : Symbol([other2.default.name], Decl(main.ts, 1, 20)) +>other2.default.name : Symbol(name, Decl(other.ts, 0, 25)) +>other2.default : Symbol(other2.default, Decl(other.ts, 5, 22)) +>other2 : Symbol(other2, Decl(main.ts, 0, 6)) +>default : Symbol(other2.default, Decl(other.ts, 5, 22)) +>name : Symbol(name, Decl(other.ts, 0, 25)) + +}; diff --git a/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.types b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.types new file mode 100644 index 00000000000..a507034378b --- /dev/null +++ b/tests/baselines/reference/declarationEmitWithDefaultAsComputedName2.types @@ -0,0 +1,52 @@ +=== tests/cases/compiler/other.ts === +type Experiment = { +>Experiment : Experiment +>Name : Name + + name: Name; +>name : Name +>Name : Name + +}; +declare const createExperiment: ( +>createExperiment : (options: Experiment) => Experiment +>Name : Name + + options: Experiment +>options : Experiment +>Experiment : Experiment +>Name : Name + +) => Experiment; +>Experiment : Experiment +>Name : Name + +export default createExperiment({ +>createExperiment({ name: "foo"}) : Experiment<"foo"> +>createExperiment : (options: Experiment) => Experiment +>{ name: "foo"} : { name: "foo"; } + + name: "foo" +>name : "foo" +>"foo" : "foo" + +}); + +=== tests/cases/compiler/main.ts === +import * as other2 from "./other"; +>other2 : typeof other2 + +export const obj = { +>obj : { [other2.default.name]: number; } +>{ [other2.default.name]: 1} : { [other2.default.name]: number; } + + [other2.default.name]: 1 +>[other2.default.name] : number +>other2.default.name : "foo" +>other2.default : { name: "foo"; } +>other2 : typeof other2 +>default : { name: "foo"; } +>name : "foo" +>1 : 1 + +}; diff --git a/tests/baselines/reference/importNotElidedWhenNotFound.errors.txt b/tests/baselines/reference/importNotElidedWhenNotFound.errors.txt index 51e1fe9b44d..b8245c0081d 100644 --- a/tests/baselines/reference/importNotElidedWhenNotFound.errors.txt +++ b/tests/baselines/reference/importNotElidedWhenNotFound.errors.txt @@ -1,8 +1,10 @@ tests/cases/compiler/importNotElidedWhenNotFound.ts(1,15): error TS2307: Cannot find module 'file'. tests/cases/compiler/importNotElidedWhenNotFound.ts(2,15): error TS2307: Cannot find module 'other_file'. +tests/cases/compiler/importNotElidedWhenNotFound.ts(10,16): error TS2307: Cannot find module 'file2'. +tests/cases/compiler/importNotElidedWhenNotFound.ts(11,16): error TS2307: Cannot find module 'file3'. -==== tests/cases/compiler/importNotElidedWhenNotFound.ts (2 errors) ==== +==== tests/cases/compiler/importNotElidedWhenNotFound.ts (4 errors) ==== import X from 'file'; ~~~~~~ !!! error TS2307: Cannot find module 'file'. @@ -14,4 +16,17 @@ tests/cases/compiler/importNotElidedWhenNotFound.ts(2,15): error TS2307: Cannot constructor() { super(X); } - } \ No newline at end of file + } + + import X2 from 'file2'; + ~~~~~~~ +!!! error TS2307: Cannot find module 'file2'. + import X3 from 'file3'; + ~~~~~~~ +!!! error TS2307: Cannot find module 'file3'. + class Q extends Z { + constructor() { + super(X2, X3); + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/importNotElidedWhenNotFound.js b/tests/baselines/reference/importNotElidedWhenNotFound.js index 303e5df8f22..9eef51f4052 100644 --- a/tests/baselines/reference/importNotElidedWhenNotFound.js +++ b/tests/baselines/reference/importNotElidedWhenNotFound.js @@ -6,7 +6,16 @@ class Y extends Z { constructor() { super(X); } -} +} + +import X2 from 'file2'; +import X3 from 'file3'; +class Q extends Z { + constructor() { + super(X2, X3); + } +} + //// [importNotElidedWhenNotFound.js] "use strict"; @@ -30,3 +39,12 @@ var Y = /** @class */ (function (_super) { } return Y; }(other_file_1["default"])); +var file2_1 = require("file2"); +var file3_1 = require("file3"); +var Q = /** @class */ (function (_super) { + __extends(Q, _super); + function Q() { + return _super.call(this, file2_1["default"], file3_1["default"]) || this; + } + return Q; +}(other_file_1["default"])); diff --git a/tests/baselines/reference/importNotElidedWhenNotFound.symbols b/tests/baselines/reference/importNotElidedWhenNotFound.symbols index 37558d6be3d..c920eefb44e 100644 --- a/tests/baselines/reference/importNotElidedWhenNotFound.symbols +++ b/tests/baselines/reference/importNotElidedWhenNotFound.symbols @@ -14,3 +14,21 @@ class Y extends Z { >X : Symbol(X, Decl(importNotElidedWhenNotFound.ts, 0, 6)) } } + +import X2 from 'file2'; +>X2 : Symbol(X2, Decl(importNotElidedWhenNotFound.ts, 9, 6)) + +import X3 from 'file3'; +>X3 : Symbol(X3, Decl(importNotElidedWhenNotFound.ts, 10, 6)) + +class Q extends Z { +>Q : Symbol(Q, Decl(importNotElidedWhenNotFound.ts, 10, 23)) +>Z : Symbol(Z, Decl(importNotElidedWhenNotFound.ts, 1, 6)) + + constructor() { + super(X2, X3); +>X2 : Symbol(X2, Decl(importNotElidedWhenNotFound.ts, 9, 6)) +>X3 : Symbol(X3, Decl(importNotElidedWhenNotFound.ts, 10, 6)) + } +} + diff --git a/tests/baselines/reference/importNotElidedWhenNotFound.types b/tests/baselines/reference/importNotElidedWhenNotFound.types index 02304b6e2d2..ca29d981944 100644 --- a/tests/baselines/reference/importNotElidedWhenNotFound.types +++ b/tests/baselines/reference/importNotElidedWhenNotFound.types @@ -16,3 +16,23 @@ class Y extends Z { >X : any } } + +import X2 from 'file2'; +>X2 : any + +import X3 from 'file3'; +>X3 : any + +class Q extends Z { +>Q : Q +>Z : any + + constructor() { + super(X2, X3); +>super(X2, X3) : void +>super : any +>X2 : any +>X3 : any + } +} + diff --git a/tests/baselines/reference/metadataImportType.errors.txt b/tests/baselines/reference/metadataImportType.errors.txt new file mode 100644 index 00000000000..31824696a0b --- /dev/null +++ b/tests/baselines/reference/metadataImportType.errors.txt @@ -0,0 +1,13 @@ +tests/cases/compiler/metadataImportType.ts(2,6): error TS2304: Cannot find name 'test'. +tests/cases/compiler/metadataImportType.ts(3,8): error TS2307: Cannot find module './b'. + + +==== tests/cases/compiler/metadataImportType.ts (2 errors) ==== + export class A { + @test + ~~~~ +!!! error TS2304: Cannot find name 'test'. + b: import('./b').B + ~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module './b'. + } \ No newline at end of file diff --git a/tests/baselines/reference/metadataImportType.js b/tests/baselines/reference/metadataImportType.js new file mode 100644 index 00000000000..9457a282582 --- /dev/null +++ b/tests/baselines/reference/metadataImportType.js @@ -0,0 +1,28 @@ +//// [metadataImportType.ts] +export class A { + @test + b: import('./b').B +} + +//// [metadataImportType.js] +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +exports.__esModule = true; +var A = /** @class */ (function () { + function A() { + } + __decorate([ + test, + __metadata("design:type", Object) + ], A.prototype, "b"); + return A; +}()); +exports.A = A; diff --git a/tests/baselines/reference/metadataImportType.symbols b/tests/baselines/reference/metadataImportType.symbols new file mode 100644 index 00000000000..ce16caa805c --- /dev/null +++ b/tests/baselines/reference/metadataImportType.symbols @@ -0,0 +1,8 @@ +=== tests/cases/compiler/metadataImportType.ts === +export class A { +>A : Symbol(A, Decl(metadataImportType.ts, 0, 0)) + + @test + b: import('./b').B +>b : Symbol(A.b, Decl(metadataImportType.ts, 0, 16)) +} diff --git a/tests/baselines/reference/metadataImportType.types b/tests/baselines/reference/metadataImportType.types new file mode 100644 index 00000000000..05ba1bc9924 --- /dev/null +++ b/tests/baselines/reference/metadataImportType.types @@ -0,0 +1,11 @@ +=== tests/cases/compiler/metadataImportType.ts === +export class A { +>A : A + + @test +>test : any + + b: import('./b').B +>b : any +>B : No type information available! +} diff --git a/tests/baselines/reference/outfile-concat-fileListing.txt b/tests/baselines/reference/outfile-concat-fileListing.txt new file mode 100644 index 00000000000..fc6a1e7b28c --- /dev/null +++ b/tests/baselines/reference/outfile-concat-fileListing.txt @@ -0,0 +1,43 @@ +*/ + /lib/ + /lib/lib.d.ts + /lib/lib.dom.d.ts + /lib/lib.es5.d.ts + /lib/lib.scripthost.d.ts + /lib/lib.webworker.importscripts.d.ts + /src/ + /src/2/ + /src/2/second-output.d.ts + /src/2/second-output.d.ts.map + /src/2/second-output.js + /src/2/second-output.js.map + /src/first/ + /src/first/bin/ + /src/first/bin/first-output.d.ts + /src/first/bin/first-output.d.ts.map + /src/first/bin/first-output.js + /src/first/bin/first-output.js.map + /src/first/first_part1.ts + /src/first/first_part2.ts + /src/first/first_part3.ts + /src/first/tsconfig.json + /src/first_part1.ts + /src/first_part2.ts + /src/first_part3.ts + /src/second/ + /src/second/second_part1.ts + /src/second/second_part2.ts + /src/second/tsconfig.json + /src/second_part1.ts + /src/second_part2.ts + /src/third/ + /src/third/third_part1.ts + /src/third/thirdjs/ + /src/third/thirdjs/output/ + /src/third/thirdjs/output/third-output.d.ts + /src/third/thirdjs/output/third-output.d.ts.map + /src/third/thirdjs/output/third-output.js + /src/third/thirdjs/output/third-output.js.map + /src/third/tsconfig.json + /src/third_part1.ts + /src/tsconfig.json \ No newline at end of file diff --git a/tests/baselines/reference/outfile-concat.js b/tests/baselines/reference/outfile-concat.js new file mode 100644 index 00000000000..e3e5aa3d5f0 --- /dev/null +++ b/tests/baselines/reference/outfile-concat.js @@ -0,0 +1,26 @@ +var s = "Hello, world"; +console.log(s); +console.log(f()); +function f() { + return "JS does hoists"; +} +//# sourceMappingURL=first-output.js.map +var N; +(function (N) { + function f() { + console.log('testing'); + } + f(); +})(N || (N = {})); +var C = (function () { + function C() { + } + C.prototype.doSomething = function () { + console.log("something got done"); + }; + return C; +}()); +//# sourceMappingURL=second-output.js.map +var c = new C(); +c.doSomething(); +//# sourceMappingURL=third-output.js.map \ No newline at end of file diff --git a/tests/baselines/reference/outfile-concat.js.map b/tests/baselines/reference/outfile-concat.js.map new file mode 100644 index 00000000000..f8220832b2a --- /dev/null +++ b/tests/baselines/reference/outfile-concat.js.map @@ -0,0 +1 @@ +{"version":3,"file":"third-output.js","sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"file":"first-output.js","sourceRoot":"","sources":["first_part1.ts","first_part2.ts","first_part3.ts"],"names":[],"mappings":"AAIA,IAAM,CAAC,GAAG,cAAc,CAAC;AAMzB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;ACVf,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;ACAjB;IACI,OAAO,gBAAgB,CAAC;AAC5B,CAAC"}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"second-output.js","sourceRoot":"","sources":["second_part1.ts","second_part2.ts"],"names":[],"mappings":"AAIA,IAAU,CAAC,CAMV;AAND,WAAU,CAAC;IACP;QACI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,CAAC,EAAE,CAAC;AACR,CAAC,EANS,CAAC,KAAD,CAAC,QAMV;ACVD;IAAA;IAIA,CAAC;IAHG,uBAAW,GAAX;QACI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtC,CAAC;IACL,QAAC;AAAD,CAAC,AAJD,IAIC"}},{"offset":{"line":22,"column":41},"map":{"version":3,"file":"third-output.js","sourceRoot":"","sources":["third_part1.ts"],"names":[],"mappings":";AAAA,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AAChB,CAAC,CAAC,WAAW,EAAE,CAAC"}}]} \ No newline at end of file diff --git a/tests/baselines/reference/third-output.js b/tests/baselines/reference/third-output.js new file mode 100644 index 00000000000..e3e5aa3d5f0 --- /dev/null +++ b/tests/baselines/reference/third-output.js @@ -0,0 +1,26 @@ +var s = "Hello, world"; +console.log(s); +console.log(f()); +function f() { + return "JS does hoists"; +} +//# sourceMappingURL=first-output.js.map +var N; +(function (N) { + function f() { + console.log('testing'); + } + f(); +})(N || (N = {})); +var C = (function () { + function C() { + } + C.prototype.doSomething = function () { + console.log("something got done"); + }; + return C; +}()); +//# sourceMappingURL=second-output.js.map +var c = new C(); +c.doSomething(); +//# sourceMappingURL=third-output.js.map \ No newline at end of file diff --git a/tests/baselines/reference/third-output.js.map b/tests/baselines/reference/third-output.js.map new file mode 100644 index 00000000000..70b9ad69d53 --- /dev/null +++ b/tests/baselines/reference/third-output.js.map @@ -0,0 +1 @@ +{"version":3,"file":"third-output.js","sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"file":"first-output.js","sourceRoot":"","sources":["../first_part1.ts","../first_part2.ts","../first_part3.ts"],"names":[],"mappings":"AAIA,IAAM,CAAC,GAAG,cAAc,CAAC;AAMzB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;ACVf,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;ACAjB;IACI,OAAO,gBAAgB,CAAC;AAC5B,CAAC"}},{"offset":{"line":7,"column":0},"map":{"version":3,"file":"second-output.js","sourceRoot":"","sources":["../second/second_part1.ts","../second/second_part2.ts"],"names":[],"mappings":"AAIA,IAAU,CAAC,CAMV;AAND,WAAU,CAAC;IACP;QACI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAED,CAAC,EAAE,CAAC;AACR,CAAC,EANS,CAAC,KAAD,CAAC,QAMV;ACVD;IAAA;IAIA,CAAC;IAHG,uBAAW,GAAX;QACI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtC,CAAC;IACL,QAAC;AAAD,CAAC,AAJD,IAIC"}},{"offset":{"line":22,"column":41},"map":{"version":3,"file":"third-output.js","sourceRoot":"","sources":["../../third_part1.ts"],"names":[],"mappings":";AAAA,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;AAChB,CAAC,CAAC,WAAW,EAAE,CAAC"}}]} \ No newline at end of file diff --git a/tests/baselines/reference/tsxErrorRecovery1.js b/tests/baselines/reference/tsxErrorRecovery1.js index 7abf1346c35..d91c464c6f9 100644 --- a/tests/baselines/reference/tsxErrorRecovery1.js +++ b/tests/baselines/reference/tsxErrorRecovery1.js @@ -14,5 +14,5 @@ function foo() { } // Shouldn't see any errors down here var y = {a} 1 }; -; + ; } diff --git a/tests/baselines/reference/tsxStatelessFunctionComponents3.js b/tests/baselines/reference/tsxStatelessFunctionComponents3.js index e7bfc980229..152c7e6ba4f 100644 --- a/tests/baselines/reference/tsxStatelessFunctionComponents3.js +++ b/tests/baselines/reference/tsxStatelessFunctionComponents3.js @@ -27,7 +27,7 @@ define(["require", "exports", "react"], function (require, exports, React) { // Should be OK var MainMenu = function (props) { return (

Main Menu

-
); }; + ); }; var App = function (_a) { var children = _a.children; return (
diff --git a/tests/cases/compiler/declarationEmitWithDefaultAsComputedName.ts b/tests/cases/compiler/declarationEmitWithDefaultAsComputedName.ts new file mode 100644 index 00000000000..868bad44847 --- /dev/null +++ b/tests/cases/compiler/declarationEmitWithDefaultAsComputedName.ts @@ -0,0 +1,19 @@ +// @declaration: true +// @target: es5 + +// @filename: other.ts +type Experiment = { + name: Name; +}; +declare const createExperiment: ( + options: Experiment +) => Experiment; +export default createExperiment({ + name: "foo" +}); + +// @filename: main.ts +import other from "./other"; +export const obj = { + [other.name]: 1, +}; \ No newline at end of file diff --git a/tests/cases/compiler/declarationEmitWithDefaultAsComputedName2.ts b/tests/cases/compiler/declarationEmitWithDefaultAsComputedName2.ts new file mode 100644 index 00000000000..8feeaa2fa61 --- /dev/null +++ b/tests/cases/compiler/declarationEmitWithDefaultAsComputedName2.ts @@ -0,0 +1,19 @@ +// @declaration: true +// @target: es5 + +// @filename: other.ts +type Experiment = { + name: Name; +}; +declare const createExperiment: ( + options: Experiment +) => Experiment; +export default createExperiment({ + name: "foo" +}); + +// @filename: main.ts +import * as other2 from "./other"; +export const obj = { + [other2.default.name]: 1 +}; \ No newline at end of file diff --git a/tests/cases/compiler/importNotElidedWhenNotFound.ts b/tests/cases/compiler/importNotElidedWhenNotFound.ts index 2ff7cd64d0d..7781dc9d430 100644 --- a/tests/cases/compiler/importNotElidedWhenNotFound.ts +++ b/tests/cases/compiler/importNotElidedWhenNotFound.ts @@ -5,4 +5,12 @@ class Y extends Z { constructor() { super(X); } -} \ No newline at end of file +} + +import X2 from 'file2'; +import X3 from 'file3'; +class Q extends Z { + constructor() { + super(X2, X3); + } +} diff --git a/tests/cases/compiler/metadataImportType.ts b/tests/cases/compiler/metadataImportType.ts new file mode 100644 index 00000000000..531764f574a --- /dev/null +++ b/tests/cases/compiler/metadataImportType.ts @@ -0,0 +1,6 @@ +// @experimentalDecorators: true +// @emitDecoratorMetadata: true +export class A { + @test + b: import('./b').B +} \ No newline at end of file diff --git a/tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction11_es5.ts b/tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction11_es5.ts new file mode 100644 index 00000000000..98630114be9 --- /dev/null +++ b/tests/cases/conformance/async/es5/asyncArrowFunction/asyncArrowFunction11_es5.ts @@ -0,0 +1,10 @@ +// @target: es5 +// @lib: esnext, dom +// @downlevelIteration: true +// https://github.com/Microsoft/TypeScript/issues/24722 +class A { + b = async (...args: any[]) => { + await Promise.resolve(); + const obj = { ["a"]: () => this }; // computed property name after `await` triggers case + }; +} \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember_generator_function.ts b/tests/cases/fourslash/codeFixAddMissingMember_generator_function.ts new file mode 100644 index 00000000000..6742cc43348 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember_generator_function.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// *method() { +//// yield* this.y(); +//// } +////} + +verify.codeFixAll({ + fixId: "addMissingMember", + fixAllDescription: "Add all missing members", + newFileContent: + `class C { + *method() { + yield* this.y(); + } + *y(): any { + throw new Error("Method not implemented."); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixAddMissingMember_non_generator_function.ts b/tests/cases/fourslash/codeFixAddMissingMember_non_generator_function.ts new file mode 100644 index 00000000000..a868646446a --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingMember_non_generator_function.ts @@ -0,0 +1,21 @@ +/// + +////class C { +//// method() { +//// yield* this.y(); +//// } +////} + +verify.codeFixAll({ + fixId: "addMissingMember", + fixAllDescription: "Add all missing members", + newFileContent: + `class C { + method() { + yield* this.y(); + } + y(): any { + throw new Error("Method not implemented."); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixClassPropertyInitialization.ts b/tests/cases/fourslash/codeFixClassPropertyInitialization.ts index 6b7cbbc2705..fd1317ccaeb 100644 --- a/tests/cases/fourslash/codeFixClassPropertyInitialization.ts +++ b/tests/cases/fourslash/codeFixClassPropertyInitialization.ts @@ -12,15 +12,15 @@ //// //// class T { //// -//// a: string; +//// a: boolean; //// -//// static b: string; +//// static b: boolean; //// -//// private c: string; +//// private c: boolean; //// //// d: number | undefined; //// -//// e: string | number; +//// e: string | boolean; //// //// f: 1; //// @@ -46,9 +46,9 @@ function fixes(name: string, type: string, options: { isPrivate?: boolean, noIni } verify.codeFixAvailable([ - ...fixes("a", "string"), - ...fixes("c", "string", { isPrivate: true }), - ...fixes("e", "string | number"), + ...fixes("a", "boolean"), + ...fixes("c", "boolean", { isPrivate: true }), + ...fixes("e", "string | boolean"), ...fixes("f", "1"), ...fixes("g", '"123" | "456"'), ...fixes("h", "boolean"), diff --git a/tests/cases/fourslash/codeFixClassPropertyInitialization3.ts b/tests/cases/fourslash/codeFixClassPropertyInitialization3.ts index 329c6107ac7..8b76da581fe 100644 --- a/tests/cases/fourslash/codeFixClassPropertyInitialization3.ts +++ b/tests/cases/fourslash/codeFixClassPropertyInitialization3.ts @@ -3,13 +3,13 @@ // @strict: true //// class T { -//// a: string; +//// a: boolean; //// } verify.codeFix({ description: `Add initializer to property 'a'`, newFileContent: `class T { - a: string = ""; + a: boolean = false; }`, index: 2 }) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassPropertyInitialization4.ts b/tests/cases/fourslash/codeFixClassPropertyInitialization4.ts deleted file mode 100644 index 17a363e15b3..00000000000 --- a/tests/cases/fourslash/codeFixClassPropertyInitialization4.ts +++ /dev/null @@ -1,15 +0,0 @@ -/// - -// @strict: true - -//// class T { -//// a: number; -//// } - -verify.codeFix({ - description: `Add initializer to property 'a'`, - newFileContent: `class T { - a: number = 0; -}`, - index: 2 -}) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassPropertyInitialization8.ts b/tests/cases/fourslash/codeFixClassPropertyInitialization8.ts index 5c1f7873c16..8af1dd263dd 100644 --- a/tests/cases/fourslash/codeFixClassPropertyInitialization8.ts +++ b/tests/cases/fourslash/codeFixClassPropertyInitialization8.ts @@ -3,13 +3,13 @@ // @strict: true //// class T { -//// a: string | number; +//// a: string | boolean; //// } verify.codeFix({ description: `Add initializer to property 'a'`, newFileContent: `class T { - a: string | number = ""; + a: string | boolean = false; }`, index: 2 }) \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassPropertyInitialization_all_3.ts b/tests/cases/fourslash/codeFixClassPropertyInitialization_all_3.ts index 5931c6bd977..08584a01f91 100644 --- a/tests/cases/fourslash/codeFixClassPropertyInitialization_all_3.ts +++ b/tests/cases/fourslash/codeFixClassPropertyInitialization_all_3.ts @@ -12,15 +12,15 @@ //// //// class T { //// -//// a: string; +//// a: boolean; //// -//// static b: string; +//// static b: boolean; //// -//// private c: string; +//// private c: boolean; //// //// d: number | undefined; //// -//// e: string | number; +//// e: string | boolean; //// //// f: 1; //// @@ -35,6 +35,8 @@ //// k: AT; //// //// l: Foo; +//// +//// m: number[]; //// } verify.codeFixAll({ @@ -50,15 +52,15 @@ class Foo {} class T { - a: string = ""; + a: boolean = false; - static b: string; + static b: boolean; - private c: string = ""; + private c: boolean = false; d: number | undefined; - e: string | number = ""; + e: string | boolean = false; f: 1 = 1; @@ -73,5 +75,7 @@ class T { k: AT = new AT; l: Foo = new Foo; + + m: number[] = []; }` }); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts new file mode 100644 index 00000000000..61ff1211c7c --- /dev/null +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts @@ -0,0 +1,24 @@ +/// + +// @noLib: true +// @noUnusedLocals: true + +////let x = 0; +////x = 1; +//// +////export class C { +//// private p: number; +//// +//// m() { this.p = 0; } +////} + +verify.codeFixAll({ + fixId: "unusedIdentifier_delete", + fixAllDescription: "Delete all unused declarations", + newFileContent: +` +export class C { + + m() { } +}`, +}); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts index 6ed5973d400..e0783706085 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts @@ -57,7 +57,7 @@ verify.codeFixAll({ x; z; } { - const [x,] = o; + const [x] = o; x; } { @@ -65,7 +65,7 @@ verify.codeFixAll({ y; } { - const [, y,] = o; + const [, y] = o; y; } { diff --git a/tests/cases/fourslash/completionListAfterStringLiteral1.ts b/tests/cases/fourslash/completionListAfterStringLiteral1.ts index 533b428cdf5..9837f8a628c 100644 --- a/tests/cases/fourslash/completionListAfterStringLiteral1.ts +++ b/tests/cases/fourslash/completionListAfterStringLiteral1.ts @@ -2,6 +2,10 @@ ////"a"./**/ -goTo.marker(); -verify.not.completionListContains('alert'); -verify.completionListContains('charAt'); \ No newline at end of file +verify.completions({ + marker: "", + exact: [ + "toString", "charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "localeCompare", "match", "replace", "search", "slice", + "split", "substring", "toLowerCase", "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase", "trim", "length", "substr", "valueOf", + ], +}); diff --git a/tests/cases/fourslash/completionsLiterals.ts b/tests/cases/fourslash/completionsLiterals.ts new file mode 100644 index 00000000000..475d3dd247e --- /dev/null +++ b/tests/cases/fourslash/completionsLiterals.ts @@ -0,0 +1,12 @@ +/// + +////const x: 0 | "one" = /**/; + +verify.completions({ + marker: "", + includes: [ + { name: "0", kind: "string", text: "0" }, + { name: '"one"', kind: "string", text: '"one"' }, + ], + isNewIdentifierLocation: true, +}); diff --git a/tests/cases/fourslash/getJavaScriptCompletions12.ts b/tests/cases/fourslash/getJavaScriptCompletions12.ts index df09b041e62..a819f26e250 100644 --- a/tests/cases/fourslash/getJavaScriptCompletions12.ts +++ b/tests/cases/fourslash/getJavaScriptCompletions12.ts @@ -24,7 +24,7 @@ ////var test1 = function(x) { return x./*4*/ }, test2 = function(a) { return a./*5*/ }; verify.completions( - { marker: "1", includes: { name: "charCodeAt", kind: "method" }, isNewIdentifierLocation: true }, + { marker: "1", includes: { name: "charCodeAt", kind: "method" } }, { marker: ["2", "3", "4"], includes: { name: "toExponential", kind: "method" } }, { marker: "5", includes: { name: "test1", kind: "warning" } }, ); diff --git a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules7.ts b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules7.ts index 9032018a7f5..beaaad57f19 100644 --- a/tests/cases/fourslash/importNameCodeFixNewImportNodeModules7.ts +++ b/tests/cases/fourslash/importNameCodeFixNewImportNodeModules7.ts @@ -15,15 +15,8 @@ // @Filename: node_modules/package-name/package.json //// { "main": "bin/lib/libfile.js" } - -// In this case, importing the module by its package name: -// import { f1 } from 'package-name' -// could in theory work, however the resulting code compiles with a module resolution error -// since bin/lib/libfile.d.ts isn't declared under "typings" in package.json -// Therefore just import the module by its qualified path - verify.importFixAtPosition([ -`import { f1 } from "package-name/bin/lib/libfile"; +`import { f1 } from "package-name"; f1('');` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/javaScriptClass1.ts b/tests/cases/fourslash/javaScriptClass1.ts index fbbb3c4880d..dd4ed33f718 100644 --- a/tests/cases/fourslash/javaScriptClass1.ts +++ b/tests/cases/fourslash/javaScriptClass1.ts @@ -22,7 +22,7 @@ edit.insert('.'); verify.completions({ exact: ["bar", "thing", "union", "Foo", "x"] }); edit.insert('bar.'); -verify.completions({ includes: ["substr"], isNewIdentifierLocation: true }); +verify.completions({ includes: ["substr"] }); edit.backspace('bar.'.length); edit.insert('union.'); diff --git a/tests/cases/fourslash/moveToNewFile_inferQuoteStyle.ts b/tests/cases/fourslash/moveToNewFile_inferQuoteStyle.ts new file mode 100644 index 00000000000..1e7b4347a87 --- /dev/null +++ b/tests/cases/fourslash/moveToNewFile_inferQuoteStyle.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /a.ts +////import 'unrelated'; +//// +////[|const x = 0;|] +////x; + +verify.moveToNewFile({ + newFileContents: { + "/a.ts": +`import { x } from './x'; + +import 'unrelated'; + +x;`, + + "/x.ts": +`export const x = 0;`, + }, +}); diff --git a/tests/cases/fourslash/moveToNewFile_jsx.ts b/tests/cases/fourslash/moveToNewFile_jsx.ts new file mode 100644 index 00000000000..b969794af30 --- /dev/null +++ b/tests/cases/fourslash/moveToNewFile_jsx.ts @@ -0,0 +1,14 @@ +/// + +// @Filename: /a.tsx +////[|
a
;|] + +verify.moveToNewFile({ + newFileContents: { + "/a.tsx": +``, + + "/newFile.tsx": +`
a
;`, + } +}); diff --git a/tests/cases/fourslash/navigationBarInitializerSpans.ts b/tests/cases/fourslash/navigationBarInitializerSpans.ts index 67752c85577..7b044db9c4c 100644 --- a/tests/cases/fourslash/navigationBarInitializerSpans.ts +++ b/tests/cases/fourslash/navigationBarInitializerSpans.ts @@ -1,9 +1,9 @@ /// -////const [|x = () => 0|]; -////const f = [|function f() {}|]; +////const [|[|x|] = () => 0|]; +////const f = [|function [|f|]() {}|]; -const [s0, s1] = test.spans(); +const [s0, s0Name, s1, s1Name] = test.spans(); const sGlobal = { start: 0, length: 45 }; verify.navigationTree({ @@ -15,11 +15,13 @@ verify.navigationTree({ text: "f", kind: "function", spans: [s1], + nameSpan: s1Name, }, { text: "x", kind: "const", spans: [s0], + nameSpan: s0Name, }, ] }, { checkSpans: true }); diff --git a/tests/projects/outfile-concat/first/first_part1.ts b/tests/projects/outfile-concat/first/first_part1.ts new file mode 100644 index 00000000000..b8810033aaa --- /dev/null +++ b/tests/projects/outfile-concat/first/first_part1.ts @@ -0,0 +1,11 @@ +interface TheFirst { + none: any; +} + +const s = "Hello, world"; + +interface NoJsForHereEither { + none: any; +} + +console.log(s); diff --git a/tests/projects/outfile-concat/first/first_part2.ts b/tests/projects/outfile-concat/first/first_part2.ts new file mode 100644 index 00000000000..bd60d3eba9f --- /dev/null +++ b/tests/projects/outfile-concat/first/first_part2.ts @@ -0,0 +1 @@ +console.log(f()); diff --git a/tests/projects/outfile-concat/first/first_part3.ts b/tests/projects/outfile-concat/first/first_part3.ts new file mode 100644 index 00000000000..6f497fc490a --- /dev/null +++ b/tests/projects/outfile-concat/first/first_part3.ts @@ -0,0 +1,3 @@ +function f() { + return "JS does hoists"; +} \ No newline at end of file diff --git a/tests/projects/outfile-concat/first/tsconfig.json b/tests/projects/outfile-concat/first/tsconfig.json new file mode 100644 index 00000000000..8370f6512b8 --- /dev/null +++ b/tests/projects/outfile-concat/first/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es5", + "composite": true, + "removeComments": true, + "strict": false, + "sourceMap": true, + "declarationMap": true, + "declaration": true, + "outFile": "./bin/first-output.js" + }, + "references": [ + ] +} diff --git a/tests/projects/outfile-concat/second/second_part1.ts b/tests/projects/outfile-concat/second/second_part1.ts new file mode 100644 index 00000000000..2b995fbe4a5 --- /dev/null +++ b/tests/projects/outfile-concat/second/second_part1.ts @@ -0,0 +1,11 @@ +namespace N { + // Comment text +} + +namespace N { + function f() { + console.log('testing'); + } + + f(); +} diff --git a/tests/projects/outfile-concat/second/second_part2.ts b/tests/projects/outfile-concat/second/second_part2.ts new file mode 100644 index 00000000000..b81737e8915 --- /dev/null +++ b/tests/projects/outfile-concat/second/second_part2.ts @@ -0,0 +1,5 @@ +class C { + doSomething() { + console.log("something got done"); + } +} diff --git a/tests/projects/outfile-concat/second/tsconfig.json b/tests/projects/outfile-concat/second/tsconfig.json new file mode 100644 index 00000000000..d835cff6d66 --- /dev/null +++ b/tests/projects/outfile-concat/second/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es5", + "composite": true, + "removeComments": true, + "strict": false, + "sourceMap": true, + "declarationMap": true, + "declaration": true, + "outFile": "../2/second-output.js" + }, + "references": [ + ] +} diff --git a/tests/projects/outfile-concat/third/third_part1.ts b/tests/projects/outfile-concat/third/third_part1.ts new file mode 100644 index 00000000000..948688ae5ff --- /dev/null +++ b/tests/projects/outfile-concat/third/third_part1.ts @@ -0,0 +1,2 @@ +var c = new C(); +c.doSomething(); diff --git a/tests/projects/outfile-concat/third/tsconfig.json b/tests/projects/outfile-concat/third/tsconfig.json new file mode 100644 index 00000000000..18c98608db1 --- /dev/null +++ b/tests/projects/outfile-concat/third/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es5", + "composite": true, + "removeComments": true, + "strict": false, + "sourceMap": true, + "declarationMap": true, + "declaration": true, + "outFile": "./thirdjs/output/third-output.js" + }, + "references": [ + { "path": "../first", "prepend": true }, + { "path": "../second", "prepend": true }, + ] +} diff --git a/tests/projects/sample1/core/index.ts b/tests/projects/sample1/core/index.ts new file mode 100644 index 00000000000..529a7f549ec --- /dev/null +++ b/tests/projects/sample1/core/index.ts @@ -0,0 +1,3 @@ +export const someString: string = "HELLO WORLD"; +export function leftPad(s: string, n: number) { return s + n; } +export function multiply(a: number, b: number) { return a * b; } diff --git a/tests/projects/sample1/core/tsconfig.json b/tests/projects/sample1/core/tsconfig.json new file mode 100644 index 00000000000..b8332f5c476 --- /dev/null +++ b/tests/projects/sample1/core/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true + } +} \ No newline at end of file diff --git a/tests/projects/sample1/logic/index.ts b/tests/projects/sample1/logic/index.ts new file mode 100644 index 00000000000..fd6b2106bb8 --- /dev/null +++ b/tests/projects/sample1/logic/index.ts @@ -0,0 +1,4 @@ +import * as c from '../core/index'; +export function getSecondsInDay() { + return c.multiply(10, 15); +} diff --git a/tests/projects/sample1/logic/tsconfig.json b/tests/projects/sample1/logic/tsconfig.json new file mode 100644 index 00000000000..a58b3a9f48e --- /dev/null +++ b/tests/projects/sample1/logic/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true + }, + "references": [ + { "path": "../core" } + ] +} diff --git a/tests/projects/sample1/tests/index.ts b/tests/projects/sample1/tests/index.ts new file mode 100644 index 00000000000..f89dcd08a82 --- /dev/null +++ b/tests/projects/sample1/tests/index.ts @@ -0,0 +1,5 @@ +import * as c from '../core/index'; +import * as logic from '../logic/index'; + +c.leftPad("", 10); +logic.getSecondsInDay(); diff --git a/tests/projects/sample1/tests/tsconfig.json b/tests/projects/sample1/tests/tsconfig.json new file mode 100644 index 00000000000..437d8ca6fb3 --- /dev/null +++ b/tests/projects/sample1/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "references": [ + { "path": "../core" }, + { "path": "../logic" } + ], + "files": ["index.ts"] +} \ No newline at end of file diff --git a/tests/projects/sample1/ui/index.ts b/tests/projects/sample1/ui/index.ts new file mode 100644 index 00000000000..9d7e7e3a89e --- /dev/null +++ b/tests/projects/sample1/ui/index.ts @@ -0,0 +1,5 @@ +import * as logic from '../logic'; + +export function run() { + console.log(logic.getSecondsInDay()); +} diff --git a/tests/projects/sample1/ui/tsconfig.json b/tests/projects/sample1/ui/tsconfig.json new file mode 100644 index 00000000000..d843e35c549 --- /dev/null +++ b/tests/projects/sample1/ui/tsconfig.json @@ -0,0 +1,5 @@ +{ + "references": [ + { "path": "../logic/index" } + ] +}