diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index bc7a867394332..4377edbb84b54 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1038,10 +1038,6 @@ namespace ts { return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; } - export function computeSignature(text: string, data: WriteFileCallbackData | undefined, computeHash: BuilderState.ComputeHash | undefined) { - return BuilderState.computeSignature(data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text, computeHash); - } - export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { @@ -1170,7 +1166,7 @@ namespace ts { const file = sourceFiles[0]; const info = state.fileInfos.get(file.resolvedPath)!; if (info.signature === file.version) { - newSignature = computeSignature(text, data, computeHash); + newSignature = BuilderState.computeSignature(text, computeHash, data); if (newSignature !== file.version) { // Update it if (host.storeFilesChangingSignatureDuringEmit) (state.filesChangingSignature ||= new Set()).add(file.resolvedPath); if (state.exportedModulesMap) BuilderState.updateExportedModules(state, file, file.exportedModulesFromDeclarationEmit); @@ -1195,7 +1191,7 @@ namespace ts { if (state.compilerOptions.composite) { const filePath = sourceFiles[0].resolvedPath; const oldSignature = state.emitSignatures?.get(filePath); - newSignature ||= computeSignature(text, data, computeHash); + newSignature ||= BuilderState.computeSignature(text, computeHash, data); if (newSignature !== oldSignature) { (state.emitSignatures ||= new Map()).set(filePath, newSignature); state.hasChangedEmitSignature = true; @@ -1203,7 +1199,7 @@ namespace ts { } } else if (state.compilerOptions.composite) { - const newSignature = computeSignature(text, data, computeHash); + const newSignature = BuilderState.computeSignature(text, computeHash, data); if (newSignature !== state.outSignature) { state.outSignature = newSignature; state.hasChangedEmitSignature = true; diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 4be044c03551e..8cc0703650d3f 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -3,8 +3,8 @@ namespace ts { export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput { const outputFiles: OutputFile[] = []; - const { emitSkipped, diagnostics, exportedModulesFromDeclarationEmit } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); - return { outputFiles, emitSkipped, diagnostics, exportedModulesFromDeclarationEmit }; + const { emitSkipped, diagnostics } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit); + return { outputFiles, emitSkipped, diagnostics }; function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { outputFiles.push({ name: fileName, writeByteOrderMark, text }); @@ -346,6 +346,10 @@ namespace ts { (state.hasCalledUpdateShapeSignature ||= new Set()).add(path); } + export function computeSignature(text: string, computeHash: ComputeHash | undefined, data?: WriteFileCallbackData) { + return (computeHash || generateDjb2Hash)(data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text); + } + /** * Returns if the shape of the signature has changed since last emit */ @@ -357,22 +361,21 @@ namespace ts { const prevSignature = info.signature; let latestSignature: string | undefined; if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) { - const emitOutput = getFileEmitOutput( - programOfThisState, + programOfThisState.emit( sourceFile, - /*emitOnlyDtsFiles*/ true, + (fileName, text, _writeByteOrderMark, _onError, _sourceFiles, data) => { + Debug.assert(isDeclarationFileName(fileName), "File extension for signature expected to be dts"); + Debug.assert(latestSignature === undefined, "Only one file should be set"); + latestSignature = computeSignature(text, computeHash, data); + if (latestSignature !== prevSignature) { + updateExportedModules(state, sourceFile, data?.exportedModulesFromDeclarationEmit); + } + }, cancellationToken, + /*emitOnlyDtsFiles*/ true, /*customTransformers*/ undefined, /*forceDtsEmit*/ true ); - const firstDts = firstOrUndefined(emitOutput.outputFiles); - if (firstDts) { - Debug.assert(isDeclarationFileName(firstDts.name), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); - latestSignature = computeSignature(firstDts.text, computeHash); - if (latestSignature !== prevSignature) { - updateExportedModules(state, sourceFile, emitOutput.exportedModulesFromDeclarationEmit); - } - } } // Default is to use file version as signature if (latestSignature === undefined) { @@ -395,10 +398,6 @@ namespace ts { return latestSignature !== prevSignature; } - export function computeSignature(text: string, computeHash: ComputeHash | undefined) { - return (computeHash || generateDjb2Hash)(text); - } - /** * Coverts the declaration emit result into exported modules map */ diff --git a/src/compiler/builderStatePublic.ts b/src/compiler/builderStatePublic.ts index 6a9495d27d491..29c6b61ef36fb 100644 --- a/src/compiler/builderStatePublic.ts +++ b/src/compiler/builderStatePublic.ts @@ -3,7 +3,6 @@ namespace ts { outputFiles: OutputFile[]; emitSkipped: boolean; /* @internal */ diagnostics: readonly Diagnostic[]; - /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; } export interface OutputFile { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 355e1fdcffd3e..29a7d8ce1e201 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -276,6 +276,15 @@ namespace ts { return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`); } + /*@internal*/ + export function getCacheDtsEmitResultKey(sourceFileOrBundle: SourceFile | Bundle, options: CompilerOptions) { + return isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.resolvedPath : outFile(options) as Path; + } + + function isForceDtsEmitResult(result: CacheDtsEmitResult | undefined): result is ForceDtsEmitResult { + return !!result && !(result as TransformationResult).transformed; + } + /*@internal*/ // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult { @@ -288,7 +297,6 @@ namespace ts { const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); let bundleBuildInfo: BundleBuildInfo | undefined; let emitSkipped = false; - let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; // Emit each output file enter(); @@ -308,7 +316,6 @@ namespace ts { diagnostics: emitterDiagnostics.getDiagnostics(), emittedFiles: emittedFilesList, sourceMaps: sourceMapDataList, - exportedModulesFromDeclarationEmit }; function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { @@ -413,8 +420,7 @@ namespace ts { substituteNode: transform.substituteNode, }); - Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); - printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions); + printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform, printer, compilerOptions); // Clean up emit nodes on parse tree transform.dispose(); @@ -435,12 +441,48 @@ namespace ts { const filesForEmit = forceDtsEmit ? sourceFiles : filter(sourceFiles, isSourceFileNotJson); // Setup and perform the transformation to retrieve declarations from the input files const inputListOrBundle = outFile(compilerOptions) ? [factory.createBundle(filesForEmit, !isSourceFile(sourceFileOrBundle) ? sourceFileOrBundle.prepends : undefined)] : filesForEmit; - if (emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { + // Use existing result: + const key = getCacheDtsEmitResultKey(sourceFileOrBundle, compilerOptions); + const cache = host.getDtsEmitResultCache(); + const existing = declarationTransformers.length === 1 ? cache.get(key) : undefined; + if (isForceDtsEmitResult(existing)) { + if (existing.diagnostics?.length) { + for (const diagnostic of existing.diagnostics) { + emitterDiagnostics.add(diagnostic); + } + } + const declBlocked = !!existing.diagnostics?.length || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; + emitSkipped = emitSkipped || declBlocked; + if (!declBlocked || forceDtsEmit) { + // Write the output file + if (declarationMapPath && !forceDtsEmit) { + if (sourceMapDataList && existing.sourceMapData) { + sourceMapDataList.push(existing.sourceMapData); + } + writeFile(host, emitterDiagnostics, declarationMapPath, existing.sourceMap!, /*writeByteOrderMark*/ false, sourceFiles); + } + writeFile( + host, + emitterDiagnostics, + declarationFilePath, + existing.text, + !!compilerOptions.emitBOM, + filesForEmit, + { + sourceMapUrlPos: existing.sourceMapUrlPos, + exportedModulesFromDeclarationEmit: existing.exportedModulesFromDeclarationEmit, + } + ); + } + return; + } + + if (!existing && emitOnlyDtsFiles && !getEmitDeclarations(compilerOptions)) { // Checker wont collect the linked aliases since thats only done when declaration is enabled. // Do that here when emitting only dts files filesForEmit.forEach(collectLinkedAliases); } - const declarationTransform = transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); + const declarationTransform = existing || transformNodes(resolver, host, factory, compilerOptions, inputListOrBundle, declarationTransformers, /*allowDtsFiles*/ false); if (length(declarationTransform.diagnostics)) { for (const diagnostic of declarationTransform.diagnostics!) { emitterDiagnostics.add(diagnostic); @@ -453,8 +495,8 @@ namespace ts { noEmitHelpers: true, module: compilerOptions.module, target: compilerOptions.target, - sourceMap: compilerOptions.sourceMap, - inlineSourceMap: compilerOptions.inlineSourceMap, + // Emit maps when there is noEmit not set with forceDtsEmit + sourceMap: (!forceDtsEmit || !compilerOptions.noEmit) && compilerOptions.declarationMap, extendedDiagnostics: compilerOptions.extendedDiagnostics, onlyPrintJsDocStyle: true, writeBundleFileInfo: !!bundleBuildInfo, @@ -474,24 +516,19 @@ namespace ts { const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit; emitSkipped = emitSkipped || declBlocked; if (!declBlocked || forceDtsEmit) { - Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); printSourceFileOrBundle( declarationFilePath, declarationMapPath, - declarationTransform.transformed[0], + declarationTransform, declarationPrinter, { - sourceMap: !forceDtsEmit && compilerOptions.declarationMap, + sourceMap: printerOptions.sourceMap, sourceRoot: compilerOptions.sourceRoot, mapRoot: compilerOptions.mapRoot, extendedDiagnostics: compilerOptions.extendedDiagnostics, // Explicitly do not passthru either `inline` option } ); - if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { - const sourceFile = declarationTransform.transformed[0]; - exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; - } } declarationTransform.dispose(); if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo; @@ -511,7 +548,10 @@ namespace ts { forEachChild(node, collectLinkedAliases); } - function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) { + function printSourceFileOrBundle( + jsFilePath: string, sourceMapFilePath: string | undefined, transform: TransformationResult, printer: Printer, mapOptions: SourceMapOptions) { + Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); + const sourceFileOrBundle = transform.transformed[0]; const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; @@ -534,9 +574,11 @@ namespace ts { } let sourceMapUrlPos; + let sourceMap; + let sourceMapData; if (sourceMapGenerator) { if (sourceMapDataList) { - sourceMapDataList.push({ + sourceMapDataList.push(sourceMapData = { inputSourceFileNames: sourceMapGenerator.getSources(), sourceMap: sourceMapGenerator.toJSON() }); @@ -557,8 +599,8 @@ namespace ts { // Write the source map if (sourceMapFilePath) { - const sourceMap = sourceMapGenerator.toString(); - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); + sourceMap = sourceMapGenerator.toString(); + if (!forceDtsEmit) writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); if (printer.bundleFileInfo) printer.bundleFileInfo.mapHash = BuilderState.computeSignature(sourceMap, maybeBind(host, host.createHash)); } } @@ -568,10 +610,33 @@ namespace ts { // Write the output file const text = writer.getText(); - writeFile(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, { sourceMapUrlPos }); + writeFile( + host, + emitterDiagnostics, + jsFilePath, + writer.getText(), + !!compilerOptions.emitBOM, + sourceFiles, + { + sourceMapUrlPos, + exportedModulesFromDeclarationEmit: sourceFile?.exportedModulesFromDeclarationEmit, + } + ); // We store the hash of the text written in the buildinfo to ensure that text of the referenced d.ts file is same as whats in the buildinfo // This is needed because incremental can be toggled between two runs and we might use stale file text to do text manipulation in prepend mode if (printer.bundleFileInfo) printer.bundleFileInfo.hash = BuilderState.computeSignature(text, maybeBind(host, host.createHash)); + if (forceDtsEmit) { + // Store the result + const key = getCacheDtsEmitResultKey(sourceFileOrBundle, compilerOptions); + host.getDtsEmitResultCache().set(key, { + diagnostics: transform.diagnostics, + text: writer.getText(), + sourceMap, + sourceMapData, + sourceMapUrlPos, + exportedModulesFromDeclarationEmit: sourceFile?.exportedModulesFromDeclarationEmit, + }); + } // Reset state writer.clear(); @@ -836,7 +901,7 @@ namespace ts { newBuildInfo.program = buildInfo.program; if (newBuildInfo.program && changedDtsText !== undefined && config.options.composite) { // Update the output signature - (newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = computeSignature(changedDtsText, changedDtsData, createHash); + (newBuildInfo.program as ProgramBundleEmitBuildInfo).outSignature = BuilderState.computeSignature(changedDtsText, createHash, changedDtsData); newBuildInfo.program.dtsChangeTime = getCurrentTime(host).getTime(); } // Update sourceFileInfo @@ -870,6 +935,7 @@ namespace ts { redirectTargetsMap: createMultiMap(), getFileIncludeReasons: notImplemented, createHash, + getDtsEmitResultCache: () => new Map(), }; emitFiles( notImplementedResolver, diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 3f6c23f2b0735..73493289f96fa 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -1,146 +1,209 @@ /*@internal*/ /** Performance measurements for the compiler. */ -namespace ts.performance { - let perfHooks: PerformanceHooks | undefined; - // when set, indicates the implementation of `Performance` to use for user timing. - // when unset, indicates user timing is unavailable or disabled. - let performanceImpl: Performance | undefined; - - export interface Timer { +namespace ts { + interface Timer { enter(): void; exit(): void; } - export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { - return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; + export interface Statistic { + name: string; + value: number; + type: StatisticType + } + + export enum StatisticType { + time, + count, + memory, } - export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { - let enterCount = 0; + const nullTimer: Timer = { enter: noop, exit: noop }; + export const performance = createPerformanceTracker(); + export const buildPerformance = createPerformanceTracker(); + + function createPerformanceTracker() { + let perfHooks: PerformanceHooks | undefined; + // when set, indicates the implementation of `Performance` to use for user timing. + // when unset, indicates user timing is unavailable or disabled. + let performanceImpl: Performance | undefined; + let enabled = false; + let timeorigin = timestamp(); + const marks = new Map(); + const counts = new Map(); + const durations = new Map(); + const durationMarks = new Set(); + let statistics: ESMap | undefined; + return { - enter, - exit + createTimerIf, + createTimer, + mark, + measure, + addStatistics, + getCount, + getDuration, + forEachMeasure, + forEachCount, + forEachStatistics, + isEnabled, + enable, + disable, }; - function enter() { - if (++enterCount === 1) { - mark(startMarkName); + function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { + return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; + } + + function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { + let enterCount = 0; + return { + enter, + exit + }; + + function enter() { + if (++enterCount === 1) { + mark(startMarkName); + } + } + + function exit() { + if (--enterCount === 0) { + mark(endMarkName); + measure(measureName, startMarkName, endMarkName); + } + else if (enterCount < 0) { + Debug.fail("enter/exit count does not match."); + } } } - function exit() { - if (--enterCount === 0) { - mark(endMarkName); - measure(measureName, startMarkName, endMarkName); + /** + * Marks a performance event. + * + * @param markName The name of the mark. + */ + function mark(markName: string) { + if (enabled) { + const count = counts.get(markName) ?? 0; + counts.set(markName, count + 1); + marks.set(markName, timestamp()); + performanceImpl?.mark(markName); } - else if (enterCount < 0) { - Debug.fail("enter/exit count does not match."); + } + + /** + * 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 not supplied, the point at which the + * profiler was enabled is used. + * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is + * used. + */ + function measure(measureName: string, startMarkName: string, endMarkName: string) { + if (enabled) { + durationMarks.add(startMarkName).add(endMarkName); + const end = marks.get(endMarkName) ?? timestamp(); + const start = marks.get(startMarkName) ?? timeorigin; + const previousDuration = durations.get(measureName) || 0; + durations.set(measureName, previousDuration + (end - start)); + performanceImpl?.measure(measureName, startMarkName, endMarkName); + } + } + + function addStatistics(s: Statistic) { + if (enabled) { + const existing = statistics?.get(s.name); + if (existing) { + if (existing.type === StatisticType.memory) existing.value = Math.max(existing.value, s.value); + else existing.value += s.value; + } + else { + (statistics ??= new Map()).set(s.name, s); + } } } - } - export const nullTimer: Timer = { enter: noop, exit: noop }; - - let enabled = false; - let timeorigin = timestamp(); - const marks = new Map(); - const counts = new Map(); - const durations = new Map(); - - /** - * Marks a performance event. - * - * @param markName The name of the mark. - */ - export function mark(markName: string) { - if (enabled) { - const count = counts.get(markName) ?? 0; - counts.set(markName, count + 1); - marks.set(markName, timestamp()); - performanceImpl?.mark(markName); + /** + * Gets the number of times a marker was encountered. + * + * @param markName The name of the mark. + */ + function getCount(markName: string) { + return counts.get(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 not supplied, the point at which the - * profiler was enabled is used. - * @param endMarkName The name of the ending mark. If not supplied, the current timestamp is - * used. - */ - export function measure(measureName: string, startMarkName?: string, endMarkName?: string) { - if (enabled) { - const end = (endMarkName !== undefined ? marks.get(endMarkName) : undefined) ?? timestamp(); - const start = (startMarkName !== undefined ? marks.get(startMarkName) : undefined) ?? timeorigin; - const previousDuration = durations.get(measureName) || 0; - durations.set(measureName, previousDuration + (end - start)); - performanceImpl?.measure(measureName, startMarkName, endMarkName); + /** + * Gets the total duration of all measurements with the supplied name. + * + * @param measureName The name of the measure whose durations should be accumulated. + */ + function getDuration(measureName: string) { + return durations.get(measureName) || 0; } - } - /** - * Gets the number of times a marker was encountered. - * - * @param markName The name of the mark. - */ - export function getCount(markName: string) { - return counts.get(markName) || 0; - } + /** + * Iterate over each measure, performing some action + * + * @param cb The action to perform for each measure + */ + function forEachMeasure(cb: (duration: number, measureName: string) => void) { + durations.forEach(cb); + } - /** - * 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 durations.get(measureName) || 0; - } + /** + * Iterate over each count which is not duration mark, performing some action + * + * @param cb The action to perform for each measure + */ + function forEachCount(cb: (count: number, countName: string) => void) { + counts.forEach((count, countName) => !durationMarks.has(countName) && cb(count, countName)); + } - /** - * Iterate over each measure, performing some action - * - * @param cb The action to perform for each measure - */ - export function forEachMeasure(cb: (measureName: string, duration: number) => void) { - durations.forEach((duration, measureName) => cb(measureName, duration)); - } - /** - * Indicates whether the performance API is enabled. - */ - export function isEnabled() { - return enabled; - } + function forEachStatistics(cb: (statistic: Statistic, name: string) => void) { + statistics?.forEach(cb); + } - /** Enables (and resets) performance measurements for the compiler. */ - export function enable(system: System = sys) { - if (!enabled) { - enabled = true; - perfHooks ||= tryGetNativePerformanceHooks(); - if (perfHooks) { - timeorigin = perfHooks.performance.timeOrigin; - // NodeJS's Web Performance API is currently slower than expected, but we'd still like - // to be able to leverage native trace events when node is run with either `--cpu-prof` - // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when - // running in debug mode (since its possible to generate a cpu profile while debugging). - if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { - performanceImpl = perfHooks.performance; + /** + * Indicates whether the performance API is enabled. + */ + function isEnabled() { + return enabled; + } + + /** Enables (and resets) performance measurements for the compiler. */ + function enable(system: System = sys) { + if (!enabled) { + enabled = true; + perfHooks ||= tryGetNativePerformanceHooks(); + if (perfHooks) { + timeorigin = perfHooks.performance.timeOrigin; + // NodeJS's Web Performance API is currently slower than expected, but we'd still like + // to be able to leverage native trace events when node is run with either `--cpu-prof` + // or `--prof`, if we're running with our own `--generateCpuProfile` flag, or when + // running in debug mode (since its possible to generate a cpu profile while debugging). + if (perfHooks.shouldWriteNativeEvents || system?.cpuProfilingEnabled?.() || system?.debugMode) { + performanceImpl = perfHooks.performance; + } } } + return true; } - return true; - } - /** Disables performance measurements for the compiler. */ - export function disable() { - if (enabled) { - marks.clear(); - counts.clear(); - durations.clear(); - performanceImpl = undefined; - enabled = false; + /** Disables performance measurements for the compiler. */ + function disable() { + if (enabled) { + marks.clear(); + counts.clear(); + durations.clear(); + durationMarks.clear(); + statistics?.clear(); + performanceImpl = undefined; + enabled = false; + } } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 018367d0fbca8..3d1d02fad9458 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1106,6 +1106,7 @@ namespace ts { // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. let redirectTargetsMap = createMultiMap(); let usesUriStyleNodeCoreModules = false; + let cacheDtsEmitResult: ESMap | undefined; /** * map with @@ -1930,6 +1931,7 @@ namespace ts { redirectTargetsMap, getFileIncludeReasons: program.getFileIncludeReasons, createHash: maybeBind(host, host.createHash), + getDtsEmitResultCache: () => cacheDtsEmitResult ||= new Map(), }; } @@ -2470,7 +2472,29 @@ namespace ts { return runWithCancellationToken(() => { const resolver = getTypeChecker().getEmitResolver(sourceFile, cancellationToken); // Don't actually write any files since we're just getting diagnostics. - return ts.getDeclarationDiagnostics(getEmitHost(noop), resolver, sourceFile) || emptyArray; + const emitHost = getEmitHost(noop); + if (sourceFile) { + if (!outFile(options)) { + // Check from cache + const existing = cacheDtsEmitResult?.get(sourceFile.resolvedPath); + if (existing) return existing.diagnostics || emptyArray; + } + const result = transformNodes(resolver, emitHost, factory, options, [sourceFile], [transformDeclarations], /*allowDtsFiles*/ false); + if (!outFile(options)) { + (cacheDtsEmitResult ||= new Map()).set(sourceFile.resolvedPath, result); + } + return result.diagnostics || emptyArray; + } + let diagnostics: DiagnosticWithLocation[] | undefined; + forEachEmittedFile(emitHost, (_emitFileNames, sourceFileOrBundle) => { + const key = getCacheDtsEmitResultKey(sourceFileOrBundle!, options); + const existing = cacheDtsEmitResult?.get(key); + if (existing) return existing.diagnostics || emptyArray; + const result = transformNodes(resolver, emitHost, factory, options, [sourceFileOrBundle!], [transformDeclarations], /*allowDtsFiles*/ false); + diagnostics = addRange(diagnostics, result.diagnostics); + (cacheDtsEmitResult ||= new Map()).set(key, result); + }); + return diagnostics || emptyArray; }); } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index e3aebc849b6a3..22bb9e92424e7 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -5,9 +5,7 @@ namespace ts { } export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { - const { enter, exit } = generatorOptions.extendedDiagnostics - ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") - : performance.nullTimer; + const { enter, exit } = performance.createTimerIf(!!generatorOptions.extendedDiagnostics, "Source Map", "beforeSourcemap", "afterSourcemap"); // Current source map file and its index in the sources list const rawSources: string[] = []; diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 76db95b260377..a0ba6b093a4c7 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -1,11 +1,5 @@ /*@internal*/ namespace ts { - export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, file: SourceFile | undefined): DiagnosticWithLocation[] | undefined { - const compilerOptions = host.getCompilerOptions(); - const result = transformNodes(resolver, host, factory, compilerOptions, file ? [file] : filter(host.getSourceFiles(), isSourceFileNotJson), [transformDeclarations], /*allowDtsFiles*/ false); - return result.diagnostics; - } - function hasInternalAnnotation(range: CommentRange, currentSourceFile: SourceFile) { const comment = currentSourceFile.text.substring(range.pos, range.end); return stringContains(comment, "@internal"); diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index e7d4d8196e13e..d07998d48ef9f 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -401,7 +401,7 @@ namespace ts { if (value) { return isParsedCommandLine(value) ? value : undefined; } - + buildPerformance.mark("beforeConfigFileParsing"); let diagnostic: Diagnostic | undefined; const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state; let parsed: ParsedCommandLine | undefined; @@ -415,6 +415,8 @@ namespace ts { parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; } configFileCache.set(configFilePath, parsed || diagnostic!); + buildPerformance.mark("afterConfigFileParsing"); + buildPerformance.measure("Config file parsing", "beforeConfigFileParsing", "afterConfigFileParsing"); return parsed; } @@ -732,6 +734,7 @@ namespace ts { if (updateOutputFileStampsPending) { updateOutputTimestamps(state, config, projectPath); } + buildPerformance.mark("Timestamps only updates"); return doneInvalidatedProject(state, projectPath); } }; @@ -845,6 +848,8 @@ namespace ts { function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { executeSteps(BuildStep.Done, cancellationToken, writeFile, customTransformers); + if (kind === InvalidatedProjectKind.Build) buildPerformance.mark("Projects built"); + else buildPerformance.mark("Bundles updated"); return doneInvalidatedProject(state, projectPath); } @@ -1342,8 +1347,7 @@ namespace ts { reportQueue: boolean ): InvalidatedProject | undefined { const info = getNextInvalidatedProjectCreateInfo(state, buildOrder, reportQueue); - if (!info) return info; - return createInvalidatedProjectWithInfo(state, info, buildOrder); + return info && createInvalidatedProjectWithInfo(state, info, buildOrder); } function listEmittedFile({ write }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { @@ -1799,7 +1803,10 @@ namespace ts { return prior; } + buildPerformance.mark("beforeUpToDateCheckTime"); const actual = getUpToDateStatusWorker(state, project, resolvedPath); + buildPerformance.mark("afterUpToDateCheckTime"); + buildPerformance.measure("Up-to-date check time", "beforeUpToDateCheckTime", "afterUpToDateCheckTime"); state.projectStatus.set(resolvedPath, actual); return actual; } @@ -1917,7 +1924,7 @@ namespace ts { } break; } - // falls through + // falls through case UpToDateStatusType.UpToDateWithInputFileText: case UpToDateStatusType.UpToDateWithUpstreamTypes: @@ -1945,6 +1952,14 @@ namespace ts { } function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers, onlyReferences?: boolean): ExitStatus { + buildPerformance.mark("beforeBuild"); + const result = buildWorker(state, project, cancellationToken, writeFile, getCustomTransformers, onlyReferences); + buildPerformance.mark("afterBuild"); + buildPerformance.measure("Build", "beforeBuild", "afterBuild"); + return result; + } + + function buildWorker(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers, onlyReferences?: boolean): ExitStatus { const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; @@ -1973,7 +1988,15 @@ namespace ts { : ExitStatus.DiagnosticsPresent_OutputsSkipped; } - function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean): ExitStatus { + buildPerformance.mark("beforeClean"); + const result = cleanWorker(state, project, onlyReferences); + buildPerformance.mark("afterClean"); + buildPerformance.measure("Clean", "beforeClean", "afterClean"); + return result; + } + + function cleanWorker(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { const buildOrder = getBuildOrderFor(state, project, onlyReferences); if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; @@ -2050,6 +2073,14 @@ namespace ts { } function buildNextInvalidatedProject(state: SolutionBuilderState, changeDetected: boolean) { + buildPerformance.mark("beforeBuild"); + const buildOrder = buildNextInvalidatedProjectWorker(state, changeDetected); + buildPerformance.mark("afterBuild"); + buildPerformance.measure("Build", "beforeBuild", "afterBuild"); + if (buildOrder) reportErrorSummary(state, buildOrder); + } + + function buildNextInvalidatedProjectWorker(state: SolutionBuilderState, changeDetected: boolean) { state.timerToBuildInvalidatedProject = undefined; if (state.reportFileChangeDetected) { state.reportFileChangeDetected = false; @@ -2079,7 +2110,7 @@ namespace ts { } } disableCache(state); - reportErrorSummary(state, buildOrder); + return buildOrder; } function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { @@ -2186,6 +2217,7 @@ namespace ts { function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { if (!state.watchAllProjectsPending) return; + buildPerformance.mark("beforeWatcherCreation"); state.watchAllProjectsPending = false; for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { const resolvedPath = toResolvedConfigFilePath(state, resolved); @@ -2204,6 +2236,8 @@ namespace ts { watchPackageJsonFiles(state, resolved, resolvedPath, cfg); } } + buildPerformance.mark("afterWatcherCreation"); + buildPerformance.measure("Watcher creation", "beforeWatcherCreation", "afterWatcherCreation"); } function stopWatching(state: SolutionBuilderState) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index df2179cba7701..eafe8c83673fc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3866,6 +3866,7 @@ namespace ts { export interface WriteFileCallbackData { /*@internal*/ sourceMapUrlPos?: number; /*@internal*/ buildInfo?: BuildInfo; + /*@internal*/ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; } export type WriteFileCallback = ( fileName: string, @@ -3974,6 +3975,19 @@ namespace ts { /*@internal*/ export type FilePreprocessingDiagnostics = FilePreprocessingReferencedDiagnostic | FilePreprocessingFileExplainingDiagnostic; + /*@internal*/ + export interface ForceDtsEmitResult { + diagnostics: readonly DiagnosticWithLocation[] | undefined; + text: string; + sourceMap: string | undefined; + sourceMapData: SourceMapEmitResult | undefined; + sourceMapUrlPos: number | undefined; + exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; + } + + /*@internal*/ + export type CacheDtsEmitResult = ForceDtsEmitResult | TransformationResult; + export interface Program extends ScriptReferenceHost { getCurrentDirectory(): string; /** @@ -4175,7 +4189,6 @@ namespace ts { diagnostics: readonly Diagnostic[]; emittedFiles?: string[]; // Array of files the compiler wrote to disk /* @internal */ sourceMaps?: SourceMapEmitResult[]; // Array of sourceMapData if compiler emitted sourcemaps - /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; } /* @internal */ @@ -7126,6 +7139,7 @@ namespace ts { getSourceFileFromReference: Program["getSourceFileFromReference"]; readonly redirectTargetsMap: RedirectTargetsMap; createHash?(data: string): string; + getDtsEmitResultCache(): ESMap; } /* @internal */ diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 68d2e51e9d4eb..1034ebe0bfa98 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -1,9 +1,4 @@ namespace ts { - interface Statistic { - name: string; - value: string; - } - function countLines(program: Program): Map { const counts = getCountsMap(); forEach(program.getSourceFiles(), file => { @@ -14,15 +9,6 @@ namespace ts { return counts; } - function countNodes(program: Program): Map { - const counts = getCountsMap(); - forEach(program.getSourceFiles(), file => { - const key = getCountKey(program, file); - counts.set(key, counts.get(key)! + file.nodeCount); - }); - return counts; - } - function getCountsMap() { const counts = new Map(); counts.set("Library", 0); @@ -751,9 +737,22 @@ namespace ts { createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)), createWatchStatusReporter(sys, buildOptions) ); + const onWatchStatusChange = buildHost.onWatchStatusChange; + let reportWatchStatistics = false; + buildHost.onWatchStatusChange = (d, newLine, options, errorCount) => { + onWatchStatusChange?.(d, newLine, options, errorCount); + if (!reportWatchStatistics) return; + if (d.code === Diagnostics.Found_0_errors_Watching_for_file_changes.code || + d.code === Diagnostics.Found_1_error_Watching_for_file_changes.code) { + reportSolutionBuilderTimes(sys, buildOptions, builder); + } + }; updateSolutionBuilderHost(sys, cb, buildHost); + enableSolutionPerformance(sys, buildOptions); const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions, watchOptions); builder.build(); + reportSolutionBuilderTimes(sys, buildOptions, builder); + reportWatchStatistics = true; return builder; } @@ -765,12 +764,38 @@ namespace ts { createReportErrorSummary(sys, buildOptions) ); updateSolutionBuilderHost(sys, cb, buildHost); + enableSolutionPerformance(sys, buildOptions); const builder = createSolutionBuilder(buildHost, projects, buildOptions); const exitStatus = buildOptions.clean ? builder.clean() : builder.build(); + reportSolutionBuilderTimes(sys, buildOptions, builder); dumpTracingLegend(); // Will no-op if there hasn't been any tracing return sys.exit(exitStatus); } + function enableSolutionPerformance(system: System, options: BuildOptions) { + if (system === sys && options.extendedDiagnostics) buildPerformance.enable(); + } + + function reportSolutionBuilderTimes(system: System, buildOptions: BuildOptions, builder: SolutionBuilder) { + if (system !== sys || !buildOptions.extendedDiagnostics) return; + + if (!buildPerformance.isEnabled()) { + sys.write(Diagnostics.Performance_timings_for_diagnostics_or_extendedDiagnostics_are_not_available_in_this_session_A_native_implementation_of_the_Web_Performance_API_could_not_be_found.message + "\n"); + return; + } + + const statistics: Statistic[] = []; + statistics.push( + { name: "Projects in scope", value: getBuildOrderFromAnyBuildOrder(builder.getBuildOrder()).length, type: StatisticType.count }, + ); + buildPerformance.forEachCount((count, name) => statistics.push({ name, value: count, type: StatisticType.count })); + buildPerformance.forEachStatistics(s => statistics.push(s)); + buildPerformance.forEachMeasure((duration, name) => statistics.push({ name: `${name} time`, value: duration, type: StatisticType.time })); + buildPerformance.disable(); + buildPerformance.enable(); + reportAllStatistics(system, statistics); + } + function createReportErrorSummary(sys: System, options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined { return shouldBePretty(sys, options) ? (errorCount, filesInError) => sys.write(getErrorSummaryText(errorCount, filesInError, sys.newLine, sys)) : @@ -840,7 +865,7 @@ namespace ts { cb: ExecuteCommandLineCallbacks, buildHost: SolutionBuilderHostBase ) { - updateCreateProgram(sys, buildHost); + updateCreateProgram(sys, buildHost, /*isBuildMode*/ true); buildHost.afterProgramEmitAndDiagnostics = program => { reportStatistics(sys, program.getProgram()); cb(program); @@ -848,12 +873,12 @@ namespace ts { buildHost.afterEmitBundle = cb; } - function updateCreateProgram(sys: System, host: { createProgram: CreateProgram; }) { + function updateCreateProgram(sys: System, host: { createProgram: CreateProgram; }, isBuildMode: boolean) { const compileUsingBuilder = host.createProgram; host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => { Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram)); if (options !== undefined) { - enableStatisticsAndTracing(sys, options, /*isBuildMode*/ true); + enableStatisticsAndTracing(sys, options, isBuildMode); } return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); }; @@ -864,7 +889,7 @@ namespace ts { cb: ExecuteCommandLineCallbacks, watchCompilerHost: WatchCompilerHost, ) { - updateCreateProgram(sys, watchCompilerHost); + updateCreateProgram(sys, watchCompilerHost, /*isBuildMode*/ false); const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217 watchCompilerHost.afterProgramCreate = builderProgram => { emitFilesUsingBuilder(builderProgram); @@ -953,18 +978,13 @@ namespace ts { reportCountStatistic("Files", program.getSourceFiles().length); const lineCounts = countLines(program); - const nodeCounts = countNodes(program); if (compilerOptions.extendedDiagnostics) { for (const key of arrayFrom(lineCounts.keys())) { reportCountStatistic("Lines of " + key, lineCounts.get(key)!); } - for (const key of arrayFrom(nodeCounts.keys())) { - reportCountStatistic("Nodes of " + key, nodeCounts.get(key)!); - } } else { reportCountStatistic("Lines", reduceLeftIterator(lineCounts.values(), (sum, count) => sum + count, 0)); - reportCountStatistic("Nodes", reduceLeftIterator(nodeCounts.values(), (sum, count) => sum + count, 0)); } reportCountStatistic("Identifiers", program.getIdentifierCount()); @@ -973,7 +993,7 @@ namespace ts { reportCountStatistic("Instantiations", program.getInstantiationCount()); if (memoryUsed >= 0) { - reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K"); + reportMemoryStatistic("Memory used", memoryUsed); } const isPerformanceEnabled = performance.isEnabled(); @@ -988,7 +1008,7 @@ namespace ts { reportCountStatistic("Subtype cache size", caches.subtype); reportCountStatistic("Strict subtype cache size", caches.strictSubtype); if (isPerformanceEnabled) { - performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration)); + performance.forEachMeasure((duration, name) => reportTimeStatistic(`${name} time`, duration, /*aggregate*/ true)); } } else if (isPerformanceEnabled) { @@ -996,17 +1016,17 @@ 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", performance.getDuration("I/O Read")); - reportTimeStatistic("I/O write", performance.getDuration("I/O Write")); - reportTimeStatistic("Parse time", programTime); - reportTimeStatistic("Bind time", bindTime); - reportTimeStatistic("Check time", checkTime); - reportTimeStatistic("Emit time", emitTime); + reportTimeStatistic("I/O read", performance.getDuration("I/O Read"), /*aggregate*/ true); + reportTimeStatistic("I/O write", performance.getDuration("I/O Write"), /*aggregate*/ true); + reportTimeStatistic("Parse time", programTime, /*aggregate*/ true); + reportTimeStatistic("Bind time", bindTime, /*aggregate*/ true); + reportTimeStatistic("Check time", checkTime, /*aggregate*/ true); + reportTimeStatistic("Emit time", emitTime, /*aggregate*/ true); } if (isPerformanceEnabled) { - reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime); + reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime, /*aggregate*/ false); } - reportStatistics(); + reportAllStatistics(sys, statistics); if (!isPerformanceEnabled) { sys.write(Diagnostics.Performance_timings_for_diagnostics_or_extendedDiagnostics_are_not_available_in_this_session_A_native_implementation_of_the_Web_Performance_API_could_not_be_found.message + "\n"); } @@ -1015,34 +1035,53 @@ namespace ts { } } - function reportStatistics() { - let nameSize = 0; - let valueSize = 0; - for (const { name, value } of statistics) { - if (name.length > nameSize) { - nameSize = name.length; - } + function reportStatisticalValue(s: Statistic, aggregate: boolean) { + statistics.push(s); + if (aggregate) buildPerformance.addStatistics({ ...s, name: `Aggregate ${s.name}` }); + } - if (value.length > valueSize) { - valueSize = value.length; - } - } + function reportMemoryStatistic(name: string, memoryUsed: number) { + reportStatisticalValue({ name, value: memoryUsed, type: StatisticType.memory }, /*aggregate*/ true); + } - for (const { name, value } of statistics) { - sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine); - } + function reportCountStatistic(name: string, count: number) { + reportStatisticalValue({ name, value: count, type: StatisticType.count }, /*aggregate*/ true); } - function reportStatisticalValue(name: string, value: string) { - statistics.push({ name, value }); + function reportTimeStatistic(name: string, time: number, aggregate: boolean) { + reportStatisticalValue({ name, value: time, type: StatisticType.time }, aggregate); } + } - function reportCountStatistic(name: string, count: number) { - reportStatisticalValue(name, "" + count); + function reportAllStatistics(sys: System, statistics: Statistic[]) { + let nameSize = 0; + let valueSize = 0; + for (const s of statistics) { + if (s.name.length > nameSize) { + nameSize = s.name.length; + } + + const valueString = statisticValue(s); + if (valueString.length > valueSize) { + valueSize = valueString.length; + } + } + + for (const s of statistics) { + sys.write(padRight(s.name + ":", nameSize + 2) + padLeft(statisticValue(s).toString(), valueSize) + sys.newLine); } + } - function reportTimeStatistic(name: string, time: number) { - reportStatisticalValue(name, (time / 1000).toFixed(2) + "s"); + function statisticValue(s: Statistic) { + switch (s.type) { + case StatisticType.count: + return "" + s.value; + case StatisticType.time: + return (s.value / 1000).toFixed(2) + "s"; + case StatisticType.memory: + return Math.round(s.value / 1000) + "K"; + default: + Debug.assertNever(s.type); } } diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index c3f5f019541af..b93215f4df2cc 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -62,7 +62,6 @@ export function Component(x: Config): any;` emitSkipped: true, diagnostics: emptyArray, outputFiles: emptyArray, - exportedModulesFromDeclarationEmit: undefined } ); @@ -80,7 +79,6 @@ export function Component(x: Config): any;` text: "export {};\r\n", writeByteOrderMark: false }], - exportedModulesFromDeclarationEmit: undefined } ); }); diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts index 97132be83b75d..8ea94a596c8a8 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -139,7 +139,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -175,7 +175,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -218,7 +218,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -261,7 +261,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -315,7 +315,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -369,7 +369,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -412,7 +412,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -455,7 +455,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -509,7 +509,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -563,7 +563,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -597,7 +597,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -628,7 +628,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -666,7 +666,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -704,7 +704,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -753,7 +753,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -802,7 +802,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -840,7 +840,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -878,7 +878,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -927,7 +927,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -976,7 +976,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1017,7 +1017,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -1053,7 +1053,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1107,7 +1107,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -1161,7 +1161,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1215,7 +1215,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -1269,7 +1269,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1323,7 +1323,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -1377,7 +1377,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1431,7 +1431,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path } }).response as EmitOutput; @@ -1485,7 +1485,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: usageTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1519,7 +1519,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1568,7 +1568,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1617,7 +1617,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1666,7 +1666,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1715,7 +1715,7 @@ ${appendDts}` assert.equal(host.writtenFiles.size, 0); // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: usageConfig.path } }).response as EmitOutput; @@ -1755,7 +1755,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -1791,7 +1791,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } }).response as EmitOutput; @@ -1846,7 +1846,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -1900,7 +1900,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } }).response as EmitOutput; @@ -1955,7 +1955,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -2009,7 +2009,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } }).response as EmitOutput; @@ -2064,7 +2064,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -2118,7 +2118,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } }).response as EmitOutput; @@ -2173,7 +2173,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path } }).response as EmitOutput; @@ -2227,7 +2227,7 @@ ${appendDts}` } // Verify EmitOutput - const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq({ + const actualEmitOutput = session.executeCommandSeq({ command: protocol.CommandTypes.EmitOutput, arguments: { file: dependencyTs.path, projectFileName: dependencyConfig.path } }).response as EmitOutput;