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:
		| @@ -1,5 +1,13 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-05-21 - 2.5.2 - fix(tsbuild) | ||||
| Improve diagnostic error handling and summary reporting for TypeScript compilation by refactoring diagnostic processing and adding pre-emit error checks. | ||||
|  | ||||
| - Introduce a dedicated processDiagnostics function that categorizes errors by file and computes error totals. | ||||
| - Refactor displayErrorSummary to provide clearer, color-coded output of error details. | ||||
| - Add pre-emit error checking in compileWithErrorTracking to prevent emission when errors exist. | ||||
| - Consolidate error summary merging in compileFileArrayWithErrorTracking for improved reporting. | ||||
|  | ||||
| ## 2025-05-15 - 2.5.1 - fix(commitinfo) | ||||
| Update commit information and metadata to synchronize release data | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tsbuild', | ||||
|   version: '2.5.1', | ||||
|   version: '2.5.2', | ||||
|   description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.' | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|    */ | ||||
|   | ||||
| @@ -6,6 +6,19 @@ export type { CompilerOptions, ScriptTarget, ModuleKind }; | ||||
|  | ||||
| export * from './tsbuild.classes.tsbuild.js'; | ||||
|  | ||||
| /** | ||||
|  * compile an array of absolute file paths with error tracking | ||||
|  */ | ||||
| export let compileFileArrayWithErrorTracking = async ( | ||||
|   fileStringArrayArg: string[], | ||||
|   compilerOptionsArg: CompilerOptions = {}, | ||||
|   argvArg?: any | ||||
| ): Promise<{ emittedFiles: any[], errorSummary: import('./tsbuild.classes.tsbuild.js').IErrorSummary }> => { | ||||
|   const { TsBuild } = await import('./tsbuild.classes.tsbuild.js'); | ||||
|   const tsBuild = new TsBuild(fileStringArrayArg, compilerOptionsArg, argvArg); | ||||
|   return tsBuild.compileWithErrorTracking(); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * compile am array of absolute file paths | ||||
|  */ | ||||
| @@ -17,6 +30,75 @@ export let compileFileArray = ( | ||||
|   return compiler(fileStringArrayArg, mergeCompilerOptions(compilerOptionsArg, argvArg), argvArg); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Helper function to merge error summaries | ||||
|  */ | ||||
| function mergeErrorSummaries(summaries: import('./tsbuild.classes.tsbuild.js').IErrorSummary[]): import('./tsbuild.classes.tsbuild.js').IErrorSummary { | ||||
|   const mergedErrorsByFile: Record<string, plugins.typescript.Diagnostic[]> = {}; | ||||
|   const mergedGeneralErrors: plugins.typescript.Diagnostic[] = []; | ||||
|   let totalErrors = 0; | ||||
|    | ||||
|   summaries.forEach(summary => { | ||||
|     // Merge errors by file | ||||
|     Object.entries(summary.errorsByFile).forEach(([fileName, errors]) => { | ||||
|       if (!mergedErrorsByFile[fileName]) { | ||||
|         mergedErrorsByFile[fileName] = []; | ||||
|       } | ||||
|       mergedErrorsByFile[fileName] = mergedErrorsByFile[fileName].concat(errors); | ||||
|     }); | ||||
|      | ||||
|     // Merge general errors | ||||
|     mergedGeneralErrors.push(...summary.generalErrors); | ||||
|     totalErrors += summary.totalErrors; | ||||
|   }); | ||||
|    | ||||
|   return { | ||||
|     errorsByFile: mergedErrorsByFile, | ||||
|     generalErrors: mergedGeneralErrors, | ||||
|     totalErrors, | ||||
|     totalFiles: Object.keys(mergedErrorsByFile).length | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Helper function to display final compilation summary | ||||
|  */ | ||||
| function displayFinalErrorSummary(errorSummary: import('./tsbuild.classes.tsbuild.js').IErrorSummary): void { | ||||
|   if (errorSummary.totalErrors === 0) { | ||||
|     console.log('\n📊 \x1b[32mCompilation Summary: All tasks completed successfully! ✅\x1b[0m\n'); | ||||
|     return; | ||||
|   } | ||||
|    | ||||
|   const colors = { | ||||
|     reset: '\x1b[0m', | ||||
|     red: '\x1b[31m', | ||||
|     yellow: '\x1b[33m', | ||||
|     cyan: '\x1b[36m', | ||||
|     brightRed: '\x1b[91m', | ||||
|     brightYellow: '\x1b[93m' | ||||
|   }; | ||||
|    | ||||
|   console.log('\n' + '='.repeat(80)); | ||||
|   console.log(`📊 ${colors.brightYellow}Final Compilation Summary${colors.reset}`); | ||||
|   console.log('='.repeat(80)); | ||||
|    | ||||
|   if (errorSummary.totalFiles > 0) { | ||||
|     console.log(`${colors.brightRed}❌ Files with errors (${errorSummary.totalFiles}):${colors.reset}`); | ||||
|      | ||||
|     Object.entries(errorSummary.errorsByFile).forEach(([fileName, errors]) => { | ||||
|       const displayPath = fileName.replace(process.cwd(), '').replace(/^\//, ''); | ||||
|       console.log(`  ${colors.red}•${colors.reset} ${colors.cyan}${displayPath}${colors.reset} ${colors.yellow}(${errors.length} error${errors.length !== 1 ? 's' : ''})${colors.reset}`); | ||||
|     }); | ||||
|   } | ||||
|    | ||||
|   if (errorSummary.generalErrors.length > 0) { | ||||
|     console.log(`${colors.brightRed}❌ General errors: ${errorSummary.generalErrors.length}${colors.reset}`); | ||||
|   } | ||||
|    | ||||
|   console.log(`\n${colors.brightRed}Total: ${errorSummary.totalErrors} error${errorSummary.totalErrors !== 1 ? 's' : ''} across ${errorSummary.totalFiles} file${errorSummary.totalFiles !== 1 ? 's' : ''}${colors.reset}`); | ||||
|   console.log('='.repeat(80) + '\n'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * compile advanced glob configurations | ||||
|  * @param globStringArrayArg a array of glob strings | ||||
| @@ -31,6 +113,7 @@ export let compileGlobStringObject = async ( | ||||
|   argvArg?: any | ||||
| ) => { | ||||
|   let compiledFiles: any[] = []; | ||||
|   const errorSummaries: import('./tsbuild.classes.tsbuild.js').IErrorSummary[] = []; | ||||
|    | ||||
|   // Log the compilation tasks in a nice format | ||||
|   console.log('\n👷 TypeScript Compilation Tasks:'); | ||||
| @@ -69,12 +152,16 @@ export let compileGlobStringObject = async ( | ||||
|         outDir: destDir, | ||||
|       }; | ||||
|        | ||||
|       // Compile the files and correctly concat the results | ||||
|       // Fixed: removed duplicating compiledFiles in the concat operation | ||||
|       const newlyCompiledFiles = await compileFileArray(absoluteFilePathArray, updatedTsOptions, argvArg); | ||||
|       compiledFiles = compiledFiles.concat(newlyCompiledFiles); | ||||
|       // Compile with error tracking | ||||
|       const result = await compileFileArrayWithErrorTracking(absoluteFilePathArray, updatedTsOptions, argvArg); | ||||
|       compiledFiles = compiledFiles.concat(result.emittedFiles); | ||||
|       errorSummaries.push(result.errorSummary); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Display final error summary after all compilation tasks | ||||
|   const finalErrorSummary = mergeErrorSummaries(errorSummaries); | ||||
|   displayFinalErrorSummary(finalErrorSummary); | ||||
|    | ||||
|   return compiledFiles; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user