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:
		@@ -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
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user