Created
September 23, 2025 09:16
-
-
Save aztack/658eefee73ee4231c8102a84fce0c947 to your computer and use it in GitHub Desktop.
TypeScript Error Reporter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ts-node | |
| /* eslint-disable @typescript-eslint/no-non-null-assertion */ | |
| /* eslint-disable curly */ | |
| /* eslint-disable @typescript-eslint/no-use-before-define */ | |
| /* | |
| * Custom type-check runner | |
| * | |
| * Usage examples: | |
| * npx ts-node scripts/custom-type-check.ts \ | |
| * --config packages/pivot-services/tsconfig.check-type.json \ | |
| * --exclude-path ../../.eden-mono/temp/node_modules \ | |
| * --exclude-path dist | |
| * | |
| * Flags: | |
| * --config <path> Path to a tsconfig.json file (defaults to ./tsconfig.json) | |
| * --exclude-path <path> Repeated flag – diagnostics whose fileName contains this | |
| * substring will be dropped. | |
| * --include-path <path> Repeated flag – if provided, only diagnostics whose | |
| * fileName contains at least one of these substrings are kept. | |
| * | |
| * Exit status is 0 when no diagnostics remain after filtering, otherwise 1. | |
| */ | |
| import path from "path" | |
| import ts from "typescript" | |
| /* -------------------------------------------------------- | |
| * CLI argument parsing (minimal – avoids extra dependencies) | |
| * -------------------------------------------------------- */ | |
| interface CliOptions { | |
| configPath: string | |
| exclude: string[] | |
| include: string[] | |
| excludeErrors: number[] | |
| includeErrors: number[] | |
| } | |
| function parseArgs(argv: string[]): CliOptions { | |
| const opts: CliOptions = { | |
| configPath: "tsconfig.json", | |
| exclude: [], | |
| include: [], | |
| excludeErrors: [], | |
| includeErrors: [], | |
| } | |
| const takeValue = (i: number, arr: string[]): [string, number] => { | |
| // Helper to support --flag=value or --flag value | |
| const raw = arr[i] | |
| if (!raw) throw new Error(`${raw} requires a value`) | |
| const eq = raw.indexOf("=") | |
| if (eq !== -1) { | |
| return [raw.slice(eq + 1), i] | |
| } else { | |
| if (i + 1 >= arr.length) throw new Error(`${raw} requires a value`) | |
| return [arr[i + 1]!, i + 1] | |
| } | |
| } | |
| for (let i = 0; i < argv.length; i++) { | |
| const arg = argv[i] | |
| if (!arg) continue | |
| switch (true) { | |
| case arg === "--config" || arg.startsWith("--config="): { | |
| const [val, newIdx] = takeValue(i, argv) | |
| opts.configPath = val | |
| i = newIdx | |
| break | |
| } | |
| case arg === "--exclude-path" || arg.startsWith("--exclude-path="): { | |
| const [val, newIdx] = takeValue(i, argv) | |
| opts.exclude.push(val) | |
| i = newIdx | |
| break | |
| } | |
| case arg === "--include-path" || arg.startsWith("--include-path="): { | |
| const [val, newIdx] = takeValue(i, argv) | |
| opts.include.push(val) | |
| i = newIdx | |
| break | |
| } | |
| case arg === "--exclude-errors" || arg.startsWith("--exclude-errors="): { | |
| const [val, newIdx] = takeValue(i, argv) | |
| opts.excludeErrors.push( | |
| ...val | |
| .split(",") | |
| .map((s) => Number(s.trim())) | |
| .filter((n) => !Number.isNaN(n)), | |
| ) | |
| i = newIdx | |
| break | |
| } | |
| case arg === "--include-errors" || arg.startsWith("--include-errors="): { | |
| const [val, newIdx] = takeValue(i, argv) | |
| opts.includeErrors.push( | |
| ...val | |
| .split(",") | |
| .map((s) => Number(s.trim())) | |
| .filter((n) => !Number.isNaN(n)), | |
| ) | |
| i = newIdx | |
| break | |
| } | |
| default: { | |
| console.warn(`Unknown argument: ${arg}`) | |
| } | |
| } | |
| } | |
| return opts | |
| } | |
| /* -------------------------------------------------------- | |
| * Diagnostic filtering helpers | |
| * -------------------------------------------------------- */ | |
| function shouldKeepDiagnostic( | |
| d: ts.Diagnostic, | |
| { | |
| include, | |
| exclude, | |
| includeErrors, | |
| excludeErrors, | |
| }: { | |
| include: string[] | |
| exclude: string[] | |
| includeErrors: number[] | |
| excludeErrors: number[] | |
| }, | |
| ): boolean { | |
| // Filter by error codes first | |
| if (excludeErrors.includes(d.code)) return false | |
| if (includeErrors.length > 0 && !includeErrors.includes(d.code)) return false | |
| // Diagnostics without a specific file (e.g. config errors) are accepted after code filtering | |
| if (!d.file) return true | |
| const fileName = path.resolve(d.file.fileName) | |
| // Exclude path filter (supports regex wrapped in / / as before) | |
| const matchesExclude = exclude.some((ex) => { | |
| if (ex.startsWith("/") && ex.endsWith("/")) { | |
| const regex = new RegExp(ex.slice(1, -1)) | |
| return regex.test(fileName) | |
| } | |
| return fileName.includes(ex) | |
| }) | |
| if (matchesExclude) return false | |
| // Include path filter | |
| if (include.length > 0 && !include.some((inc) => fileName.includes(inc))) return false | |
| return true | |
| } | |
| /* -------------------------------------------------------- | |
| * Main execution flow | |
| * -------------------------------------------------------- */ | |
| function run() { | |
| const { configPath, exclude, include, excludeErrors, includeErrors } = parseArgs(process.argv.slice(2)) | |
| const resolvedConfigPath = path.resolve(configPath) | |
| const configFile = ts.readConfigFile(resolvedConfigPath, ts.sys.readFile) | |
| if (configFile.error) { | |
| reportAndExit([configFile.error]) | |
| } | |
| const parsedCmd = ts.parseJsonConfigFileContent( | |
| configFile.config, | |
| ts.sys, | |
| path.dirname(resolvedConfigPath), | |
| /* existingOptions */ {}, | |
| resolvedConfigPath, | |
| ) | |
| const program = ts.createProgram({ | |
| rootNames: parsedCmd.fileNames, | |
| options: parsedCmd.options, | |
| }) | |
| const diagnostics: ts.Diagnostic[] = [ | |
| ...program.getOptionsDiagnostics(), | |
| ...program.getGlobalDiagnostics(), | |
| ...program.getSyntacticDiagnostics(), | |
| ...program.getSemanticDiagnostics(), | |
| ] | |
| const filtered = diagnostics.filter((d) => | |
| shouldKeepDiagnostic(d, { | |
| include, | |
| exclude, | |
| includeErrors, | |
| excludeErrors, | |
| }), | |
| ) | |
| reportAndExit(filtered) | |
| } | |
| function reportAndExit(diags: ts.Diagnostic[]): void { | |
| const formatHost: ts.FormatDiagnosticsHost = { | |
| getCanonicalFileName: (f) => f, | |
| getCurrentDirectory: ts.sys.getCurrentDirectory, | |
| getNewLine: () => ts.sys.newLine, | |
| } | |
| if (diags.length > 0) { | |
| console.error(ts.formatDiagnosticsWithColorAndContext(diags, formatHost)) | |
| console.error(`\nType checking failed with ${diags.length} error(s).`) | |
| process.exit(1) | |
| } else { | |
| console.log("Type checking passed with no errors.") | |
| } | |
| } | |
| run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment