From b8a9efb66cc814855e11bb0a4a91e827d3d5de10 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 17 May 2016 18:37:08 -0700 Subject: [PATCH 01/12] Wrap performance metrics in object to reduce deoptimizations on ts namespace. --- src/compiler/binder.ts | 7 +-- src/compiler/checker.ts | 7 +-- src/compiler/core.ts | 135 ++++++++++++++++++++++++++++++++++++++++ src/compiler/program.ts | 28 ++++----- src/compiler/tsc.ts | 14 +++-- 5 files changed, 163 insertions(+), 28 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index f0c58c66bef..1619fc0fc8c 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,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - const start = new Date().getTime(); + Performance.mark("bindStart"); binder(file, options); - bindTime += new Date().getTime() - start; + Performance.mark("bindEnd"); + Performance.measure("bindTime", "bindStart", "bindEnd"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 344dd35a5c3..725ed2c1bad 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,12 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - const start = new Date().getTime(); + Performance.mark("checkStart"); checkSourceFileWorker(node); - checkTime += new Date().getTime() - start; + Performance.mark("checkEnd"); + Performance.measure("checkTime", "checkStart", "checkEnd"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bae17c642d2..0a070844903 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1134,4 +1134,139 @@ namespace ts { : ((fileName) => fileName.toLowerCase()); } + /*@internal*/ + export namespace Performance { + interface MarkData { + markName: string; + timestamp: number; + } + + interface MeasureData { + measureName: string; + startMarkName: string; + endMarkName: string; + timestamp: number; + marksOffset: number; + } + + export interface Measure { + name: string; + startTime: number; + duration: number; + } + + const marks: MarkData[] = []; + const measures: MeasureData[] = []; + + let start = now(); + + /** Gets the current timer for performance measurements. */ + export function now() { + // TODO(rbuckton): Determine if there is a higher-resolution timer we can use. + return Date.now(); + } + + /** + * Adds a performance mark with the specified name. + * + * @param markName The name of the performance mark. + */ + export function mark(markName: string) { + marks.push({ markName, timestamp: now() }); + } + + /** + * Adds a performance measurement with the specified name. + * + * @param measureName The name of the performance measurement. + * @param startMarkName The name of the starting mark. + * If provided, the most recent time value of the start mark is used. + * If not specified, the value is the time that the performance service was + * initialized or the last time it was reset. + * @param endMarkName The name of the ending mark. + * If provided, the most recent time value of the end mark is used. + * If not specified, the current time is used. + */ + export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + measures.push({ + measureName, + startMarkName, + endMarkName, + timestamp: now(), + marksOffset: marks.length + }); + } + + /** + * Gets an array of performance measures. + * + * @param measureName The name of the measure. + * If provided, only measures with the provided name are returned. + * If not specified, all measures are returned since the last time the + * performance service was reset. + */ + export function getMeasures(measureName?: string) { + const result: Measure[] = []; + for (const measure of measures) { + if (measureName !== undefined && measureName !== measure.measureName) { + continue; + } + + let startOffset = 0; + let startTime = start; + if (measure.startMarkName) { + const startMarkIndex = getMarkOffset(measure.startMarkName, 0, measure.marksOffset); + if (startMarkIndex >= 0) { + startOffset = startMarkIndex; + startTime = marks[startMarkIndex].timestamp; + } + } + + let endTime = measure.timestamp; + if (measure.endMarkName) { + const endMarkIndex = getMarkOffset(measure.endMarkName, startOffset, measure.marksOffset); + if (endMarkIndex >= 0) { + endTime = marks[endMarkIndex].timestamp; + } + } + + const duration = endTime - startTime; + result.push({ + name: measure.measureName, + startTime, + duration + }); + } + + return result; + } + + function getMarkOffset(markName: string, markStart: number, markEnd: number) { + if (markName === undefined) { + return -1; + } + + if (markStart < 0) { + markStart = 0; + } + + for (let i = markEnd - 1; i >= markStart; i--) { + const mark = marks[i]; + if (mark.markName === markName) { + return i; + } + } + + return -1; + } + + /** + * Resets all marks and measurements in the performance service. + */ + export function reset() { + marks.length = 0; + measures.length = 0; + start = now(); + } + } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bf5b7cb5727..1e7d4118e6c 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,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - const start = new Date().getTime(); + Performance.mark("ioReadStart"); text = sys.readFile(fileName, options.charset); - ioReadTime += new Date().getTime() - start; + Performance.mark("ioReadEnd"); + Performance.measure("ioReadTime", "ioReadStart", "ioReadEnd"); } catch (e) { if (onError) { @@ -850,7 +846,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - const start = new Date().getTime(); + Performance.mark("ioWriteStart"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -860,7 +856,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - ioWriteTime += new Date().getTime() - start; + Performance.mark("ioWriteEnd"); + Performance.measure("ioWriteTime", "ioWriteStart", "ioWriteEnd"); } catch (e) { if (onError) { @@ -962,7 +959,7 @@ namespace ts { let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); - const start = new Date().getTime(); + Performance.mark("programStart"); host = host || createCompilerHost(options); @@ -1055,7 +1052,8 @@ namespace ts { verifyCompilerOptions(); - programTime += new Date().getTime() - start; + Performance.mark("programEnd"); + Performance.measure("programTime", "programStart", "programEnd"); return program; @@ -1288,7 +1286,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(); + Performance.mark("emitStart"); // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. let useLegacyEmitter = options.useLegacyEmitter; @@ -1302,7 +1300,9 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - emitTime += new Date().getTime() - start; + Performance.mark("emitEnd"); + Performance.measure("emitTime", "emitStart", "emitEnd"); + return emitResult; } @@ -2076,7 +2076,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/tsc.ts b/src/compiler/tsc.ts index 7c9fae6b632..2325f75a665 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -544,12 +544,7 @@ namespace ts { } function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { - ioReadTime = 0; - ioWriteTime = 0; - programTime = 0; - bindTime = 0; - checkTime = 0; - emitTime = 0; + Performance.reset(); const program = createProgram(fileNames, compilerOptions, compilerHost); const exitStatus = compileProgram(); @@ -561,6 +556,13 @@ namespace ts { } if (compilerOptions.diagnostics) { + const ioReadTime = reduceLeft(Performance.getMeasures("ioReadTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const ioWriteTime = reduceLeft(Performance.getMeasures("ioWriteTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const programTime = reduceLeft(Performance.getMeasures("programTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const bindTime = reduceLeft(Performance.getMeasures("bindTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const checkTime = reduceLeft(Performance.getMeasures("checkTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const emitTime = reduceLeft(Performance.getMeasures("emitTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); From abc9fda0e81397c754fabc0fc904ebfe224d821c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 17 May 2016 18:43:13 -0700 Subject: [PATCH 02/12] Conditionally enable performance metrics --- src/compiler/binder.ts | 6 +++--- src/compiler/checker.ts | 6 +++--- src/compiler/core.ts | 34 +++++++++++++++++++++++++--------- src/compiler/program.ts | 24 ++++++++++++------------ src/compiler/tsc.ts | 19 ++++++++++++------- 5 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1619fc0fc8c..0424d3b7f43 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -94,10 +94,10 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - Performance.mark("bindStart"); + performance.mark("bindStart"); binder(file, options); - Performance.mark("bindEnd"); - Performance.measure("bindTime", "bindStart", "bindEnd"); + performance.mark("bindEnd"); + performance.measure("bindTime", "bindStart", "bindEnd"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 725ed2c1bad..755b285266c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16064,12 +16064,12 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - Performance.mark("checkStart"); + performance.mark("checkStart"); checkSourceFileWorker(node); - Performance.mark("checkEnd"); - Performance.measure("checkTime", "checkStart", "checkEnd"); + performance.mark("checkEnd"); + performance.measure("checkTime", "checkStart", "checkEnd"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0a070844903..1e8e66739cc 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1134,8 +1134,9 @@ namespace ts { : ((fileName) => fileName.toLowerCase()); } + /** Performance measurements for the compiler. */ /*@internal*/ - export namespace Performance { + export namespace performance { interface MarkData { markName: string; timestamp: number; @@ -1159,6 +1160,7 @@ namespace ts { const measures: MeasureData[] = []; let start = now(); + let enabled = false; /** Gets the current timer for performance measurements. */ export function now() { @@ -1172,7 +1174,9 @@ namespace ts { * @param markName The name of the performance mark. */ export function mark(markName: string) { - marks.push({ markName, timestamp: now() }); + if (enabled) { + marks.push({ markName, timestamp: now() }); + } } /** @@ -1188,13 +1192,15 @@ namespace ts { * If not specified, the current time is used. */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - measures.push({ - measureName, - startMarkName, - endMarkName, - timestamp: now(), - marksOffset: marks.length - }); + if (enabled) { + measures.push({ + measureName, + startMarkName, + endMarkName, + timestamp: now(), + marksOffset: marks.length + }); + } } /** @@ -1268,5 +1274,15 @@ namespace ts { measures.length = 0; start = now(); } + + /** Enables performance measurements for the compiler. */ + export function enable() { + enabled = true; + } + + /** Disables performance measurements for the compiler. */ + export function disable() { + enabled = false; + } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 1e7d4118e6c..1346a8c0268 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -776,10 +776,10 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - Performance.mark("ioReadStart"); + performance.mark("ioReadStart"); text = sys.readFile(fileName, options.charset); - Performance.mark("ioReadEnd"); - Performance.measure("ioReadTime", "ioReadStart", "ioReadEnd"); + performance.mark("ioReadEnd"); + performance.measure("ioReadTime", "ioReadStart", "ioReadEnd"); } catch (e) { if (onError) { @@ -846,7 +846,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - Performance.mark("ioWriteStart"); + performance.mark("ioWriteStart"); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -856,8 +856,8 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - Performance.mark("ioWriteEnd"); - Performance.measure("ioWriteTime", "ioWriteStart", "ioWriteEnd"); + performance.mark("ioWriteEnd"); + performance.measure("ioWriteTime", "ioWriteStart", "ioWriteEnd"); } catch (e) { if (onError) { @@ -959,7 +959,7 @@ namespace ts { let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); - Performance.mark("programStart"); + performance.mark("programStart"); host = host || createCompilerHost(options); @@ -1052,8 +1052,8 @@ namespace ts { verifyCompilerOptions(); - Performance.mark("programEnd"); - Performance.measure("programTime", "programStart", "programEnd"); + performance.mark("programEnd"); + performance.measure("programTime", "programStart", "programEnd"); return program; @@ -1286,7 +1286,7 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - Performance.mark("emitStart"); + performance.mark("emitStart"); // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. let useLegacyEmitter = options.useLegacyEmitter; @@ -1300,8 +1300,8 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - Performance.mark("emitEnd"); - Performance.measure("emitTime", "emitStart", "emitEnd"); + performance.mark("emitEnd"); + performance.measure("emitTime", "emitStart", "emitEnd"); return emitResult; } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 2325f75a665..27b098d011a 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -544,7 +544,10 @@ namespace ts { } function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { - Performance.reset(); + if (compilerOptions.diagnostics) { + performance.enable(); + performance.reset(); + } const program = createProgram(fileNames, compilerOptions, compilerHost); const exitStatus = compileProgram(); @@ -556,12 +559,14 @@ namespace ts { } if (compilerOptions.diagnostics) { - const ioReadTime = reduceLeft(Performance.getMeasures("ioReadTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const ioWriteTime = reduceLeft(Performance.getMeasures("ioWriteTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const programTime = reduceLeft(Performance.getMeasures("programTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const bindTime = reduceLeft(Performance.getMeasures("bindTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const checkTime = reduceLeft(Performance.getMeasures("checkTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const emitTime = reduceLeft(Performance.getMeasures("emitTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const ioReadTime = reduceLeft(performance.getMeasures("ioReadTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const ioWriteTime = reduceLeft(performance.getMeasures("ioWriteTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const programTime = reduceLeft(performance.getMeasures("programTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const bindTime = reduceLeft(performance.getMeasures("bindTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const checkTime = reduceLeft(performance.getMeasures("checkTime"), (aggregate, measure) => aggregate + measure.duration, 0); + const emitTime = reduceLeft(performance.getMeasures("emitTime"), (aggregate, measure) => aggregate + measure.duration, 0); + performance.disable(); + performance.reset(); const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); From b5dec0b8ab9d321267f917f5aa5726f67587444c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 18 May 2016 16:42:37 -0700 Subject: [PATCH 03/12] Removed nodeEmitOptions to reduce GC overhead --- src/compiler/core.ts | 1 + src/compiler/factory.ts | 93 ++++++++++--------- src/compiler/printer.ts | 82 ++++++++++------ src/compiler/scanner.ts | 2 +- src/compiler/transformer.ts | 83 +++++------------ src/compiler/transformers/destructuring.ts | 4 +- src/compiler/transformers/es6.ts | 12 ++- src/compiler/transformers/module/system.ts | 4 +- src/compiler/transformers/ts.ts | 21 +++-- src/compiler/types.ts | 22 +---- .../reference/assignmentLHSIsValue.js | 2 +- ...poundExponentiationAssignmentLHSIsValue.js | 2 +- 12 files changed, 164 insertions(+), 164 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1e8e66739cc..287cac2b372 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; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index b21888514f1..3523cdb05ee 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())); @@ -21,10 +21,6 @@ namespace ts { node.flags = flags; } - if (emitOptions) { - node.emitOptions = emitOptions; - } - return node; } @@ -68,11 +64,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); + const clone = createNode(node.kind, /*location*/ undefined, /*flags*/ undefined); clone.flags = node.flags; clone.original = node; @@ -90,8 +86,8 @@ 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; @@ -101,35 +97,35 @@ namespace ts { /** * 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; + export function getUniqueClone(node: T): T { + const clone = getMutableClone(node); + clone.id = 0; 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 +134,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 +159,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 +293,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 +511,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 +742,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); @@ -1170,29 +1179,29 @@ 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 if (isIdentifier(node)) { - return getMutableClone(node, emitOptions && clone(emitOptions)); + return getMutableClone(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/printer.ts b/src/compiler/printer.ts index 3ec8ce0b394..6b284545f0d 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -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; @@ -2706,6 +2708,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 +2734,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/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/transformer.ts b/src/compiler/transformer.ts index a18b8546db8..b9f0402b6ca 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -54,7 +54,9 @@ namespace ts { * @param transforms An array of Transformers. */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { - const nodeEmitOptions: NodeEmitOptions[] = []; + const nodeEmitFlags: Map = {}; + const sourceMapRanges: Map = {}; + const commentRanges: Map = {}; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); @@ -170,34 +172,14 @@ 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; - } - } - return options; - } - /** * Gets flags that control emit behavior of a 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; + let flags = nodeEmitFlags[getNodeId(node)]; + if (flags !== undefined) { + return flags; } node = node.original; @@ -210,12 +192,11 @@ namespace ts { * Sets flags that control emit behavior of a node. */ function setNodeEmitFlags(node: T, flags: NodeEmitFlags) { - const options = getEmitOptions(node, /*create*/ true); if (flags & NodeEmitFlags.Merge) { - flags = options.flags | (flags & ~NodeEmitFlags.Merge); + flags = getNodeEmitFlags(node) | (flags & ~NodeEmitFlags.Merge); } - options.flags = flags; + nodeEmitFlags[getNodeId(node)] = flags; return node; } @@ -225,9 +206,9 @@ namespace ts { function getSourceMapRange(node: Node) { let current = node; while (current) { - const options = getEmitOptions(current); - if (options && options.sourceMapRange !== undefined) { - return options.sourceMapRange; + const sourceMapRange = sourceMapRanges[getNodeId(current)]; + if (sourceMapRange !== undefined) { + return sourceMapRange; } current = current.original; @@ -240,16 +221,19 @@ namespace ts { * Sets a custom text range to use when emitting source maps. */ function setSourceMapRange(node: T, range: TextRange) { - getEmitOptions(node, /*create*/ true).sourceMapRange = range; + sourceMapRanges[getNodeId(node)] = range; return node; } - function getTokenSourceMapRanges(node: Node) { + /** + * Gets the TextRange to use for source maps for a token of a node. + */ + function getTokenSourceMapRange(node: Node, token: SyntaxKind) { let current = node; while (current) { - const options = getEmitOptions(current); - if (options && options.tokenSourceMapRange) { - return options.tokenSourceMapRange; + const tokenSourceMapRange = sourceMapRanges[getNodeId(node) + "-" + token]; + if (tokenSourceMapRange !== undefined) { + return tokenSourceMapRange; } current = current.original; @@ -258,30 +242,11 @@ namespace ts { 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; - } - /** * Sets the TextRange to use for source maps for a token of a node. */ 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; + sourceMapRanges[getNodeId(node) + "-" + token] = range; return node; } @@ -291,9 +256,9 @@ namespace ts { function getCommentRange(node: Node) { let current = node; while (current) { - const options = getEmitOptions(current, /*create*/ false); - if (options && options.commentRange !== undefined) { - return options.commentRange; + const commentRange = commentRanges[getNodeId(current)]; + if (commentRange !== undefined) { + return commentRange; } current = current.original; @@ -306,7 +271,7 @@ namespace ts { * Sets a custom text range to use when emitting comments. */ function setCommentRange(node: T, range: TextRange) { - getEmitOptions(node, /*create*/ true).commentRange = range; + commentRanges[getNodeId(node)] = 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..e2d41783982 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 @@ -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 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 abc50d280a6..9d3ed9f051c 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -927,10 +927,10 @@ namespace ts { function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { Debug.assert(isIdentifier(node.name)); const name = node.name as Identifier; - const propertyName = getMutableClone(name); + const propertyName = getUniqueClone(name); setNodeEmitFlags(propertyName, NodeEmitFlags.NoComments | NodeEmitFlags.NoSourceMap); - const localName = getMutableClone(name); + const localName = getUniqueClone(name); setNodeEmitFlags(localName, NodeEmitFlags.NoComments); return startOnNewLine( @@ -1033,7 +1033,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); } @@ -2713,6 +2713,11 @@ namespace ts { && resolver.isTopLevelValueImportEqualsWithEntityName(node)); } + function disableCommentsRecursive(node: Node) { + setNodeEmitFlags(node, NodeEmitFlags.NoComments | NodeEmitFlags.Merge); + forEachChild(node, disableCommentsRecursive); + } + /** * Visits an import equals declaration. * @@ -2727,7 +2732,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}; @@ -3149,9 +3155,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/types.ts b/src/compiler/types.ts index 380dd259915..059434e5251 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -469,7 +469,6 @@ 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) } export interface NodeArray extends Array, TextRange { @@ -502,6 +501,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) @@ -2957,26 +2957,6 @@ namespace ts { 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; - } - /** Additional context provided to `visitEachChild` */ /* @internal */ export interface LexicalEnvironment { 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); From 96ba0f26967c8327640f4a4f62ae796de97dc240 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 May 2016 10:36:23 -0700 Subject: [PATCH 04/12] Added metrics for printing --- src/compiler/binder.ts | 3 +- src/compiler/checker.ts | 3 +- src/compiler/comments.ts | 61 +++++++++++----- src/compiler/core.ts | 149 ++++++++++++++++++++------------------- src/compiler/printer.ts | 2 + src/compiler/program.ts | 12 ++-- src/compiler/tsc.ts | 25 +++---- 7 files changed, 143 insertions(+), 112 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 0424d3b7f43..3c323d564c6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -96,8 +96,7 @@ namespace ts { export function bindSourceFile(file: SourceFile, options: CompilerOptions) { performance.mark("bindStart"); binder(file, options); - performance.mark("bindEnd"); - performance.measure("bindTime", "bindStart", "bindEnd"); + performance.measure("bindTime", "bindStart"); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 755b285266c..71e099bbf83 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16068,8 +16068,7 @@ namespace ts { checkSourceFileWorker(node); - performance.mark("checkEnd"); - performance.measure("checkTime", "checkStart", "checkEnd"); + performance.measure("checkTime", "checkStart"); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 343b388ffe2..2b7ab987e4e 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,9 +31,9 @@ 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 ? createCommentRemovingWriter() @@ -45,7 +44,6 @@ namespace ts { 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 +67,6 @@ namespace ts { reset, setSourceFile, getLeadingComments, - getLeadingCommentsOfPosition, getTrailingComments, getTrailingCommentsOfPosition, emitLeadingComments, @@ -81,9 +78,14 @@ namespace ts { function getLeadingComments(range: TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { + performance.mark("commentStart"); + + let comments: CommentRange[] = []; + let ignored = false; if (contextNode) { range = getTextRangeCallback(contextNode) || range; if (ignoreNodeCallback(contextNode)) { + ignored = true; // If the node will not be emitted in JS, remove all the comments (normal, // pinned and `///`) associated with the node, unless it is a triple slash // comment at the top of the file. @@ -97,15 +99,17 @@ namespace ts { // The first `///` will NOT be removed while the second one will be removed // even though both nodes will not be emitted. if (range.pos === 0) { - return filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); + comments = filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); } - - return undefined; } } + if (!ignored) { + comments = getLeadingCommentsOfPosition(range.pos); + } - return getLeadingCommentsOfPosition(range.pos); + performance.measure("commentTime", "commentStart"); + return comments; } /** @@ -127,15 +131,25 @@ 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) { + performance.mark("commentStart"); + + 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 = getTrailingCommentsOfPositionWorker(range.end); + } + + performance.measure("commentTime", "commentStart"); + return comments; } function getLeadingCommentsOfPosition(pos: number) { @@ -151,6 +165,13 @@ namespace ts { } function getTrailingCommentsOfPosition(pos: number) { + performance.mark("commentStart"); + const comments = getTrailingCommentsOfPositionWorker(pos); + performance.measure("commentTime", "commentStart"); + return comments; + } + + function getTrailingCommentsOfPositionWorker(pos: number) { if (positionIsSynthesized(pos) || trailingCommentRangePositions[pos]) { return undefined; } @@ -163,6 +184,7 @@ namespace ts { function emitLeadingComments(range: TextRange, comments: CommentRange[]): void; function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode: Node, getTextRangeCallback: (contextNode: Node) => TextRange): void; function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange) { + performance.mark("commentStart"); if (comments && comments.length > 0) { if (contextNode) { range = getTextRangeCallback(contextNode) || range; @@ -173,11 +195,14 @@ namespace ts { // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } + performance.measure("commentTime", "commentStart"); } function emitTrailingComments(range: TextRange, comments: CommentRange[]) { // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ + performance.mark("commentStart"); emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); + performance.measure("commentTime", "commentStart"); } function emitLeadingDetachedComments(range: TextRange): void; @@ -187,7 +212,9 @@ namespace ts { return; } + performance.mark("commentStart"); emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); + performance.measure("commentTime", "commentStart"); } function emitTrailingDetachedComments(range: TextRange): void; @@ -264,9 +291,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 287cac2b372..908de8d4806 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1157,15 +1157,15 @@ namespace ts { duration: number; } - const marks: MarkData[] = []; - const measures: MeasureData[] = []; + const markTimestamps: Map = {}; + const markCounts: Map = {}; + const measureDurations: Map = {}; let start = now(); let enabled = false; /** Gets the current timer for performance measurements. */ export function now() { - // TODO(rbuckton): Determine if there is a higher-resolution timer we can use. return Date.now(); } @@ -1176,103 +1176,110 @@ namespace ts { */ export function mark(markName: string) { if (enabled) { - marks.push({ markName, timestamp: now() }); + markTimestamps[markName] = now(); + markCounts[markName] = getCount(markName) + 1; } } + /** + * Gets the number of marks with the specified name. + * + * @param markName The name of the marks that should be counted. + */ + export function getCount(markName: string) { + if (enabled) { + return getProperty(markCounts, markName) || 0; + } + return 0; + } + + /** + * Gets the most recent timestamp for the marks with the specified name. + * + * @param markName The name of the mark. + */ + export function getTimestamp(markName: string) { + if (enabled) { + return getProperty(markTimestamps, markName) || 0; + } + return 0; + } + + /** + * Clears performance marks. + * + * @param markName The name of the mark whose time values should be cleared. If not + * specified, all marks will be cleared. + */ + export function clearMarks(markName?: string) { + if (markName === undefined) { + forEachKey(markTimestamps, clearMark); + } + else { + clearMark(markName); + } + } + + function clearMark(markName: string) { + markTimestamps[markName] = 0; + markCounts[markName] = 0; + } + /** * Adds a performance measurement with the specified name. * * @param measureName The name of the performance measurement. * @param startMarkName The name of the starting mark. - * If provided, the most recent time value of the start mark is used. - * If not specified, the value is the time that the performance service was - * initialized or the last time it was reset. + * If provided, the most recent time value of the start mark is used. + * If not specified, the value is the time that the performance service was + * initialized or the last time it was reset. * @param endMarkName The name of the ending mark. - * If provided, the most recent time value of the end mark is used. - * If not specified, the current time is used. + * If provided, the most recent time value of the end mark is used. + * If not specified, the current time is used. */ export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { if (enabled) { - measures.push({ - measureName, - startMarkName, - endMarkName, - timestamp: now(), - marksOffset: marks.length - }); + const startTime = startMarkName ? getTimestamp(startMarkName) : start; + const endTime = endMarkName ? getTimestamp(endMarkName) : now(); + const duration = endTime - startTime; + measureDurations[measureName] = getDuration(measureName) + duration; } } /** - * Gets an array of performance measures. + * Gets the total duration of all measurements with the supplied name. * - * @param measureName The name of the measure. - * If provided, only measures with the provided name are returned. - * If not specified, all measures are returned since the last time the - * performance service was reset. + * @param measureName The name of the measure whose durations should be accumulated. */ - export function getMeasures(measureName?: string) { - const result: Measure[] = []; - for (const measure of measures) { - if (measureName !== undefined && measureName !== measure.measureName) { - continue; - } - - let startOffset = 0; - let startTime = start; - if (measure.startMarkName) { - const startMarkIndex = getMarkOffset(measure.startMarkName, 0, measure.marksOffset); - if (startMarkIndex >= 0) { - startOffset = startMarkIndex; - startTime = marks[startMarkIndex].timestamp; - } - } - - let endTime = measure.timestamp; - if (measure.endMarkName) { - const endMarkIndex = getMarkOffset(measure.endMarkName, startOffset, measure.marksOffset); - if (endMarkIndex >= 0) { - endTime = marks[endMarkIndex].timestamp; - } - } - - const duration = endTime - startTime; - result.push({ - name: measure.measureName, - startTime, - duration - }); - } - - return result; + export function getDuration(measureName: string) { + return getProperty(measureDurations, measureName) || 0; } - function getMarkOffset(markName: string, markStart: number, markEnd: number) { - if (markName === undefined) { - return -1; + /** + * Clears performance measures. + * + * @param measureName The name of the measure whose durations should be cleared. If not + * specified, all measures will be cleared. + */ + export function clearMeasures(measureName?: string) { + if (measureName === undefined) { + forEachKey(measureDurations, clearMeasure); } - - if (markStart < 0) { - markStart = 0; + else { + clearMeasure(measureName); } + } - for (let i = markEnd - 1; i >= markStart; i--) { - const mark = marks[i]; - if (mark.markName === markName) { - return i; - } - } - - return -1; + function clearMeasure(measureName: string) { + measureDurations[measureName] = 0; } /** * Resets all marks and measurements in the performance service. */ export function reset() { - marks.length = 0; - measures.length = 0; + clearMarks(); + clearMeasures(); start = now(); } diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 6b284545f0d..1b4531102c9 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -276,7 +276,9 @@ const _super = (function (geti, seti) { currentFileIdentifiers = node.identifiers; sourceMap.setSourceFile(node); comments.setSourceFile(node); + performance.mark("printStart"); emitNodeWithNotificationOption(node, emitWorker); + performance.measure("printTime", "printStart"); return node; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 1346a8c0268..5559f9d3740 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -778,8 +778,7 @@ namespace ts { try { performance.mark("ioReadStart"); text = sys.readFile(fileName, options.charset); - performance.mark("ioReadEnd"); - performance.measure("ioReadTime", "ioReadStart", "ioReadEnd"); + performance.measure("ioReadTime", "ioReadStart"); } catch (e) { if (onError) { @@ -856,8 +855,7 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - performance.mark("ioWriteEnd"); - performance.measure("ioWriteTime", "ioWriteStart", "ioWriteEnd"); + performance.measure("ioWriteTime", "ioWriteStart"); } catch (e) { if (onError) { @@ -1052,8 +1050,7 @@ namespace ts { verifyCompilerOptions(); - performance.mark("programEnd"); - performance.measure("programTime", "programStart", "programEnd"); + performance.measure("programTime", "programStart"); return program; @@ -1300,8 +1297,7 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - performance.mark("emitEnd"); - performance.measure("emitTime", "emitStart", "emitEnd"); + performance.measure("emitTime", "emitStart"); return emitResult; } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 27b098d011a..ea317d03268 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) { @@ -559,15 +559,6 @@ namespace ts { } if (compilerOptions.diagnostics) { - const ioReadTime = reduceLeft(performance.getMeasures("ioReadTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const ioWriteTime = reduceLeft(performance.getMeasures("ioWriteTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const programTime = reduceLeft(performance.getMeasures("programTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const bindTime = reduceLeft(performance.getMeasures("bindTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const checkTime = reduceLeft(performance.getMeasures("checkTime"), (aggregate, measure) => aggregate + measure.duration, 0); - const emitTime = reduceLeft(performance.getMeasures("emitTime"), (aggregate, measure) => aggregate + measure.duration, 0); - performance.disable(); - performance.reset(); - const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1; reportCountStatistic("Files", program.getSourceFiles().length); reportCountStatistic("Lines", countLines(program)); @@ -584,13 +575,23 @@ namespace ts { // 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")); + reportTimeStatistic("Print time", performance.getDuration("printTime")); + reportTimeStatistic("Comment time", performance.getDuration("commentTime")); + reportTimeStatistic("Sourcemap time", performance.getDuration("sourcemapTime")); + 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(); + performance.reset(); } return { program, exitStatus }; From e5c473c9fc498ff7f865473545773d76e334b9ec Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 May 2016 11:40:01 -0700 Subject: [PATCH 05/12] Adds a NodeFlag to indicate a synthetic node. --- src/compiler/factory.ts | 8 +++----- src/compiler/parser.ts | 2 +- src/compiler/transformers/ts.ts | 3 +++ src/compiler/types.ts | 1 + src/compiler/utilities.ts | 9 ++++++--- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 3523cdb05ee..e59ddfd7317 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -17,9 +17,7 @@ namespace ts { ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); - if (flags) { - node.flags = flags; - } + node.flags = flags | NodeFlags.Synthesized; return node; } @@ -68,8 +66,7 @@ namespace ts { // 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); - clone.flags = node.flags; + const clone = createNode(node.kind, /*location*/ undefined, node.flags); clone.original = node; for (const key in node) { @@ -827,6 +824,7 @@ namespace ts { // Create an identifier and give it a parent. This allows us to resolve the react // namespace during emit. const react = createIdentifier(reactNamespace || "React"); + react.flags &= ~NodeFlags.Synthesized; react.parent = parent; return react; } 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/transformers/ts.ts b/src/compiler/transformers/ts.ts index 9d3ed9f051c..3b7cf95d2f1 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1752,7 +1752,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) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 059434e5251..42beb0e6335 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" diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5f023b3909f..655f731f10c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1831,14 +1831,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; } From 81d5d2b6b6a2eff88938410ce1f485da862a1b3e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 May 2016 13:18:24 -0700 Subject: [PATCH 06/12] Added 'extendedDiagnostics' option for comments/sourcemap measurements. --- src/compiler/commandLineParser.ts | 5 ++ src/compiler/comments.ts | 85 +++++++++++++++++++++++-------- src/compiler/emitter.ts | 2 +- src/compiler/printer.ts | 11 ++-- src/compiler/sourcemap.ts | 75 +++++++++++++++++++++++++-- src/compiler/types.ts | 1 + 6 files changed, 150 insertions(+), 29 deletions(-) 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 2b7ab987e4e..7ad380f06af 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -35,10 +35,14 @@ namespace ts { 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, @@ -78,8 +82,6 @@ namespace ts { function getLeadingComments(range: TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { - performance.mark("commentStart"); - let comments: CommentRange[] = []; let ignored = false; if (contextNode) { @@ -108,7 +110,6 @@ namespace ts { comments = getLeadingCommentsOfPosition(range.pos); } - performance.measure("commentTime", "commentStart"); return comments; } @@ -131,8 +132,6 @@ 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) { - performance.mark("commentStart"); - let ignored = false; if (contextNode) { if (ignoreNodeCallback(contextNode)) { @@ -145,10 +144,8 @@ namespace ts { let comments: CommentRange[]; if (!ignored) { - comments = getTrailingCommentsOfPositionWorker(range.end); + comments = getTrailingCommentsOfPosition(range.end); } - - performance.measure("commentTime", "commentStart"); return comments; } @@ -165,13 +162,6 @@ namespace ts { } function getTrailingCommentsOfPosition(pos: number) { - performance.mark("commentStart"); - const comments = getTrailingCommentsOfPositionWorker(pos); - performance.measure("commentTime", "commentStart"); - return comments; - } - - function getTrailingCommentsOfPositionWorker(pos: number) { if (positionIsSynthesized(pos) || trailingCommentRangePositions[pos]) { return undefined; } @@ -184,7 +174,6 @@ namespace ts { function emitLeadingComments(range: TextRange, comments: CommentRange[]): void; function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode: Node, getTextRangeCallback: (contextNode: Node) => TextRange): void; function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange) { - performance.mark("commentStart"); if (comments && comments.length > 0) { if (contextNode) { range = getTextRangeCallback(contextNode) || range; @@ -195,14 +184,11 @@ namespace ts { // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); } - performance.measure("commentTime", "commentStart"); } function emitTrailingComments(range: TextRange, comments: CommentRange[]) { // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - performance.mark("commentStart"); emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); - performance.measure("commentTime", "commentStart"); } function emitLeadingDetachedComments(range: TextRange): void; @@ -212,9 +198,7 @@ namespace ts { return; } - performance.mark("commentStart"); emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); - performance.measure("commentTime", "commentStart"); } function emitTrailingDetachedComments(range: TextRange): void; @@ -276,6 +260,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[] { + performance.mark("commentStart"); + 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[] { + performance.mark("commentStart"); + const comments = getTrailingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); + performance.measure("commentTime", "commentStart"); + return comments; + }, + getTrailingCommentsOfPosition(pos: number): CommentRange[] { + performance.mark("commentStart"); + const comments = getTrailingCommentsOfPosition(pos); + performance.measure("commentTime", "commentStart"); + return comments; + }, + emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { + performance.mark("commentStart"); + emitLeadingComments(range, comments, contextNode, getTextRangeCallback); + performance.measure("commentTime", "commentStart"); + }, + emitTrailingComments(range: TextRange, comments: CommentRange[]): void { + performance.mark("commentStart"); + emitLeadingComments(range, comments); + performance.measure("commentTime", "commentStart"); + }, + emitLeadingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { + performance.mark("commentStart"); + emitLeadingDetachedComments(range, contextNode, ignoreNodeCallback); + performance.measure("commentTime", "commentStart"); + }, + emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { + performance.mark("commentStart"); + emitTrailingDetachedComments(range, contextNode, ignoreNodeCallback); + performance.measure("commentTime", "commentStart"); + } + }; + } + function reset() { currentSourceFile = undefined; currentText = 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/printer.ts b/src/compiler/printer.ts index 1b4531102c9..f1f3e213d69 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, @@ -267,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) { @@ -276,8 +276,13 @@ const _super = (function (geti, seti) { currentFileIdentifiers = node.identifiers; sourceMap.setSourceFile(node); comments.setSourceFile(node); - performance.mark("printStart"); emitNodeWithNotificationOption(node, emitWorker); + return node; + } + + function printSourceFileWithExtendedDiagnostics(node: SourceFile) { + performance.mark("printStart"); + printSourceFile(node); performance.measure("printTime", "printStart"); return node; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 58df47c770c..8e7cc3b1d44 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 { + performance.mark("sourcemapStart"); + emitPos(pos); + performance.measure("sourcemapTime", "sourcemapStart"); + }, + emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { + performance.mark("sourcemapStart"); + 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 { + performance.mark("sourcemapStart"); + 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 { + performance.mark("sourcemapStart"); + 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 { + performance.mark("sourcemapStart"); + 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/types.ts b/src/compiler/types.ts index 42beb0e6335..110ca5f512d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2476,6 +2476,7 @@ namespace ts { declaration?: boolean; declarationDir?: string; diagnostics?: boolean; + /*@internal*/ extendedDiagnostics?: boolean; emitBOM?: boolean; help?: boolean; init?: boolean; From fb48731afa0f0a45559f7554da59ac040f142bba Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 May 2016 13:42:51 -0700 Subject: [PATCH 07/12] Cache most recent values for node emit flags and custom ranges --- src/compiler/transformer.ts | 133 ++++++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 19 deletions(-) diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index b9f0402b6ca..ae1ab6fbf62 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -60,6 +60,15 @@ namespace ts { const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + 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[]; @@ -174,103 +183,189 @@ namespace ts { /** * 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) { + // 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; + } + + + let flags: NodeEmitFlags; while (node) { - let flags = nodeEmitFlags[getNodeId(node)]; + let flags = node.id ? nodeEmitFlags[node.id] : undefined; if (flags !== undefined) { - return flags; + break; } node = node.original; } - return undefined; + // 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) { - if (flags & NodeEmitFlags.Merge) { - flags = getNodeEmitFlags(node) | (flags & ~NodeEmitFlags.Merge); + function setNodeEmitFlags(node: T, emitFlags: NodeEmitFlags) { + if (emitFlags & NodeEmitFlags.Merge) { + emitFlags = getNodeEmitFlags(node) | (emitFlags & ~NodeEmitFlags.Merge); } - nodeEmitFlags[getNodeId(node)] = flags; + // Cache the most recently requested value. + lastNodeEmitFlagsNode = node; + lastNodeEmitFlags = emitFlags; + nodeEmitFlags[getNodeId(node)] = emitFlags; 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; + } + + let range: TextRange; let current = node; while (current) { - const sourceMapRange = sourceMapRanges[getNodeId(current)]; - if (sourceMapRange !== undefined) { - return sourceMapRange; + range = current.id ? sourceMapRanges[current.id] : undefined; + 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) { + // Cache the most recently requested value. + lastSourceMapRangeNode = node; + lastSourceMapRange = range; sourceMapRanges[getNodeId(node)] = range; return 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 lastSourceMapRange; + } + + let range: TextRange; let current = node; while (current) { - const tokenSourceMapRange = sourceMapRanges[getNodeId(node) + "-" + token]; - if (tokenSourceMapRange !== undefined) { - return tokenSourceMapRange; + range = current.id ? sourceMapRanges[current.id + "-" + token] : undefined; + if (range !== undefined) { + break; } current = current.original; } - 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) { + // Cache the most recently requested value. + lastTokenSourceMapRangeNode = node; + lastTokenSourceMapRangeToken = token; + lastTokenSourceMapRange = range; sourceMapRanges[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; + } + + let range: TextRange; let current = node; while (current) { - const commentRange = commentRanges[getNodeId(current)]; - if (commentRange !== undefined) { - return commentRange; + range = current.id ? commentRanges[current.id] : undefined; + 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) { + // Cache the most recently requested value. + lastCommentMapRangeNode = node; + lastCommentMapRange = range; commentRanges[getNodeId(node)] = range; return node; } From e64724ea35af979931b07b96a31947c5b9ee2186 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 24 May 2016 16:27:39 -0700 Subject: [PATCH 08/12] Transient node properties for transformations. --- src/compiler/core.ts | 34 ++++++++---- src/compiler/transformer.ts | 107 ++++++++++++++++++++++++++++-------- src/compiler/tsc.ts | 28 ++++++++-- src/compiler/types.ts | 6 ++ 4 files changed, 136 insertions(+), 39 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 908de8d4806..aada521c2e4 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1059,6 +1059,7 @@ namespace ts { this.excludeTransformFlags = TransformFlags.None; this.parent = undefined; this.original = undefined; + this.transformId = 0; } export let objectAllocator: ObjectAllocator = { @@ -1181,16 +1182,20 @@ namespace ts { } } + /** + * Gets the names of all marks. + */ + export function getMarkNames() { + return getKeys(markCounts); + } + /** * Gets the number of marks with the specified name. * * @param markName The name of the marks that should be counted. */ export function getCount(markName: string) { - if (enabled) { - return getProperty(markCounts, markName) || 0; - } - return 0; + return enabled && getProperty(markCounts, markName) || 0; } /** @@ -1199,10 +1204,7 @@ namespace ts { * @param markName The name of the mark. */ export function getTimestamp(markName: string) { - if (enabled) { - return getProperty(markTimestamps, markName) || 0; - } - return 0; + return enabled && getProperty(markTimestamps, markName) || 0; } /** @@ -1221,8 +1223,9 @@ namespace ts { } function clearMark(markName: string) { - markTimestamps[markName] = 0; - markCounts[markName] = 0; + if (delete markTimestamps[markName]) { + delete markCounts[markName]; + } } /** @@ -1246,13 +1249,20 @@ namespace ts { } } + /** + * Gets the names of all recorded measures. + */ + export function getMeasureNames() { + return getKeys(measureDurations); + } + /** * 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 getProperty(measureDurations, measureName) || 0; + return enabled && getProperty(measureDurations, measureName) || 0; } /** @@ -1271,7 +1281,7 @@ namespace ts { } function clearMeasure(measureName: string) { - measureDurations[measureName] = 0; + delete measureDurations[measureName]; } /** diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index ae1ab6fbf62..9e58b02c208 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -45,6 +45,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,12 +62,13 @@ namespace ts { * @param transforms An array of Transformers. */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]) { - const nodeEmitFlags: Map = {}; - const sourceMapRanges: Map = {}; - const commentRanges: Map = {}; + 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; @@ -118,7 +127,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; } /** @@ -181,6 +203,28 @@ namespace ts { emit(node); } + /** + * 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); + } + } + } + /** * Gets flags that control emit behavior of a node. * @@ -196,15 +240,19 @@ namespace ts { return lastNodeEmitFlags; } - + // Get the emit flags for a node or from one of its original nodes. let flags: NodeEmitFlags; - while (node) { - let flags = node.id ? nodeEmitFlags[node.id] : undefined; - if (flags !== undefined) { - break; + let current = node; + while (current) { + if (current.transformId === transformId) { + const nodeEmitFlags = current.emitFlags; + if (nodeEmitFlags) { + flags = nodeEmitFlags & ~NodeEmitFlags.HasNodeEmitFlags; + break; + } } - node = node.original; + current = current.original; } // Cache the most recently requested value. @@ -220,14 +268,17 @@ namespace ts { * @param emitFlags The NodeEmitFlags for the node. */ function setNodeEmitFlags(node: T, emitFlags: NodeEmitFlags) { + // Merge existing flags. if (emitFlags & NodeEmitFlags.Merge) { emitFlags = getNodeEmitFlags(node) | (emitFlags & ~NodeEmitFlags.Merge); } + beforeSetAnnotation(node); + // Cache the most recently requested value. lastNodeEmitFlagsNode = node; lastNodeEmitFlags = emitFlags; - nodeEmitFlags[getNodeId(node)] = emitFlags; + node.emitFlags = emitFlags | NodeEmitFlags.HasNodeEmitFlags; return node; } @@ -246,12 +297,15 @@ namespace ts { 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) { - range = current.id ? sourceMapRanges[current.id] : undefined; - if (range !== undefined) { - break; + if (current.transformId === transformId) { + range = current.sourceMapRange; + if (range !== undefined) { + break; + } } current = current.original; @@ -270,10 +324,12 @@ namespace ts { * @param range The text range. */ function setSourceMapRange(node: T, range: TextRange) { + beforeSetAnnotation(node); + // Cache the most recently requested value. lastSourceMapRangeNode = node; lastSourceMapRange = range; - sourceMapRanges[getNodeId(node)] = range; + node.sourceMapRange = range; return node; } @@ -290,13 +346,15 @@ namespace ts { // 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 lastSourceMapRange; + 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) { - range = current.id ? sourceMapRanges[current.id + "-" + token] : undefined; + range = current.id ? tokenSourceMapRanges[current.id + "-" + token] : undefined; if (range !== undefined) { break; } @@ -323,7 +381,7 @@ namespace ts { lastTokenSourceMapRangeNode = node; lastTokenSourceMapRangeToken = token; lastTokenSourceMapRange = range; - sourceMapRanges[getNodeId(node) + "-" + token] = range; + tokenSourceMapRanges[getNodeId(node) + "-" + token] = range; return node; } @@ -342,12 +400,15 @@ namespace ts { 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) { - range = current.id ? commentRanges[current.id] : undefined; - if (range !== undefined) { - break; + if (current.transformId === transformId) { + range = current.commentRange; + if (range !== undefined) { + break; + } } current = current.original; @@ -363,10 +424,12 @@ namespace ts { * Sets a custom text range to use when emitting comments. */ function setCommentRange(node: T, range: TextRange) { + beforeSetAnnotation(node); + // Cache the most recently requested value. lastCommentMapRangeNode = node; lastCommentMapRange = range; - commentRanges[getNodeId(node)] = range; + node.commentRange = range; return node; } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ea317d03268..9d521c17f28 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -544,7 +544,7 @@ namespace ts { } function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { - if (compilerOptions.diagnostics) { + if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); performance.reset(); } @@ -558,7 +558,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)); @@ -577,9 +577,6 @@ namespace ts { // emit time includes I/O write time. We preserve this behavior so we can accurately compare times. reportTimeStatistic("I/O read", performance.getDuration("ioReadTime")); reportTimeStatistic("I/O write", performance.getDuration("ioWriteTime")); - reportTimeStatistic("Print time", performance.getDuration("printTime")); - reportTimeStatistic("Comment time", performance.getDuration("commentTime")); - reportTimeStatistic("Sourcemap time", performance.getDuration("sourcemapTime")); const programTime = performance.getDuration("programTime"); const bindTime = performance.getDuration("bindTime"); const checkTime = performance.getDuration("checkTime"); @@ -590,6 +587,27 @@ namespace ts { reportTimeStatistic("Emit time", emitTime); reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + if (compilerOptions.extendedDiagnostics) { + sys.write("Extended Diagnostics:" + sys.newLine); + sys.write("Marks:" + sys.newLine); + for (const markName of performance.getMarkNames()) { + if (/^(ioReadStart|ioWriteStart|programStart|bindStart|checkStart|emitStart)$/.test(markName)) { + continue; + } + + reportCountStatistic(" " + markName, performance.getCount(markName)); + } + + sys.write("Measures:" + sys.newLine); + for (const measureName of performance.getMeasureNames()) { + if (/^(ioReadTime|ioWriteTime|programTime|bindTime|checkTime|emitTime)$/.test(measureName)) { + continue; + } + + reportTimeStatistic(" " + measureName, performance.getDuration(measureName)); + } + } + performance.disable(); performance.reset(); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 110ca5f512d..2b601b9c1ea 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -470,6 +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 */ 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 { @@ -2957,6 +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. + + HasNodeEmitFlags = 1 << 31, // Indicates the node has emit flags set. } /** Additional context provided to `visitEachChild` */ From 3bf4f2a6f54d12aadf26a04449d41e280baaf340 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 May 2016 16:48:13 -0700 Subject: [PATCH 09/12] PR feedback, removed now-redundant getUniqueClone --- src/compiler/comments.ts | 13 ++++--------- src/compiler/factory.ts | 15 +++------------ src/compiler/transformers/es6.ts | 4 ++-- src/compiler/transformers/ts.ts | 6 +++--- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 7ad380f06af..79bb795efe5 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -82,12 +82,9 @@ namespace ts { function getLeadingComments(range: TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; function getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { - let comments: CommentRange[] = []; - let ignored = false; if (contextNode) { range = getTextRangeCallback(contextNode) || range; if (ignoreNodeCallback(contextNode)) { - ignored = true; // If the node will not be emitted in JS, remove all the comments (normal, // pinned and `///`) associated with the node, unless it is a triple slash // comment at the top of the file. @@ -101,16 +98,14 @@ namespace ts { // The first `///` will NOT be removed while the second one will be removed // even though both nodes will not be emitted. if (range.pos === 0) { - comments = filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); + return filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); } + + return; } } - if (!ignored) { - comments = getLeadingCommentsOfPosition(range.pos); - } - - return comments; + return getLeadingCommentsOfPosition(range.pos); } /** diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 2fc5a437565..d04d7f0fd0d 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -91,16 +91,6 @@ namespace ts { return clone; } - /** - * Gets a clone of a node with a unique node ID. - */ - export function getUniqueClone(node: T): T { - const clone = getMutableClone(node); - clone.id = 0; - getNodeId(clone); - return clone; - } - // Literals export function createLiteral(textSource: StringLiteral | Identifier, location?: TextRange): StringLiteral; @@ -821,8 +811,9 @@ 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; diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index e2d41783982..1c412f98af7 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -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. @@ -2916,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/ts.ts b/src/compiler/transformers/ts.ts index 488b76cc469..d4f7ea020cb 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -928,10 +928,10 @@ namespace ts { function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { Debug.assert(isIdentifier(node.name)); const name = node.name as Identifier; - const propertyName = getUniqueClone(name); + const propertyName = getMutableClone(name); setNodeEmitFlags(propertyName, NodeEmitFlags.NoComments | NodeEmitFlags.NoSourceMap); - const localName = getUniqueClone(name); + const localName = getMutableClone(name); setNodeEmitFlags(localName, NodeEmitFlags.NoComments); return startOnNewLine( @@ -2922,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; From 3c6ceaf85e4516db1b06ad59da71a5d55624d957 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 26 May 2016 18:25:11 -0700 Subject: [PATCH 10/12] Simplified performance timers --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 4 +- src/compiler/comments.ts | 28 ++++---- src/compiler/core.ts | 132 ++++++-------------------------------- src/compiler/printer.ts | 4 +- src/compiler/program.ts | 16 ++--- src/compiler/sourcemap.ts | 20 +++--- src/compiler/tsc.ts | 27 ++------ 8 files changed, 63 insertions(+), 172 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 707a7567d72..fe225d92254 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -94,9 +94,9 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - performance.mark("bindStart"); + const bindStart = performance.mark(); binder(file, options); - performance.measure("bindTime", "bindStart"); + performance.measure("bindTime", bindStart); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 71e099bbf83..c03c5a163cf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16064,11 +16064,11 @@ namespace ts { } function checkSourceFile(node: SourceFile) { - performance.mark("checkStart"); + const checkStart = performance.mark(); checkSourceFileWorker(node); - performance.measure("checkTime", "checkStart"); + performance.measure("checkTime", checkStart); } // Fully type check a source file and collect the relevant diagnostics. diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 79bb795efe5..a7c7c34ab40 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -272,42 +272,42 @@ namespace ts { reset, setSourceFile, getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { - performance.mark("commentStart"); + const commentStart = performance.mark(); const comments = getLeadingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); return comments; }, getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { - performance.mark("commentStart"); + const commentStart = performance.mark(); const comments = getTrailingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); return comments; }, getTrailingCommentsOfPosition(pos: number): CommentRange[] { - performance.mark("commentStart"); + const commentStart = performance.mark(); const comments = getTrailingCommentsOfPosition(pos); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); return comments; }, emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { - performance.mark("commentStart"); + const commentStart = performance.mark(); emitLeadingComments(range, comments, contextNode, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); }, emitTrailingComments(range: TextRange, comments: CommentRange[]): void { - performance.mark("commentStart"); + const commentStart = performance.mark(); emitLeadingComments(range, comments); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); }, emitLeadingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { - performance.mark("commentStart"); + const commentStart = performance.mark(); emitLeadingDetachedComments(range, contextNode, ignoreNodeCallback); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); }, emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { - performance.mark("commentStart"); + const commentStart = performance.mark(); emitTrailingDetachedComments(range, contextNode, ignoreNodeCallback); - performance.measure("commentTime", "commentStart"); + performance.measure("commentTime", commentStart); } }; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index aada521c2e4..1980cf9ad9e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1139,158 +1139,64 @@ namespace ts { /** Performance measurements for the compiler. */ /*@internal*/ export namespace performance { - interface MarkData { - markName: string; - timestamp: number; - } - - interface MeasureData { - measureName: string; - startMarkName: string; - endMarkName: string; - timestamp: number; - marksOffset: number; - } - - export interface Measure { - name: string; - startTime: number; - duration: number; - } - - const markTimestamps: Map = {}; - const markCounts: Map = {}; - const measureDurations: Map = {}; - - let start = now(); + let counters: Map = {}; + let measures: Map = {}; let enabled = false; - /** Gets the current timer for performance measurements. */ - export function now() { - return Date.now(); - } - /** - * Adds a performance mark with the specified name. + * Increments a counter with the specified name. * - * @param markName The name of the performance mark. + * @param counterName The name of the counter. */ - export function mark(markName: string) { + export function increment(counterName: string) { if (enabled) { - markTimestamps[markName] = now(); - markCounts[markName] = getCount(markName) + 1; + counters[counterName] = (getProperty(counters, counterName) || 0) + 1; } } /** - * Gets the names of all marks. - */ - export function getMarkNames() { - return getKeys(markCounts); - } - - /** - * Gets the number of marks with the specified name. + * Gets the value of the counter with the specified name. * - * @param markName The name of the marks that should be counted. + * @param counterName The name of the counter. */ - export function getCount(markName: string) { - return enabled && getProperty(markCounts, markName) || 0; + export function getCount(counterName: string) { + return enabled && getProperty(counters, counterName) || 0; } /** - * Gets the most recent timestamp for the marks with the specified name. - * - * @param markName The name of the mark. + * Marks the start of a performance measurement. */ - export function getTimestamp(markName: string) { - return enabled && getProperty(markTimestamps, markName) || 0; - } - - /** - * Clears performance marks. - * - * @param markName The name of the mark whose time values should be cleared. If not - * specified, all marks will be cleared. - */ - export function clearMarks(markName?: string) { - if (markName === undefined) { - forEachKey(markTimestamps, clearMark); - } - else { - clearMark(markName); - } - } - - function clearMark(markName: string) { - if (delete markTimestamps[markName]) { - delete markCounts[markName]; - } + export function mark() { + return enabled ? Date.now() : 0; } /** * Adds a performance measurement with the specified name. * * @param measureName The name of the performance measurement. - * @param startMarkName The name of the starting mark. - * If provided, the most recent time value of the start mark is used. - * If not specified, the value is the time that the performance service was - * initialized or the last time it was reset. - * @param endMarkName The name of the ending mark. - * If provided, the most recent time value of the end mark is used. - * If not specified, the current time is used. + * @param marker The timestamp of the starting mark. */ - export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { + export function measure(measureName: string, marker: number) { if (enabled) { - const startTime = startMarkName ? getTimestamp(startMarkName) : start; - const endTime = endMarkName ? getTimestamp(endMarkName) : now(); - const duration = endTime - startTime; - measureDurations[measureName] = getDuration(measureName) + duration; + measures[measureName] = (getProperty(measures, measureName) || 0) + (mark() - marker); } } - /** - * Gets the names of all recorded measures. - */ - export function getMeasureNames() { - return getKeys(measureDurations); - } - /** * 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 enabled && getProperty(measureDurations, measureName) || 0; - } - - /** - * Clears performance measures. - * - * @param measureName The name of the measure whose durations should be cleared. If not - * specified, all measures will be cleared. - */ - export function clearMeasures(measureName?: string) { - if (measureName === undefined) { - forEachKey(measureDurations, clearMeasure); - } - else { - clearMeasure(measureName); - } - } - - function clearMeasure(measureName: string) { - delete measureDurations[measureName]; + return enabled && getProperty(measures, measureName) || 0; } /** * Resets all marks and measurements in the performance service. */ export function reset() { - clearMarks(); - clearMeasures(); - start = now(); + counters = {}; + measures = {}; } /** Enables performance measurements for the compiler. */ diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 12d399b04d8..c577f7999f5 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -281,9 +281,9 @@ const _super = (function (geti, seti) { } function printSourceFileWithExtendedDiagnostics(node: SourceFile) { - performance.mark("printStart"); + const printStart = performance.mark(); printSourceFile(node); - performance.measure("printTime", "printStart"); + performance.measure("printTime", printStart); return node; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 5559f9d3740..03db19ed445 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -776,9 +776,9 @@ namespace ts { function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile { let text: string; try { - performance.mark("ioReadStart"); + const ioReadStart = performance.mark(); text = sys.readFile(fileName, options.charset); - performance.measure("ioReadTime", "ioReadStart"); + performance.measure("ioReadTime", ioReadStart); } catch (e) { if (onError) { @@ -845,7 +845,7 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { - performance.mark("ioWriteStart"); + const ioWriteStart = performance.mark(); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) { @@ -855,7 +855,7 @@ namespace ts { sys.writeFile(fileName, data, writeByteOrderMark); } - performance.measure("ioWriteTime", "ioWriteStart"); + performance.measure("ioWriteTime", ioWriteStart); } catch (e) { if (onError) { @@ -957,7 +957,7 @@ namespace ts { let resolvedTypeReferenceDirectives: Map = {}; let fileProcessingDiagnostics = createDiagnosticCollection(); - performance.mark("programStart"); + const programStart = performance.mark(); host = host || createCompilerHost(options); @@ -1050,7 +1050,7 @@ namespace ts { verifyCompilerOptions(); - performance.measure("programTime", "programStart"); + performance.measure("programTime", programStart); return program; @@ -1283,7 +1283,7 @@ namespace ts { // checked is to not pass the file to getEmitResolver. const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); - performance.mark("emitStart"); + const emitStart = performance.mark(); // TODO(rbuckton): remove USE_TRANSFORMS condition when we switch to transforms permanently. let useLegacyEmitter = options.useLegacyEmitter; @@ -1297,7 +1297,7 @@ namespace ts { getEmitHost(writeFileCallback), sourceFile); - performance.measure("emitTime", "emitStart"); + performance.measure("emitTime", emitStart); return emitResult; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 8e7cc3b1d44..de056196bd0 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -782,30 +782,30 @@ namespace ts { getSourceMapData, setSourceFile, emitPos(pos: number): void { - performance.mark("sourcemapStart"); + const sourcemapStart = performance.mark(); emitPos(pos); - performance.measure("sourcemapTime", "sourcemapStart"); + performance.measure("sourceMapTime", sourcemapStart); }, emitStart(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { - performance.mark("sourcemapStart"); + const sourcemapStart = performance.mark(); emitStart(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); - performance.measure("sourcemapTime", "sourcemapStart"); + performance.measure("sourceMapTime", sourcemapStart); }, emitEnd(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean, ignoreChildrenCallback?: (node: Node) => boolean, getTextRangeCallback?: (node: Node) => TextRange): void { - performance.mark("sourcemapStart"); + const sourcemapStart = performance.mark(); emitEnd(range, contextNode, ignoreNodeCallback, ignoreChildrenCallback, getTextRangeCallback); - performance.measure("sourcemapTime", "sourcemapStart"); + performance.measure("sourceMapTime", sourcemapStart); }, emitTokenStart(token: SyntaxKind, tokenStartPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - performance.mark("sourcemapStart"); + const sourcemapStart = performance.mark(); tokenStartPos = emitTokenStart(token, tokenStartPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); - performance.measure("sourcemapTime", "sourcemapStart"); + performance.measure("sourceMapTime", sourcemapStart); return tokenStartPos; }, emitTokenEnd(token: SyntaxKind, tokenEndPos: number, contextNode?: Node, ignoreTokenCallback?: (node: Node) => boolean, getTokenTextRangeCallback?: (node: Node, token: SyntaxKind) => TextRange): number { - performance.mark("sourcemapStart"); + const sourcemapStart = performance.mark(); tokenEndPos = emitTokenEnd(token, tokenEndPos, contextNode, ignoreTokenCallback, getTokenTextRangeCallback); - performance.measure("sourcemapTime", "sourcemapStart"); + performance.measure("sourceMapTime", sourcemapStart); return tokenEndPos; }, changeEmitSourcePos, diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9d521c17f28..f7bb2f5d4d8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -571,6 +571,12 @@ 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 @@ -587,27 +593,6 @@ namespace ts { reportTimeStatistic("Emit time", emitTime); reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); - if (compilerOptions.extendedDiagnostics) { - sys.write("Extended Diagnostics:" + sys.newLine); - sys.write("Marks:" + sys.newLine); - for (const markName of performance.getMarkNames()) { - if (/^(ioReadStart|ioWriteStart|programStart|bindStart|checkStart|emitStart)$/.test(markName)) { - continue; - } - - reportCountStatistic(" " + markName, performance.getCount(markName)); - } - - sys.write("Measures:" + sys.newLine); - for (const measureName of performance.getMeasureNames()) { - if (/^(ioReadTime|ioWriteTime|programTime|bindTime|checkTime|emitTime)$/.test(measureName)) { - continue; - } - - reportTimeStatistic(" " + measureName, performance.getDuration(measureName)); - } - } - performance.disable(); performance.reset(); } From 0d2b1c47f8eb6fd09257395332f243cf733b340b Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 27 May 2016 14:55:11 -0700 Subject: [PATCH 11/12] Moved code around to fix compile errors in processDiagnosticMessages script. --- src/compiler/core.ts | 12 +++++ src/compiler/transformer.ts | 94 +++++++++++++++++++++++++++++++++++++ src/compiler/types.ts | 93 ------------------------------------ src/compiler/utilities.ts | 12 ----- 4 files changed, 106 insertions(+), 105 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1980cf9ad9e..de6e5357ea7 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1120,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) { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 9e58b02c208..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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2b601b9c1ea..d868e024e0f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2975,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 655f731f10c..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; } From 82e2531f6daad275e0bf5ba141856d224424cad5 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 27 May 2016 14:55:59 -0700 Subject: [PATCH 12/12] Performance API cleanup, pre-init with common values. --- src/compiler/core.ts | 43 +++++++++++++++++++++++-------------------- src/compiler/tsc.ts | 2 -- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index de6e5357ea7..324975a9da9 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1151,9 +1151,8 @@ namespace ts { /** Performance measurements for the compiler. */ /*@internal*/ export namespace performance { - let counters: Map = {}; - let measures: Map = {}; - let enabled = false; + let counters: Map; + let measures: Map; /** * Increments a counter with the specified name. @@ -1161,7 +1160,7 @@ namespace ts { * @param counterName The name of the counter. */ export function increment(counterName: string) { - if (enabled) { + if (counters) { counters[counterName] = (getProperty(counters, counterName) || 0) + 1; } } @@ -1172,14 +1171,14 @@ namespace ts { * @param counterName The name of the counter. */ export function getCount(counterName: string) { - return enabled && getProperty(counters, counterName) || 0; + return counters && getProperty(counters, counterName) || 0; } /** * Marks the start of a performance measurement. */ export function mark() { - return enabled ? Date.now() : 0; + return measures ? Date.now() : 0; } /** @@ -1189,7 +1188,7 @@ namespace ts { * @param marker The timestamp of the starting mark. */ export function measure(measureName: string, marker: number) { - if (enabled) { + if (measures) { measures[measureName] = (getProperty(measures, measureName) || 0) + (mark() - marker); } } @@ -1200,25 +1199,29 @@ namespace ts { * @param measureName The name of the measure whose durations should be accumulated. */ export function getDuration(measureName: string) { - return enabled && getProperty(measures, measureName) || 0; + return measures && getProperty(measures, measureName) || 0; } - /** - * Resets all marks and measurements in the performance service. - */ - export function reset() { - counters = {}; - measures = {}; - } - - /** Enables performance measurements for the compiler. */ + /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - enabled = true; + counters = { }; + measures = { + programTime: 0, + parseTime: 0, + bindTime: 0, + emitTime: 0, + ioReadTime: 0, + ioWriteTime: 0, + printTime: 0, + commentTime: 0, + sourceMapTime: 0 + }; } - /** Disables performance measurements for the compiler. */ + /** Disables (and clears) performance measurements for the compiler. */ export function disable() { - enabled = false; + counters = undefined; + measures = undefined; } } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index f7bb2f5d4d8..2c15a972bf8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -546,7 +546,6 @@ namespace ts { function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) { if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); - performance.reset(); } const program = createProgram(fileNames, compilerOptions, compilerHost); @@ -594,7 +593,6 @@ namespace ts { reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); performance.disable(); - performance.reset(); } return { program, exitStatus };