fix(tsbuild): Improve diagnostic error handling and summary reporting for TypeScript compilation by refactoring diagnostic processing and adding pre-emit error checks.

This commit is contained in:
2025-05-21 00:20:45 +00:00
parent 88c0601c03
commit 0909fa306a
4 changed files with 206 additions and 18 deletions

View File

@@ -3,6 +3,16 @@ import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type { CompilerOptions, ScriptTarget, ModuleKind } from './tsbuild.exports.js';
/**
* Interface for error summary data
*/
export interface IErrorSummary {
errorsByFile: Record<string, plugins.typescript.Diagnostic[]>;
generalErrors: plugins.typescript.Diagnostic[];
totalErrors: number;
totalFiles: number;
}
/**
* Default compiler options for TypeScript compilation
*/
@@ -166,14 +176,9 @@ export class TsBuild {
}
/**
* Helper function to handle and log TypeScript diagnostics
* Helper function to process TypeScript diagnostics and return error summary
*/
private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean {
if (diagnostics.length === 0) {
return false;
}
// Group errors by file for better readability
private processDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): IErrorSummary {
const errorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {};
const generalErrors: plugins.typescript.Diagnostic[] = [];
@@ -190,12 +195,27 @@ export class TsBuild {
}
});
// Print error summary header
const totalErrorCount = diagnostics.length;
const fileCount = Object.keys(errorsByFile).length;
return {
errorsByFile,
generalErrors,
totalErrors: diagnostics.length,
totalFiles: Object.keys(errorsByFile).length
};
}
/**
* Helper function to display error summary
*/
private displayErrorSummary(errorSummary: IErrorSummary): void {
if (errorSummary.totalErrors === 0) {
return;
}
const { errorsByFile, generalErrors, totalErrors, totalFiles } = errorSummary;
// Print error summary header
console.log('\n' + '='.repeat(80));
console.log(`❌ Found ${totalErrorCount} error${totalErrorCount !== 1 ? 's' : ''} in ${fileCount} file${fileCount !== 1 ? 's' : ''}:`);
console.log(`❌ Found ${totalErrors} error${totalErrors !== 1 ? 's' : ''} in ${totalFiles} file${totalFiles !== 1 ? 's' : ''}:`);
console.log('='.repeat(80));
// Color codes for error formatting
@@ -255,8 +275,15 @@ export class TsBuild {
}
console.log('\n' + '='.repeat(80) + '\n');
return diagnostics.length > 0;
}
/**
* Helper function to handle and log TypeScript diagnostics (legacy method)
*/
private handleDiagnostics(diagnostics: readonly plugins.typescript.Diagnostic[]): boolean {
const errorSummary = this.processDiagnostics(diagnostics);
this.displayErrorSummary(errorSummary);
return errorSummary.totalErrors > 0;
}
/**
@@ -282,6 +309,72 @@ export class TsBuild {
this.options = { ...this.options, ...options };
}
/**
* The main compiler function that compiles the files and returns error summary
*/
public async compileWithErrorTracking(): Promise<{ emittedFiles: any[], errorSummary: IErrorSummary }> {
if (this.options.skipLibCheck) {
console.log('\n⚠ WARNING ⚠️');
console.log('You are skipping libcheck... Is that really wanted?');
console.log('Continuing in 5 seconds...\n');
await plugins.smartdelay.delayFor(5000);
}
console.log(`🔨 Compiling ${this.fileNames.length} files...`);
const done = plugins.smartpromise.defer<{ emittedFiles: any[], errorSummary: IErrorSummary }>();
const program = this.createProgram();
// Check for pre-emit diagnostics first
const preEmitDiagnostics = plugins.typescript.getPreEmitDiagnostics(program);
const preEmitErrorSummary = this.processDiagnostics(preEmitDiagnostics);
// Only continue to emit phase if no pre-emit errors
if (preEmitErrorSummary.totalErrors > 0) {
this.displayErrorSummary(preEmitErrorSummary);
console.error('\n❌ TypeScript pre-emit checks failed. Please fix the issues listed above before proceeding.');
console.error(' Type errors must be resolved before the compiler can emit output files.\n');
// Return error summary instead of exiting to allow final summary display
done.resolve({ emittedFiles: [], errorSummary: preEmitErrorSummary });
return done.promise;
}
// If no pre-emit errors, proceed with emit
const emitResult = program.emit();
const emitErrorSummary = this.processDiagnostics(emitResult.diagnostics);
// Combine error summaries
const combinedErrorSummary: IErrorSummary = {
errorsByFile: { ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile },
generalErrors: [...preEmitErrorSummary.generalErrors, ...emitErrorSummary.generalErrors],
totalErrors: preEmitErrorSummary.totalErrors + emitErrorSummary.totalErrors,
totalFiles: Object.keys({ ...preEmitErrorSummary.errorsByFile, ...emitErrorSummary.errorsByFile }).length
};
const exitCode = emitResult.emitSkipped ? 1 : 0;
if (exitCode === 0) {
console.log('\n✅ TypeScript emit succeeded!');
// Get count of emitted files by type
const jsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.js')).length || 0;
const dtsFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.d.ts')).length || 0;
const mapFiles = emitResult.emittedFiles?.filter(f => f.endsWith('.map')).length || 0;
// If we have emitted files, show a summary
if (emitResult.emittedFiles && emitResult.emittedFiles.length > 0) {
console.log(` Generated ${emitResult.emittedFiles.length} files: ${jsFiles} .js, ${dtsFiles} .d.ts, ${mapFiles} source maps`);
}
done.resolve({ emittedFiles: emitResult.emittedFiles || [], errorSummary: combinedErrorSummary });
} else {
this.displayErrorSummary(combinedErrorSummary);
console.error('\n❌ TypeScript emit failed. Please investigate the errors listed above!');
console.error(' No output files have been generated.\n');
process.exit(exitCode);
}
return done.promise;
}
/**
* The main compiler function that compiles the files
*/