Skip to content

Instantly share code, notes, and snippets.

@lifeart
Last active June 29, 2023 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lifeart/5168d2e4c98068525db3182c36fdf063 to your computer and use it in GitHub Desktop.
Save lifeart/5168d2e4c98068525db3182c36fdf063 to your computer and use it in GitHub Desktop.
Typescript custom diagnostics
const ts = require('typescript');
const fs = require('fs');
const path = require('path');
function getFileInfo(file, start) {
const { fileName } = file;
const { line, character } = file.getLineAndCharacterOfPosition(start);
return { fileName, line: line + 1, character: character + 1 };
}
function reportDiagnostics(diagnostics) {
const errors = diagnostics.map(diagnostic => {
const { file, messageText, start } = diagnostic;
if (file) {
const { fileName, line, character } = getFileInfo(file, start);
return `Error ${fileName} (${line},${character}): ${ts.flattenDiagnosticMessageText(messageText, '\n')}`;
} else {
return `Error: ${ts.flattenDiagnosticMessageText(messageText, '\n')}`;
}
});
console.error(errors.join('\n'));
}
function readConfigFile(configFileName) {
const configFileText = fs.readFileSync(configFileName).toString();
const result = ts.parseConfigFileTextToJson(configFileName, configFileText);
return ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(configFileName));
}
function compile(configFileName) {
const config = readConfigFile(configFileName);
const program = ts.createProgram(config.fileNames, config.options);
const emitResult = program.emit();
const results = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
reportDiagnostics(results);
console.warn('Compilation completed');
process.exit(0);
}
compile('jsconfig.json');
// https://github.com/microsoft/TypeScript-wiki/blob/main/Using-the-Compiler-API.md
const ts = require('typescript');
const fs = require('fs');
const path = require('path');
const MAX_ERRORS = 411;
const MAX_FILES_WITH_ERRORS = 109;
/**
*
* @param {Array<import('typescript').Diagnostic>} diagnostics
*/
function reportDiagnostics(diagnostics) {
const files = {};
const generalErrors = [];
diagnostics.forEach(diagnostic => {
if (diagnostic.file) {
const { fileName } = diagnostic.file;
if (fileName.includes('bower_components') || fileName.includes('polyfills') || fileName.includes('vendor')) {
return;
}
}
let message = 'Error';
if (diagnostic.file) {
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
message += ` ${diagnostic.file.fileName} (${line + 1},${character + 1})`;
message += ': ' + ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (!(diagnostic.file.fileName in files)) {
files[diagnostic.file.fileName] = [];
files[diagnostic.file.fileName]._line = line + 1;
files[diagnostic.file.fileName]._character = character + 1;
}
files[diagnostic.file.fileName].push(message);
} else {
message += ': ' + ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
generalErrors.push(message);
}
});
if (generalErrors.length) {
console.error(generalErrors.join('\n'));
}
const filesWithErrors = [];
Object.keys(files).forEach(fileName => {
filesWithErrors.push({
name: fileName,
errors: files[fileName],
line: files[fileName]._line,
character: files[fileName]._character,
amountOfErrors: files[fileName].length,
});
});
filesWithErrors.sort((a, b) => {
return b.amountOfErrors - a.amountOfErrors;
});
console.warn('-----');
console.warn('Top 5 files with errors:');
console.warn('-----');
console.warn(
filesWithErrors
.slice(0, 5)
.map(el => `${el.amountOfErrors} .${el.name.split('frontend').pop()}:${el.line}:${el.character}`)
.join('\n'),
);
console.warn('-----');
console.warn('Easy to fix errors:');
console.warn('-----');
console.warn(
filesWithErrors
.slice(-5)
.map(el => `${el.amountOfErrors} .${el.name.split('frontend').pop()}:${el.line}:${el.character}`)
.join('\n'),
);
console.warn('-----');
const totalErrors = filesWithErrors.reduce((acc, el) => {
return acc + el.amountOfErrors;
}, 0);
console.warn('Total Errors: ' + totalErrors + ', total files with errors: ' + filesWithErrors.length);
console.warn('-----');
return {
totalErrors,
filesWithErrors: filesWithErrors.length,
};
}
function readConfigFile(configFileName) {
// Read config file
const configFileText = fs.readFileSync(configFileName).toString();
// Parse JSON, after removing comments. Just fancier JSON.parse
const result = ts.parseConfigFileTextToJson(configFileName, configFileText);
const configObject = result.config;
if (!configObject) {
reportDiagnostics([result.error]);
process.exit(1);
}
// Extract config information
const configParseResult = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configFileName));
if (configParseResult.errors.length > 0) {
reportDiagnostics(configParseResult.errors);
process.exit(1);
}
return configParseResult;
}
function compile(configFileName) {
// Extract configuration from config file
let config = readConfigFile(configFileName);
// Compile
let program = ts.createProgram(config.fileNames, config.options);
let emitResult = program.emit();
// Report errors
const { totalErrors, filesWithErrors } = reportDiagnostics(
ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics),
);
if (totalErrors > MAX_ERRORS) {
console.error(`Total amount of errors (${totalErrors}) is bigger than ${MAX_ERRORS}`);
process.exit(1);
} else if (filesWithErrors > MAX_FILES_WITH_ERRORS) {
console.error(
`Acceptable amount of files with errors (${filesWithErrors}) is bigger than ${MAX_FILES_WITH_ERRORS}`,
);
process.exit(1);
} else {
console.warn('All fine at the moment, amount of errors within range');
process.exit(0);
}
}
compile('jsconfig.json');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment