diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 676a3afb561..fe225d92254 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3,8 +3,6 @@ /* @internal */ namespace ts { - export let bindTime = 0; - export const enum ModuleInstanceState { NonInstantiated = 0, Instantiated = 1, @@ -96,9 +94,9 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - const start = new Date().getTime(); + const bindStart = performance.mark(); binder(file, options); - bindTime += new Date().getTime() - start; + performance.measure("bindTime", bindStart); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 344dd35a5c3..c03c5a163cf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14,8 +14,6 @@ namespace ts { return node.id; } - export let checkTime = 0; - export function getSymbolId(symbol: Symbol): number { if (!symbol.id) { symbol.id = nextSymbolId; @@ -16066,11 +16064,11 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - const start = new Date().getTime(); + const checkStart = performance.mark(); checkSourceFileWorker(node); - checkTime += new Date().getTime() - start; + performance.measure("checkTime", checkStart); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index fef057b9107..2cabb930a68 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -27,6 +27,11 @@ namespace ts { name: "diagnostics", type: "boolean", }, + { + name: "extendedDiagnostics", + type: "boolean", + experimental: true, + }, { name: "emitBOM", type: "boolean" diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 343b388ffe2..a7c7c34ab40 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -7,7 +7,6 @@ namespace ts { setSourceFile(sourceFile: SourceFile): void; getLeadingComments(range: TextRange): CommentRange[]; getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; - getLeadingCommentsOfPosition(pos: number): CommentRange[]; getTrailingComments(range: TextRange): CommentRange[]; getTrailingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; getTrailingCommentsOfPosition(pos: number): CommentRange[]; @@ -32,20 +31,23 @@ namespace ts { // This maps start->end for a comment range. See `hasConsumedCommentRange` and // `consumeCommentRange` for usage. - let consumedCommentRanges: number[]; - let leadingCommentRangePositions: boolean[]; - let trailingCommentRangePositions: boolean[]; + let consumedCommentRanges: Map; + let leadingCommentRangePositions: Map; + let trailingCommentRangePositions: Map; - return compilerOptions.removeComments + const commentWriter = compilerOptions.removeComments ? createCommentRemovingWriter() : createCommentPreservingWriter(); + return compilerOptions.extendedDiagnostics + ? createCommentWriterWithExtendedDiagnostics(commentWriter) + : commentWriter; + function createCommentRemovingWriter(): CommentWriter { return { reset, setSourceFile, getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { return undefined; }, - getLeadingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { return undefined; }, getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { }, @@ -69,7 +71,6 @@ namespace ts { reset, setSourceFile, getLeadingComments, - getLeadingCommentsOfPosition, getTrailingComments, getTrailingCommentsOfPosition, emitLeadingComments, @@ -100,11 +101,10 @@ namespace ts { return filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); } - return undefined; + return; } } - return getLeadingCommentsOfPosition(range.pos); } @@ -127,15 +127,21 @@ namespace ts { function getTrailingComments(range: TextRange): CommentRange[]; function getTrailingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; function getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { + let ignored = false; if (contextNode) { if (ignoreNodeCallback(contextNode)) { - return undefined; + ignored = true; + } + else { + range = getTextRangeCallback(contextNode) || range; } - - range = getTextRangeCallback(contextNode) || range; } - return getTrailingCommentsOfPosition(range.end); + let comments: CommentRange[]; + if (!ignored) { + comments = getTrailingCommentsOfPosition(range.end); + } + return comments; } function getLeadingCommentsOfPosition(pos: number) { @@ -249,6 +255,63 @@ namespace ts { } } + function createCommentWriterWithExtendedDiagnostics(writer: CommentWriter): CommentWriter { + const { + reset, + setSourceFile, + getLeadingComments, + getTrailingComments, + getTrailingCommentsOfPosition, + emitLeadingComments, + emitTrailingComments, + emitLeadingDetachedComments, + emitTrailingDetachedComments + } = writer; + + return { + reset, + setSourceFile, + getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { + const commentStart = performance.mark(); + const comments = getLeadingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); + performance.measure("commentTime", commentStart); + return comments; + }, + getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { + const commentStart = performance.mark(); + const comments = getTrailingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); + performance.measure("commentTime", commentStart); + return comments; + }, + getTrailingCommentsOfPosition(pos: number): CommentRange[] { + const commentStart = performance.mark(); + const comments = getTrailingCommentsOfPosition(pos); + performance.measure("commentTime", commentStart); + return comments; + }, + emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { + const commentStart = performance.mark(); + emitLeadingComments(range, comments, contextNode, getTextRangeCallback); + performance.measure("commentTime", commentStart); + }, + emitTrailingComments(range: TextRange, comments: CommentRange[]): void { + const commentStart = performance.mark(); + emitLeadingComments(range, comments); + performance.measure("commentTime", commentStart); + }, + emitLeadingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { + const commentStart = performance.mark(); + emitLeadingDetachedComments(range, contextNode, ignoreNodeCallback); + performance.measure("commentTime", commentStart); + }, + emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { + const commentStart = performance.mark(); + emitTrailingDetachedComments(range, contextNode, ignoreNodeCallback); + performance.measure("commentTime", commentStart); + } + }; + } + function reset() { currentSourceFile = undefined; currentText = undefined; @@ -264,9 +327,9 @@ namespace ts { currentText = sourceFile.text; currentLineMap = getLineStarts(sourceFile); detachedCommentsInfo = undefined; - consumedCommentRanges = []; - leadingCommentRangePositions = []; - trailingCommentRangePositions = []; + consumedCommentRanges = {}; + leadingCommentRangePositions = {}; + trailingCommentRangePositions = {}; } function hasDetachedComments(pos: number) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bae17c642d2..324975a9da9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1049,6 +1049,7 @@ namespace ts { } function Node(kind: SyntaxKind, pos: number, end: number) { + this.id = 0; this.kind = kind; this.pos = pos; this.end = end; @@ -1058,6 +1059,7 @@ namespace ts { this.excludeTransformFlags = TransformFlags.None; this.parent = undefined; this.original = undefined; + this.transformId = 0; } export let objectAllocator: ObjectAllocator = { @@ -1118,6 +1120,18 @@ namespace ts { } } + export function getEnvironmentVariable(name: string, host?: CompilerHost) { + if (host && host.getEnvironmentVariable) { + return host.getEnvironmentVariable(name); + } + + if (sys && sys.getEnvironmentVariable) { + return sys.getEnvironmentVariable(name); + } + + return ""; + } + export function copyListRemovingItem(item: T, list: T[]) { const copiedList: T[] = []; for (const e of list) { @@ -1134,4 +1148,80 @@ namespace ts { : ((fileName) => fileName.toLowerCase()); } + /** Performance measurements for the compiler. */ + /*@internal*/ + export namespace performance { + let counters: Map; + let measures: Map; + + /** + * Increments a counter with the specified name. + * + * @param counterName The name of the counter. + */ + export function increment(counterName: string) { + if (counters) { + counters[counterName] = (getProperty(counters, counterName) || 0) + 1; + } + } + + /** + * Gets the value of the counter with the specified name. + * + * @param counterName The name of the counter. + */ + export function getCount(counterName: string) { + return counters && getProperty(counters, counterName) || 0; + } + + /** + * Marks the start of a performance measurement. + */ + export function mark() { + return measures ? Date.now() : 0; + } + + /** + * Adds a performance measurement with the specified name. + * + * @param measureName The name of the performance measurement. + * @param marker The timestamp of the starting mark. + */ + export function measure(measureName: string, marker: number) { + if (measures) { + measures[measureName] = (getProperty(measures, measureName) || 0) + (mark() - marker); + } + } + + /** + * Gets the total duration of all measurements with the supplied name. + * + * @param measureName The name of the measure whose durations should be accumulated. + */ + export function getDuration(measureName: string) { + return measures && getProperty(measures, measureName) || 0; + } + + /** Enables (and resets) performance measurements for the compiler. */ + export function enable() { + counters = { }; + measures = { + programTime: 0, + parseTime: 0, + bindTime: 0, + emitTime: 0, + ioReadTime: 0, + ioWriteTime: 0, + printTime: 0, + commentTime: 0, + sourceMapTime: 0 + }; + } + + /** Disables (and clears) performance measurements for the compiler. */ + export function disable() { + counters = undefined; + measures = undefined; + } + } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8bcdd4c8201..c2d602204dd 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -524,7 +524,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge const writer = createTextWriter(newLine); const { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer; - let sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter(); + let sourceMap = createSourceMapWriter(host, writer); let { setSourceFile, emitStart, emitEnd, emitPos } = sourceMap; let currentSourceFile: SourceFile; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 5523baf54fd..d04d7f0fd0d 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -8,7 +8,7 @@ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags, emitOptions?: NodeEmitOptions): Node { + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { const ConstructorForKind = kind === SyntaxKind.SourceFile ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); @@ -17,13 +17,7 @@ namespace ts { ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); - if (flags) { - node.flags = flags; - } - - if (emitOptions) { - node.emitOptions = emitOptions; - } + node.flags = flags | NodeFlags.Synthesized; return node; } @@ -68,12 +62,11 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node with no source map location. */ - export function getSynthesizedClone(node: T, emitOptions?: NodeEmitOptions): T { + export function getSynthesizedClone(node: T): T { // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). - const clone = createNode(node.kind, /*location*/ undefined, /*flags*/ undefined, emitOptions); - clone.flags = node.flags; + const clone = createNode(node.kind, /*location*/ undefined, node.flags); clone.original = node; for (const key in node) { @@ -90,46 +83,36 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node for mutation. */ - export function getMutableClone(node: T, emitOptions?: NodeEmitOptions): T { - const clone = getSynthesizedClone(node, emitOptions); + export function getMutableClone(node: T): T { + const clone = getSynthesizedClone(node); clone.pos = node.pos; clone.end = node.end; clone.parent = node.parent; return clone; } - /** - * Gets a clone of a node with a unique node ID. - */ - export function getUniqueClone(node: T, emitOptions?: NodeEmitOptions): T { - const clone = getMutableClone(node, emitOptions); - clone.id = undefined; - getNodeId(clone); - return clone; - } - // Literals - export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions): StringLiteral; - export function createLiteral(value: string, location?: TextRange, emitOptions?: NodeEmitOptions): StringLiteral; - export function createLiteral(value: number, location?: TextRange, emitOptions?: NodeEmitOptions): LiteralExpression; - export function createLiteral(value: string | number | boolean, location?: TextRange, emitOptions?: NodeEmitOptions): PrimaryExpression; - export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions): PrimaryExpression { + export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange): StringLiteral; + export function createLiteral(value: string, location?: TextRange): StringLiteral; + export function createLiteral(value: number, location?: TextRange): LiteralExpression; + export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; + export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange): PrimaryExpression { if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral, location, /*flags*/ undefined, emitOptions); + const node = createNode(SyntaxKind.NumericLiteral, location, /*flags*/ undefined); node.text = value.toString(); return node; } else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location, /*flags*/ undefined, emitOptions); + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location, /*flags*/ undefined); } else if (typeof value === "string") { - const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined, emitOptions); + const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined); node.text = value; return node; } else { - const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined, emitOptions); + const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined); node.textSourceNode = value; node.text = value.text; return node; @@ -138,17 +121,23 @@ namespace ts { // Identifiers + let nextAutoGenerateId = 0; + export function createIdentifier(text: string, location?: TextRange): Identifier { const node = createNode(SyntaxKind.Identifier, location); node.text = escapeIdentifier(text); node.originalKeywordKind = stringToToken(text); + node.autoGenerateKind = GeneratedIdentifierKind.None; + node.autoGenerateId = 0; return node; } export function createTempVariable(recordTempVariable: (node: Identifier) => void, location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); + name.text = ""; + name.originalKeywordKind = SyntaxKind.Unknown; name.autoGenerateKind = GeneratedIdentifierKind.Auto; - getNodeId(name); + name.autoGenerateId = nextAutoGenerateId++; if (recordTempVariable) { recordTempVariable(name); } @@ -157,24 +146,29 @@ namespace ts { export function createLoopVariable(location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); + name.text = ""; + name.originalKeywordKind = SyntaxKind.Unknown; name.autoGenerateKind = GeneratedIdentifierKind.Loop; - getNodeId(name); + name.autoGenerateId = nextAutoGenerateId++; return name; } export function createUniqueName(text: string, location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); name.text = text; + name.originalKeywordKind = SyntaxKind.Unknown; name.autoGenerateKind = GeneratedIdentifierKind.Unique; - getNodeId(name); + name.autoGenerateId = nextAutoGenerateId++; return name; } export function getGeneratedNameForNode(node: Node, location?: TextRange): Identifier { const name = createNode(SyntaxKind.Identifier, location); - name.autoGenerateKind = GeneratedIdentifierKind.Node; name.original = node; - getNodeId(name); + name.text = ""; + name.originalKeywordKind = SyntaxKind.Unknown; + name.autoGenerateKind = GeneratedIdentifierKind.Node; + name.autoGenerateId = nextAutoGenerateId++; return name; } @@ -286,8 +280,8 @@ namespace ts { return node; } - export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange, emitOptions?: NodeEmitOptions) { - const node = createNode(SyntaxKind.PropertyAccessExpression, location, /*flags*/ undefined, emitOptions); + export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAccessExpression, location, /*flags*/ undefined); node.expression = parenthesizeForAccess(expression); node.dotToken = createSynthesizedNode(SyntaxKind.DotToken); node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -504,8 +498,8 @@ namespace ts { return node; } - export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange, emitOptions?: NodeEmitOptions) { - const node = createNode(SyntaxKind.ForStatement, location, /*flags*/ undefined, emitOptions); + export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { + const node = createNode(SyntaxKind.ForStatement, location, /*flags*/ undefined); node.initializer = initializer; node.condition = condition; node.incrementor = incrementor; @@ -735,9 +729,11 @@ namespace ts { return node; } - export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, setNodeEmitFlags: (node: Node, flags: NodeEmitFlags) => void, location?: TextRange): MemberExpression { if (isIdentifier(memberName)) { - return createPropertyAccess(target, memberName, location, { flags: NodeEmitFlags.NoNestedSourceMaps | NodeEmitFlags.Merge }); + const expression = createPropertyAccess(target, memberName, location); + setNodeEmitFlags(expression, NodeEmitFlags.NoNestedSourceMaps); + return expression; } else if (isComputedPropertyName(memberName)) { return createElementAccess(target, memberName.expression, location); @@ -815,9 +811,11 @@ namespace ts { } function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { - // Create an identifier and give it a parent. This allows us to resolve the react - // namespace during emit. + // To ensure the emit resolver can properly resolve the namespace, we need to + // treat this identifier as if it were a source tree node by clearing the `Synthesized` + // flag and setting a parent node. const react = createIdentifier(reactNamespace || "React"); + react.flags &= ~NodeFlags.Synthesized; react.parent = parent; return react; } @@ -1170,26 +1168,26 @@ namespace ts { return reduceLeft(expressions, createComma); } - export function createExpressionFromEntityName(node: EntityName | Expression, emitOptions?: NodeEmitOptions): Expression { + export function createExpressionFromEntityName(node: EntityName | Expression): Expression { if (isQualifiedName(node)) { - const left = createExpressionFromEntityName(node.left, emitOptions); - const right = getMutableClone(node.right, emitOptions && clone(emitOptions)); - return createPropertyAccess(left, right, /*location*/ node, emitOptions && clone(emitOptions)); + const left = createExpressionFromEntityName(node.left); + const right = getMutableClone(node.right); + return createPropertyAccess(left, right, /*location*/ node); } else { - return getMutableClone(node, emitOptions && clone(emitOptions)); + return getMutableClone(node); } } - export function createExpressionForPropertyName(memberName: PropertyName, emitOptions?: NodeEmitOptions): Expression { + export function createExpressionForPropertyName(memberName: PropertyName): Expression { if (isIdentifier(memberName)) { - return createLiteral(memberName, /*location*/ undefined, emitOptions); + return createLiteral(memberName, /*location*/ undefined); } else if (isComputedPropertyName(memberName)) { - return getMutableClone(memberName.expression, emitOptions); + return getMutableClone(memberName.expression); } else { - return getMutableClone(memberName, emitOptions); + return getMutableClone(memberName); } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 7689055c746..d9d1eee73d4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2034,7 +2034,7 @@ namespace ts { // FormalParameter [Yield,Await]: // BindingElement[?Yield,?Await] node.name = parseIdentifierOrPattern(); - if (getFullWidth(node.name) === 0 && node.flags === 0 && isModifierKind(token)) { + if (getFullWidth(node.name) === 0 && !hasModifiers(node) && isModifierKind(token)) { // in cases like // 'use strict' // function foo(static) diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 62d62ec024f..c577f7999f5 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -151,7 +151,7 @@ const _super = (function (geti, seti) { decreaseIndent } = writer; - const sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter(); + const sourceMap = createSourceMapWriter(host, writer); const { emitStart, emitEnd, @@ -180,7 +180,8 @@ const _super = (function (geti, seti) { let isEmitNotificationEnabled: (node: Node) => boolean; let onSubstituteNode: (node: Node, isExpression: boolean) => Node; let onEmitNode: (node: Node, emit: (node: Node) => void) => void; - let nodeToGeneratedName: string[]; + let nodeIdToGeneratedName: string[]; + let autoGeneratedIdToGeneratedName: string[]; let generatedNameSet: Map; let tempFlags: TempFlags; let currentSourceFile: SourceFile; @@ -197,7 +198,8 @@ const _super = (function (geti, seti) { function doPrint(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) { sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit); - nodeToGeneratedName = []; + nodeIdToGeneratedName = []; + autoGeneratedIdToGeneratedName = []; generatedNameSet = {}; isOwnFileEmit = !isBundledEmit; @@ -265,7 +267,7 @@ const _super = (function (geti, seti) { isEmitNotificationEnabled = context.isEmitNotificationEnabled; onSubstituteNode = context.onSubstituteNode; onEmitNode = context.onEmitNode; - return printSourceFile; + return compilerOptions.extendedDiagnostics ? printSourceFileWithExtendedDiagnostics : printSourceFile; } function printSourceFile(node: SourceFile) { @@ -278,6 +280,13 @@ const _super = (function (geti, seti) { return node; } + function printSourceFileWithExtendedDiagnostics(node: SourceFile) { + const printStart = performance.mark(); + printSourceFile(node); + performance.measure("printTime", printStart); + return node; + } + /** * Emits a node. */ @@ -2706,6 +2715,11 @@ const _super = (function (geti, seti) { return makeUniqueName("class"); } + /** + * Generates a unique name from a node. + * + * @param node A node. + */ function generateNameForNode(node: Node): string { switch (node.kind) { case SyntaxKind.Identifier: @@ -2727,46 +2741,69 @@ const _super = (function (geti, seti) { } } - function generateIdentifier(node: Identifier) { - switch (node.autoGenerateKind) { + /** + * Generates a unique identifier for a node. + * + * @param name A generated name. + */ + function generateName(name: Identifier) { + switch (name.autoGenerateKind) { case GeneratedIdentifierKind.Auto: return makeTempVariableName(TempFlags.Auto); case GeneratedIdentifierKind.Loop: return makeTempVariableName(TempFlags._i); case GeneratedIdentifierKind.Unique: - return makeUniqueName(node.text); - case GeneratedIdentifierKind.Node: - return generateNameForNode(getSourceNodeForGeneratedName(node)); + return makeUniqueName(name.text); } + + Debug.fail("Unsupported GeneratedIdentifierKind."); } - function getGeneratedIdentifier(node: Identifier) { - const id = getNodeIdForGeneratedIdentifier(node); - return nodeToGeneratedName[id] || (nodeToGeneratedName[id] = unescapeIdentifier(generateIdentifier(node))); - } + /** + * Gets the node from which a name should be generated. + * + * @param name A generated name wrapper. + */ + function getNodeForGeneratedName(name: Identifier) { + const autoGenerateId = name.autoGenerateId; + let node = name as Node; + let original = node.original; + while (original) { + node = original; - function getSourceNodeForGeneratedName(name: Identifier) { - let node: Node = name; - while (node.original !== undefined) { - const nodeId = node.id; - node = node.original; - // If "node" is not the exact clone of "original" identifier, use "original" identifier to generate the name - if (isIdentifier(node) && node.autoGenerateKind === GeneratedIdentifierKind.Node && node.id !== nodeId) { + // if "node" is a different generated name (having a different + // "autoGenerateId"), use it and stop traversing. + if (isIdentifier(node) + && node.autoGenerateKind === GeneratedIdentifierKind.Node + && node.autoGenerateId !== autoGenerateId) { break; } + + original = node.original; } + // otherwise, return the original node for the source; return node; } - function getNodeIdForGeneratedIdentifier(node: Identifier) { - switch (node.autoGenerateKind) { - case GeneratedIdentifierKind.Auto: - case GeneratedIdentifierKind.Loop: - case GeneratedIdentifierKind.Unique: - return getNodeId(node); - case GeneratedIdentifierKind.Node: - return getNodeId(getSourceNodeForGeneratedName(node)); + /** + * Gets the generated identifier text from a generated identifier. + * + * @param name The generated identifier. + */ + function getGeneratedIdentifier(name: Identifier) { + if (name.autoGenerateKind === GeneratedIdentifierKind.Node) { + // Generated names generate unique names based on their original node + // and are cached based on that node's id + const node = getNodeForGeneratedName(name); + const nodeId = getNodeId(node); + return nodeIdToGeneratedName[nodeId] || (nodeIdToGeneratedName[nodeId] = unescapeIdentifier(generateNameForNode(node))); + } + else { + // Auto, Loop, and Unique names are cached based on their unique + // autoGenerateId. + const autoGenerateId = name.autoGenerateId; + return autoGeneratedIdToGeneratedName[autoGenerateId] || (autoGeneratedIdToGeneratedName[autoGenerateId] = unescapeIdentifier(generateName(name))); } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bf5b7cb5727..03db19ed445 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -4,11 +4,6 @@ /// namespace ts { - /* @internal */ export let programTime = 0; - /* @internal */ export let emitTime = 0; - /* @internal */ export let ioReadTime = 0; - /* @internal */ export let ioWriteTime = 0; - /** The version of the TypeScript compiler release */ const emptyArray: any[] = []; @@ -781,9 +776,9 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - const start = new Date().getTime(); + const ioReadStart = performance.mark(); text = sys.readFile(fileName, options.charset); - ioReadTime += new Date().getTime() - start; + performance.measure("ioReadTime", ioReadStart); } catch (e) { if (onError) { @@ -850,7 +845,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - const start = new Date().getTime(); + const ioWriteStart = performance.mark(); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -860,7 +855,7 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - ioWriteTime += new Date().getTime() - start; + performance.measure("ioWriteTime", ioWriteStart); } catch (e) { if (onError) { @@ -962,7 +957,7 @@ namespace ts { let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); - const start = new Date().getTime(); + const programStart = performance.mark(); host = host || createCompilerHost(options); @@ -1055,7 +1050,7 @@ namespace ts { verifyCompilerOptions(); - programTime += new Date().getTime() - start; + performance.measure("programTime", programStart); return program; @@ -1288,7 +1283,7 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - const start = new Date().getTime(); + const emitStart = performance.mark(); // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. let useLegacyEmitter = options.useLegacyEmitter; @@ -1302,7 +1297,8 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - emitTime += new Date().getTime() - start; + performance.measure("emitTime", emitStart); + return emitResult; } @@ -2076,7 +2072,7 @@ namespace ts { } // Cannot specify module gen that isn't amd or system with --out - // Report this error if user specified --module moduleKind + // Report this error if user specified --module moduleKind // or if there is external module in compilation which defaults to commonjs const emitModuleKind = getEmitModuleKind(options); if (outFile && (options.module || firstExternalModuleSourceFile) && !(emitModuleKind === ModuleKind.AMD || emitModuleKind === ModuleKind.System)) { diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 1e280a06cf6..1a3ece1f5db 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -593,7 +593,7 @@ namespace ts { function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] { let result: CommentRange[]; let collecting = trailing || pos === 0; - while (pos < text.length) { + while (pos >= 0 && pos < text.length) { const ch = text.charCodeAt(pos); switch (ch) { case CharacterCodes.carriageReturn: diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 58df47c770c..de056196bd0 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -170,6 +170,20 @@ namespace ts { getSourceMappingURL(): string; } + export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { + const compilerOptions = host.getCompilerOptions(); + if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) { + if (compilerOptions.extendedDiagnostics) { + return createSourceMapWriterWithExtendedDiagnostics(host, writer); + } + + return createSourceMapWriterWorker(host, writer); + } + else { + return getNullSourceMapWriter(); + } + } + let nullSourceMapWriter: SourceMapWriter; export function getNullSourceMapWriter(): SourceMapWriter { @@ -182,8 +196,8 @@ namespace ts { emitPos(pos: number): void { }, emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { }, emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { }, - emitTokenStart(token: SyntaxKind, pos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind, pos: number) => TextRange): number { return -1; }, - emitTokenEnd(token: SyntaxKind, end: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind, pos: number) => TextRange): number { return -1; }, + emitTokenStart(token: SyntaxKind, pos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; }, + emitTokenEnd(token: SyntaxKind, end: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { return -1; }, changeEmitSourcePos(): void { }, stopOverridingSpan(): void { }, getText(): string { return undefined; }, @@ -203,7 +217,7 @@ namespace ts { sourceIndex: 0 }; - export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { + function createSourceMapWriterWorker(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { const compilerOptions = host.getCompilerOptions(); let currentSourceFile: SourceFile; let currentSourceText: string; @@ -746,6 +760,61 @@ namespace ts { } } + function createSourceMapWriterWithExtendedDiagnostics(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { + const { + initialize, + reset, + getSourceMapData, + setSourceFile, + emitPos, + emitStart, + emitEnd, + emitTokenStart, + emitTokenEnd, + changeEmitSourcePos, + stopOverridingSpan, + getText, + getSourceMappingURL, + } = createSourceMapWriterWorker(host, writer); + return { + initialize, + reset, + getSourceMapData, + setSourceFile, + emitPos(pos: number): void { + const sourcemapStart = performance.mark(); + emitPos(pos); + performance.measure("sourceMapTime", sourcemapStart); + }, + emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { + const sourcemapStart = performance.mark(); + emitStart(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); + performance.measure("sourceMapTime", sourcemapStart); + }, + emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { + const sourcemapStart = performance.mark(); + emitEnd(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); + performance.measure("sourceMapTime", sourcemapStart); + }, + emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { + const sourcemapStart = performance.mark(); + tokenStartPos = emitTokenStart(token, tokenStartPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); + performance.measure("sourceMapTime", sourcemapStart); + return tokenStartPos; + }, + emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { + const sourcemapStart = performance.mark(); + tokenEndPos = emitTokenEnd(token, tokenEndPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); + performance.measure("sourceMapTime", sourcemapStart); + return tokenEndPos; + }, + changeEmitSourcePos, + stopOverridingSpan, + getText, + getSourceMappingURL, + }; + } + const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; function base64FormatEncode(inValue: number) { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index a18b8546db8..a448893f345 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -23,6 +23,100 @@ namespace ts { EmitNotifications = 1 << 1, } + /* @internal */ + export interface TransformationContext extends LexicalEnvironment { + getCompilerOptions(): CompilerOptions; + getEmitResolver(): EmitResolver; + getEmitHost(): EmitHost; + + /** + * Gets flags used to customize later transformations or emit. + */ + getNodeEmitFlags(node: Node): NodeEmitFlags; + + /** + * Sets flags used to customize later transformations or emit. + */ + setNodeEmitFlags(node: T, flags: NodeEmitFlags): T; + + /** + * Gets the TextRange to use for source maps for the node. + */ + getSourceMapRange(node: Node): TextRange; + + /** + * Sets the TextRange to use for source maps for the node. + */ + setSourceMapRange(node: T, range: TextRange): T; + + /** + * Gets the TextRange to use for source maps for a token of a node. + */ + getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange; + + /** + * Sets the TextRange to use for source maps for a token of a node. + */ + setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange): T; + + /** + * Gets the TextRange to use for comments for the node. + */ + getCommentRange(node: Node): TextRange; + + /** + * Sets the TextRange to use for comments for the node. + */ + setCommentRange(node: T, range: TextRange): T; + + /** + * Hoists a function declaration to the containing scope. + */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + + /** + * Hoists a variable declaration to the containing scope. + */ + hoistVariableDeclaration(node: Identifier): void; + + /** + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. + */ + enableSubstitution(kind: SyntaxKind): void; + + /** + * Determines whether expression substitutions are enabled for the provided node. + */ + isSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + */ + onSubstituteNode?: (node: Node, isExpression: boolean) => Node; + + /** + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + + /** + * Hook used to allow transformers to capture state before or after + * the printer emits a node. + */ + onEmitNode?: (node: Node, emit: (node: Node) => void) => void; + } + + /* @internal */ + export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; + export function getTransformers(compilerOptions: CompilerOptions) { const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); @@ -45,6 +139,14 @@ namespace ts { return transformers; } + /** + * Tracks a monotonically increasing transformation id used to associate a node with a specific + * transformation. This ensures transient properties related to transformations can be safely + * stored on source tree nodes that may be reused across multiple transformations (such as + * with compile-on-save). + */ + let nextTransformId = 1; + /** * Transforms an array of SourceFiles by passing them through each transformer. * @@ -54,10 +156,22 @@ namespace ts { * @param transforms An array of Transformers. */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { - const nodeEmitOptions: NodeEmitOptions[] = []; + const transformId = nextTransformId++; + const tokenSourceMapRanges: Map = { }; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + const sourceTreeNodesWithAnnotations: Node[] = []; + + let lastNodeEmitFlagsNode: Node; + let lastNodeEmitFlags: NodeEmitFlags; + let lastSourceMapRangeNode: Node; + let lastSourceMapRange: TextRange; + let lastTokenSourceMapRangeNode: Node; + let lastTokenSourceMapRangeToken: SyntaxKind; + let lastTokenSourceMapRange: TextRange; + let lastCommentMapRangeNode: Node; + let lastCommentMapRange: TextRange; let lexicalEnvironmentStackOffset = 0; let hoistedVariableDeclarations: VariableDeclaration[]; let hoistedFunctionDeclarations: FunctionDeclaration[]; @@ -107,7 +221,20 @@ namespace ts { } currentSourceFile = sourceFile; - return transformation(sourceFile); + sourceFile = transformation(sourceFile); + + // Cleanup source tree nodes with annotations + for (const node of sourceTreeNodesWithAnnotations) { + if (node.transformId === transformId) { + node.transformId = 0; + node.emitFlags = 0; + node.commentRange = undefined; + node.sourceMapRange = undefined; + } + } + + sourceTreeNodesWithAnnotations.length = 0; + return sourceFile; } /** @@ -170,143 +297,233 @@ namespace ts { emit(node); } - function getEmitOptions(node: Node, create?: boolean) { - let options = isSourceTreeNode(node) - ? nodeEmitOptions[getNodeId(node)] - : node.emitOptions; - if (!options && create) { - options = { }; - if (isSourceTreeNode(node)) { - nodeEmitOptions[getNodeId(node)] = options; - } - else { - node.emitOptions = options; + /** + * Associates a node with the current transformation, initializing + * various transient transformation properties. + * + * @param node The node. + */ + function beforeSetAnnotation(node: Node) { + if (node.transformId !== transformId) { + node.transformId = transformId; + if ((node.flags & NodeFlags.Synthesized) === 0) { + node.emitFlags = 0; + node.sourceMapRange = undefined; + node.commentRange = undefined; + + // To avoid holding onto transformation artifacts, we keep track of any + // source tree node we are annotating. This allows us to clean them up after + // all transformations have completed. + sourceTreeNodesWithAnnotations.push(node); } } - return options; } /** * Gets flags that control emit behavior of a node. + * + * If the node does not have its own NodeEmitFlags set, the node emit flags of its + * original pointer are used. + * + * @param node The node. */ function getNodeEmitFlags(node: Node) { - while (node) { - const options = getEmitOptions(node, /*create*/ false); - if (options && options.flags !== undefined) { - if (options.flags & NodeEmitFlags.Merge) { - options.flags = (options.flags | getNodeEmitFlags(node.original)) & ~NodeEmitFlags.Merge; - } - - return options.flags; - } - - node = node.original; + // As a performance optimization, use the cached value of the most recent node. + // This helps for cases where this function is called repeatedly for the same node. + if (lastNodeEmitFlagsNode === node) { + return lastNodeEmitFlags; } - return undefined; + // Get the emit flags for a node or from one of its original nodes. + let flags: NodeEmitFlags; + let current = node; + while (current) { + if (current.transformId === transformId) { + const nodeEmitFlags = current.emitFlags; + if (nodeEmitFlags) { + flags = nodeEmitFlags & ~NodeEmitFlags.HasNodeEmitFlags; + break; + } + } + + current = current.original; + } + + // Cache the most recently requested value. + lastNodeEmitFlagsNode = node; + lastNodeEmitFlags = flags; + return flags; } /** * Sets flags that control emit behavior of a node. + * + * @param node The node. + * @param emitFlags The NodeEmitFlags for the node. */ - function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { - const options = getEmitOptions(node, /*create*/ true); - if (flags & NodeEmitFlags.Merge) { - flags = options.flags | (flags & ~NodeEmitFlags.Merge); + function setNodeEmitFlags(node: T, emitFlags: NodeEmitFlags) { + // Merge existing flags. + if (emitFlags & NodeEmitFlags.Merge) { + emitFlags = getNodeEmitFlags(node) | (emitFlags & ~NodeEmitFlags.Merge); } - options.flags = flags; + beforeSetAnnotation(node); + + // Cache the most recently requested value. + lastNodeEmitFlagsNode = node; + lastNodeEmitFlags = emitFlags; + node.emitFlags = emitFlags | NodeEmitFlags.HasNodeEmitFlags; return node; } /** * Gets a custom text range to use when emitting source maps. + * + * If a node does not have its own custom source map text range, the custom source map + * text range of its original pointer is used. + * + * @param node The node. */ function getSourceMapRange(node: Node) { + // As a performance optimization, use the cached value of the most recent node. + // This helps for cases where this function is called repeatedly for the same node. + if (lastSourceMapRangeNode === node) { + return lastSourceMapRange || node; + } + + // Get the custom source map range for a node or from one of its original nodes. + let range: TextRange; let current = node; while (current) { - const options = getEmitOptions(current); - if (options && options.sourceMapRange !== undefined) { - return options.sourceMapRange; + if (current.transformId === transformId) { + range = current.sourceMapRange; + if (range !== undefined) { + break; + } } current = current.original; } - return node; + // Cache the most recently requested value. + lastSourceMapRangeNode = node; + lastSourceMapRange = range; + return range || node; } /** * Sets a custom text range to use when emitting source maps. + * + * @param node The node. + * @param range The text range. */ function setSourceMapRange(node: T, range: TextRange) { - getEmitOptions(node, /*create*/ true).sourceMapRange = range; + beforeSetAnnotation(node); + + // Cache the most recently requested value. + lastSourceMapRangeNode = node; + lastSourceMapRange = range; + node.sourceMapRange = range; return node; } - function getTokenSourceMapRanges(node: Node) { + /** + * Gets the TextRange to use for source maps for a token of a node. + * + * If a node does not have its own custom source map text range for a token, the custom + * source map text range for the token of its original pointer is used. + * + * @param node The node. + * @param token The token. + */ + function getTokenSourceMapRange(node: Node, token: SyntaxKind) { + // As a performance optimization, use the cached value of the most recent node. + // This helps for cases where this function is called repeatedly for the same node. + if (lastTokenSourceMapRangeNode === node && lastTokenSourceMapRangeToken === token) { + return lastTokenSourceMapRange; + } + + // Get the custom token source map range for a node or from one of its original nodes. + // Custom token ranges are not stored on the node to avoid the GC burden. + let range: TextRange; let current = node; while (current) { - const options = getEmitOptions(current); - if (options && options.tokenSourceMapRange) { - return options.tokenSourceMapRange; + range = current.id ? tokenSourceMapRanges[current.id + "-" + token] : undefined; + if (range !== undefined) { + break; } current = current.original; } - return undefined; - } - - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - function getTokenSourceMapRange(node: Node, token: SyntaxKind) { - const ranges = getTokenSourceMapRanges(node); - if (ranges) { - return ranges[token]; - } - - return undefined; + // Cache the most recently requested value. + lastTokenSourceMapRangeNode = node; + lastTokenSourceMapRangeToken = token; + lastTokenSourceMapRange = range; + return range; } /** * Sets the TextRange to use for source maps for a token of a node. + * + * @param node The node. + * @param token The token. + * @param range The text range. */ function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange) { - const options = getEmitOptions(node, /*create*/ true); - if (!options.tokenSourceMapRange) { - const existingRanges = getTokenSourceMapRanges(node); - const ranges = existingRanges ? clone(existingRanges) : { }; - options.tokenSourceMapRange = ranges; - } - - options.tokenSourceMapRange[token] = range; + // Cache the most recently requested value. + lastTokenSourceMapRangeNode = node; + lastTokenSourceMapRangeToken = token; + lastTokenSourceMapRange = range; + tokenSourceMapRanges[getNodeId(node) + "-" + token] = range; return node; } /** * Gets a custom text range to use when emitting comments. + * + * If a node does not have its own custom source map text range, the custom source map + * text range of its original pointer is used. + * + * @param node The node. */ function getCommentRange(node: Node) { + // As a performance optimization, use the cached value of the most recent node. + // This helps for cases where this function is called repeatedly for the same node. + if (lastCommentMapRangeNode === node) { + return lastCommentMapRange || node; + } + + // Get the custom comment range for a node or from one of its original nodes. + let range: TextRange; let current = node; while (current) { - const options = getEmitOptions(current, /*create*/ false); - if (options && options.commentRange !== undefined) { - return options.commentRange; + if (current.transformId === transformId) { + range = current.commentRange; + if (range !== undefined) { + break; + } } current = current.original; } - return node; + // Cache the most recently requested value. + lastCommentMapRangeNode = node; + lastCommentMapRange = range; + return range || node; } /** * Sets a custom text range to use when emitting comments. */ function setCommentRange(node: T, range: TextRange) { - getEmitOptions(node, /*create*/ true).commentRange = range; + beforeSetAnnotation(node); + + // Cache the most recently requested value. + lastCommentMapRangeNode = node; + lastCommentMapRange = range; + node.commentRange = range; return node; } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 9060b460f9c..53f314c7504 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -251,7 +251,9 @@ namespace ts { emitArrayLiteralAssignment(target, value, location); } else { - const name = getSynthesizedClone(target, { sourceMapRange: target, commentRange: target }); + const name = getMutableClone(target); + context.setSourceMapRange(name, target); + context.setCommentRange(name, target); emitAssignment(name, value, location, /*original*/ undefined); } } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index ef9f4f29d1d..1c412f98af7 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -164,7 +164,7 @@ namespace ts { let currentNode: Node; let enclosingBlockScopeContainer: Node; let enclosingBlockScopeContainerParent: Node; - let containingNonArrowFunction: FunctionLikeDeclaration; + let containingNonArrowFunction: FunctionLikeDeclaration | ClassElement; /** * Used to track if we are emitting body of the converted loop @@ -1003,7 +1003,7 @@ namespace ts { } // `declarationName` is the name of the local declaration for the parameter. - const declarationName = getUniqueClone(parameter.name); + const declarationName = getMutableClone(parameter.name); setNodeEmitFlags(declarationName, NodeEmitFlags.NoSourceMap); // `expressionName` is the name of the parameter used in expressions. @@ -1150,6 +1150,7 @@ namespace ts { createMemberAccessForPropertyName( receiver, visitNode(member.name, visitor, isPropertyName), + setNodeEmitFlags, /*location*/ member.name ), func @@ -2311,7 +2312,8 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(property.name, visitor, isPropertyName) + visitNode(property.name, visitor, isPropertyName), + setNodeEmitFlags ), visitNode(property.initializer, visitor, isExpression), /*location*/ property @@ -2329,7 +2331,8 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(property.name, visitor, isPropertyName) + visitNode(property.name, visitor, isPropertyName), + setNodeEmitFlags ), getSynthesizedClone(property.name), /*location*/ property @@ -2347,7 +2350,8 @@ namespace ts { return createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(method.name, visitor, isPropertyName) + visitNode(method.name, visitor, isPropertyName), + setNodeEmitFlags ), transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined), /*location*/ method @@ -2912,7 +2916,7 @@ namespace ts { */ function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: NodeEmitFlags) { if (node.name && !isGeneratedIdentifier(node.name)) { - const name = getUniqueClone(node.name); + const name = getMutableClone(node.name); emitFlags |= getNodeEmitFlags(node.name); if (!allowSourceMaps) { emitFlags |= NodeEmitFlags.NoSourceMap; diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 4da4c7d0ed7..6c1169713c7 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -100,7 +100,7 @@ namespace ts { exportFunctionForFile = createUniqueName("exports"); contextObjectForFile = createUniqueName("context"); - exportFunctionForFileMap[getNodeId(node)] = exportFunctionForFile; + exportFunctionForFileMap[getOriginalNodeId(node)] = exportFunctionForFile; const dependencyGroups = collectDependencyGroups(externalImports); @@ -968,7 +968,7 @@ namespace ts { function onEmitNode(node: Node, emit: (node: Node) => void): void { if (node.kind === SyntaxKind.SourceFile) { - exportFunctionForFile = exportFunctionForFileMap[getNodeId(node)]; + exportFunctionForFile = exportFunctionForFileMap[getOriginalNodeId(node)]; previousOnEmitNode(node, emit); exportFunctionForFile = undefined; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index afc9e5b7570..d4f7ea020cb 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1034,7 +1034,7 @@ namespace ts { function transformInitializedProperty(node: ClassExpression | ClassDeclaration, property: PropertyDeclaration, receiver: LeftHandSideExpression) { const propertyName = visitPropertyNameOfClassElement(property); const initializer = visitNode(property.initializer, visitor, isExpression); - const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); + const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, setNodeEmitFlags, /*location*/ propertyName); if (!isComputedPropertyName(propertyName)) { setNodeEmitFlags(memberAccess, NodeEmitFlags.NoNestedSourceMaps); } @@ -1753,7 +1753,10 @@ namespace ts { function serializeEntityNameAsExpression(node: EntityName, useFallback: boolean): Expression { switch (node.kind) { case SyntaxKind.Identifier: + // Create a clone of the name with a new parent, and treat it as if it were + // a source tree node for the purposes of the checker. const name = getMutableClone(node); + name.flags &= ~NodeFlags.Synthesized; name.original = undefined; name.parent = currentScope; if (useFallback) { @@ -2714,6 +2717,11 @@ namespace ts { && resolver.isTopLevelValueImportEqualsWithEntityName(node)); } + function disableCommentsRecursive(node: Node) { + setNodeEmitFlags(node, NodeEmitFlags.NoComments | NodeEmitFlags.Merge); + forEachChild(node, disableCommentsRecursive); + } + /** * Visits an import equals declaration. * @@ -2728,7 +2736,8 @@ namespace ts { return undefined; } - const moduleReference = createExpressionFromEntityName(node.moduleReference, { flags: NodeEmitFlags.NoComments }); + const moduleReference = createExpressionFromEntityName(node.moduleReference); + disableCommentsRecursive(moduleReference); if (isNamedExternalModuleExport(node) || !isNamespaceExport(node)) { // export var ${name} = ${moduleReference}; // var ${name} = ${moduleReference}; @@ -2913,7 +2922,7 @@ namespace ts { */ function getDeclarationName(node: DeclarationStatement | ClassExpression, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags?: NodeEmitFlags) { if (node.name) { - const name = getUniqueClone(node.name); + const name = getMutableClone(node.name); emitFlags |= getNodeEmitFlags(node.name); if (!allowSourceMaps) { emitFlags |= NodeEmitFlags.NoSourceMap; @@ -3150,9 +3159,12 @@ namespace ts { // behavior of class names in ES6. const declaration = resolver.getReferencedValueDeclaration(node); if (declaration) { - const classAlias = currentDecoratedClassAliases[getNodeId(declaration)]; + const classAlias = currentDecoratedClassAliases[getOriginalNodeId(declaration)]; if (classAlias) { - return getSynthesizedClone(classAlias, { sourceMapRange: node, commentRange: node }); + const clone = getSynthesizedClone(classAlias); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; } } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 7c9fae6b632..2c15a972bf8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -238,7 +238,7 @@ namespace ts { } function reportStatisticalValue(name: string, value: string) { - sys.write(padRight(name + ":", 12) + padLeft(value.toString(), 10) + sys.newLine); + sys.write(padRight(name + ":", 20) + padLeft(value.toString(), 10) + sys.newLine); } function reportCountStatistic(name: string, count: number) { @@ -544,12 +544,9 @@ namespace ts { } function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { - ioReadTime = 0; - ioWriteTime = 0; - programTime = 0; - bindTime = 0; - checkTime = 0; - emitTime = 0; + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { + performance.enable(); + } const program = createProgram(fileNames, compilerOptions, compilerHost); const exitStatus = compileProgram(); @@ -560,7 +557,7 @@ namespace ts { }); } - if (compilerOptions.diagnostics) { + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); @@ -573,17 +570,29 @@ namespace ts { reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); } + if (compilerOptions.extendedDiagnostics) { + reportTimeStatistic("Print time", performance.getDuration("printTime")); + reportTimeStatistic("Comment time", performance.getDuration("commentTime")); + reportTimeStatistic("SourceMap time", performance.getDuration("sourceMapTime")); + } + // Individual component times. // Note: To match the behavior of previous versions of the compiler, the reported parse time includes // I/O read time and processing time for triple-slash references and module imports, and the reported // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. - reportTimeStatistic("I/O read", ioReadTime); - reportTimeStatistic("I/O write", ioWriteTime); + reportTimeStatistic("I/O read", performance.getDuration("ioReadTime")); + reportTimeStatistic("I/O write", performance.getDuration("ioWriteTime")); + const programTime = performance.getDuration("programTime"); + const bindTime = performance.getDuration("bindTime"); + const checkTime = performance.getDuration("checkTime"); + const emitTime = performance.getDuration("emitTime"); reportTimeStatistic("Parse time", programTime); reportTimeStatistic("Bind time", bindTime); reportTimeStatistic("Check time", checkTime); reportTimeStatistic("Emit time", emitTime); reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + + performance.disable(); } return { program, exitStatus }; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 380dd259915..d868e024e0f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -383,6 +383,7 @@ namespace ts { Let = 1 << 0, // Variable declaration Const = 1 << 1, // Variable declaration NestedNamespace = 1 << 2, // Namespace declaration + Synthesized = 1 << 3, // Node was synthesized during transformation Namespace = 1 << 12, // Namespace declaration ExportContext = 1 << 13, // Export context (initialized by binding) ContainsThis = 1 << 14, // Interface contains references to "this" @@ -469,7 +470,10 @@ namespace ts { /* @internal */ locals?: SymbolTable; // Locals associated with node (initialized by binding) /* @internal */ nextContainer?: Node; // Next container in declaration order (initialized by binding) /* @internal */ localSymbol?: Symbol; // Local symbol declared by node (initialized by binding only for exported nodes) - /* @internal */ emitOptions?: NodeEmitOptions; // Options used to control node emit (used by transforms, should never be set directly on a source tree node) + /* @internal */ transformId?: number; // Associates transient transformation properties with a specific transformation (initialized by transformation). + /* @internal */ emitFlags?: NodeEmitFlags; // Transient emit flags for a synthesized node (initialized by transformation). + /* @internal */ sourceMapRange?: TextRange; // Transient custom sourcemap range for a synthesized node (initialized by transformation). + /* @internal */ commentRange?: TextRange; // Transient custom comment range for a synthesized node (initialized by transformation). } export interface NodeArray extends Array, TextRange { @@ -502,6 +506,7 @@ namespace ts { text: string; // Text of identifier (with escapes converted to characters) originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. + /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. } // Transient identifier node (marked by id === -1) @@ -2475,6 +2480,7 @@ namespace ts { declaration?: boolean; declarationDir?: string; diagnostics?: boolean; + /*@internal*/ extendedDiagnostics?: boolean; emitBOM?: boolean; help?: boolean; init?: boolean; @@ -2955,26 +2961,8 @@ namespace ts { // align with the old emitter. SourceMapEmitOpenBraceAsToken = 1 << 21, // Emits the open brace of a block function body as a source mapped token. SourceMapAdjustRestParameterLoop = 1 << 22, // Emits adjusted source map positions for a ForStatement generated when transforming a rest parameter for ES5/3. - } - /* @internal */ - export interface NodeEmitOptions { - /** - * Specifies a custom range to use when emitting source maps. - */ - sourceMapRange?: TextRange; - /** - * Specifies a custom range to use when emitting tokens of a node. - */ - tokenSourceMapRange?: Map; - /** - * Specifies a custom range to use when emitting comments. - */ - commentRange?: TextRange; - /** - * Specifies flags to use to customize emit. - */ - flags?: NodeEmitFlags; + HasNodeEmitFlags = 1 << 31, // Indicates the node has emit flags set. } /** Additional context provided to `visitEachChild` */ @@ -2987,99 +2975,6 @@ namespace ts { endLexicalEnvironment(): Statement[]; } - /* @internal */ - export interface TransformationContext extends LexicalEnvironment { - getCompilerOptions(): CompilerOptions; - getEmitResolver(): EmitResolver; - getEmitHost(): EmitHost; - - /** - * Gets flags used to customize later transformations or emit. - */ - getNodeEmitFlags(node: Node): NodeEmitFlags; - - /** - * Sets flags used to customize later transformations or emit. - */ - setNodeEmitFlags(node: T, flags: NodeEmitFlags): T; - - /** - * Gets the TextRange to use for source maps for the node. - */ - getSourceMapRange(node: Node): TextRange; - - /** - * Sets the TextRange to use for source maps for the node. - */ - setSourceMapRange(node: T, range: TextRange): T; - - /** - * Gets the TextRange to use for source maps for a token of a node. - */ - getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange; - - /** - * Sets the TextRange to use for source maps for a token of a node. - */ - setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange): T; - - /** - * Gets the TextRange to use for comments for the node. - */ - getCommentRange(node: Node): TextRange; - - /** - * Sets the TextRange to use for comments for the node. - */ - setCommentRange(node: T, range: TextRange): T; - - /** - * Hoists a function declaration to the containing scope. - */ - hoistFunctionDeclaration(node: FunctionDeclaration): void; - - /** - * Hoists a variable declaration to the containing scope. - */ - hoistVariableDeclaration(node: Identifier): void; - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - enableSubstitution(kind: SyntaxKind): void; - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - isSubstitutionEnabled(node: Node): boolean; - - /** - * Hook used by transformers to substitute expressions just before they - * are emitted by the pretty printer. - */ - onSubstituteNode?: (node: Node, isExpression: boolean) => Node; - - /** - * Enables before/after emit notifications in the pretty printer for the provided - * SyntaxKind. - */ - enableEmitNotification(kind: SyntaxKind): void; - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - isEmitNotificationEnabled(node: Node): boolean; - - /** - * Hook used to allow transformers to capture state before or after - * the printer emits a node. - */ - onEmitNode?: (node: Node, emit: (node: Node) => void) => void; - } - - /* @internal */ - export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; export interface TextSpan { start: number; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5f023b3909f..76c70195017 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -207,18 +207,6 @@ namespace ts { return `${ file.fileName }(${ loc.line + 1 },${ loc.character + 1 })`; } - export function getEnvironmentVariable(name: string, host?: CompilerHost) { - if (host && host.getEnvironmentVariable) { - return host.getEnvironmentVariable(name); - } - - if (sys && sys.getEnvironmentVariable) { - return sys.getEnvironmentVariable(name); - } - - return ""; - } - export function getStartPosOfNode(node: Node): number { return node.pos; } @@ -1831,14 +1819,17 @@ namespace ts { } export function isSourceTreeNode(node: Node): boolean { - return node.original === undefined - && (node.parent !== undefined || node.kind === SyntaxKind.SourceFile); + return (node.flags & NodeFlags.Synthesized) === 0; } export function getSourceTreeNode(node: Node): Node { + if (isSourceTreeNode(node)) { + return node; + } + node = getOriginalNode(node); - if (node && (node.parent !== undefined || node.kind === SyntaxKind.SourceFile)) { + if (isSourceTreeNode(node)) { return node; } diff --git a/tests/baselines/reference/assignmentLHSIsValue.js b/tests/baselines/reference/assignmentLHSIsValue.js index ecc1cded2cc..8a3b4b35d0b 100644 --- a/tests/baselines/reference/assignmentLHSIsValue.js +++ b/tests/baselines/reference/assignmentLHSIsValue.js @@ -113,7 +113,7 @@ false = value; } value; // array literals -"" = value[0], "" = value[1]; +'' = value[0], '' = value[1]; // super var Derived = (function (_super) { __extends(Derived, _super); diff --git a/tests/baselines/reference/compoundExponentiationAssignmentLHSIsValue.js b/tests/baselines/reference/compoundExponentiationAssignmentLHSIsValue.js index cf4ad26ffe6..16500b823b8 100644 --- a/tests/baselines/reference/compoundExponentiationAssignmentLHSIsValue.js +++ b/tests/baselines/reference/compoundExponentiationAssignmentLHSIsValue.js @@ -134,7 +134,7 @@ false = Math.pow(false, value); } value; // array literals -_a = Math.pow(['', ''], value), "" = _a[0], "" = _a[1]; +_a = Math.pow(['', ''], value), '' = _a[0], '' = _a[1]; // super var Derived = (function (_super) { __extends(Derived, _super);