@eventcatalog/linter
Version:
A linter for EventCatalog to validate frontmatter and resource references
155 lines • 6.71 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reportErrors = exports.groupErrorsByFile = exports.formatParseError = exports.formatError = void 0;
const chalk_1 = __importDefault(require("chalk"));
const formatError = (error, showFilename = true) => {
const lineInfo = error.line ? chalk_1.default.dim(`:${error.line}:1`) : '';
const filename = showFilename ? `${chalk_1.default.dim(error.file)}${lineInfo}` : '';
const isWarning = error.severity === 'warning';
const severity = isWarning ? chalk_1.default.yellow('warning') : chalk_1.default.red('error');
const icon = isWarning ? chalk_1.default.yellow('⚠') : chalk_1.default.red('✖');
const errorCode = getErrorCode(error);
const field = error.field ? chalk_1.default.dim(`[${error.field}]`) : '';
const parts = [];
if (filename)
parts.push(filename);
parts.push(icon, severity, error.message, field, chalk_1.default.dim(errorCode));
return parts.filter(Boolean).join(' ');
};
exports.formatError = formatError;
const getErrorCode = (error) => {
if (error.rule) {
return `(${error.rule})`;
}
// Fallback to generic error codes
if (error.type === 'schema') {
if (error.field) {
if (error.message.includes('Required'))
return '(@eventcatalog/required-field)';
if (error.message.includes('Expected'))
return '(@eventcatalog/invalid-type)';
return '(@eventcatalog/schema-validation)';
}
return '(@eventcatalog/schema)';
}
if (error.type === 'reference') {
return '(@eventcatalog/invalid-reference)';
}
return '(@eventcatalog/unknown)';
};
const formatParseError = (error, showFilename = true) => {
const filename = showFilename ? chalk_1.default.dim(error.file.relativePath) : '';
const severity = chalk_1.default.red('error');
const message = `Parse error: ${error.error.message}`;
const errorCode = chalk_1.default.dim('(@eventcatalog/parse-error)');
const parts = [];
if (filename)
parts.push(filename);
parts.push(chalk_1.default.red('✖'), severity, message, errorCode);
return parts.filter(Boolean).join(' ');
};
exports.formatParseError = formatParseError;
const groupErrorsByFile = (errors) => {
const grouped = new Map();
for (const error of errors) {
if (!grouped.has(error.file)) {
grouped.set(error.file, []);
}
grouped.get(error.file).push(error);
}
return grouped;
};
exports.groupErrorsByFile = groupErrorsByFile;
const reportErrors = (validationErrors, parseErrors, verbose = false) => {
const allErrors = [
...validationErrors,
...parseErrors.map((pe) => ({
type: 'parse',
resource: pe.file.resourceType || 'unknown',
message: pe.error.message,
file: pe.file.relativePath,
line: undefined,
})),
];
const schemaErrors = validationErrors.filter((e) => e.type === 'schema');
const referenceErrors = validationErrors.filter((e) => e.type === 'reference');
const warnings = validationErrors.filter((e) => e.severity === 'warning');
const errors = [...validationErrors.filter((e) => e.severity !== 'warning'), ...parseErrors];
const totalErrors = errors.length;
const totalWarnings = warnings.length;
if (totalErrors === 0 && totalWarnings === 0) {
console.log(chalk_1.default.green('✔ No problems found!'));
return {
totalErrors: 0,
totalWarnings: 0,
schemaErrors: 0,
referenceErrors: 0,
parseErrors: 0,
filesChecked: 0,
filesWithErrors: 0,
};
}
const grouped = (0, exports.groupErrorsByFile)(validationErrors);
const parseErrorsGrouped = groupParseErrorsByFile(parseErrors);
const allFiles = new Set([...grouped.keys(), ...parseErrorsGrouped.keys()]);
console.log(); // Empty line
// Report by file for better readability
for (const file of Array.from(allFiles).sort()) {
const fileErrors = grouped.get(file) || [];
const fileParseErrors = parseErrorsGrouped.get(file) || [];
const fileErrorCount = fileErrors.length + fileParseErrors.length;
if (fileErrorCount === 0)
continue;
// File header (ESLint-style)
console.log(chalk_1.default.underline(file));
// Parse errors first
for (const error of fileParseErrors) {
console.log(` ${(0, exports.formatParseError)(error, false)}`);
}
// Then validation errors
for (const error of fileErrors) {
console.log(` ${(0, exports.formatError)(error, false)}`);
}
// File summary
const fileWarnings = fileErrors.filter((e) => e.severity === 'warning').length;
const fileActualErrors = fileErrorCount - fileWarnings;
const problemText = fileErrorCount === 1 ? 'problem' : 'problems';
const summaryColor = fileActualErrors > 0 ? chalk_1.default.red : chalk_1.default.yellow;
const summaryIcon = fileActualErrors > 0 ? '✖' : '⚠';
console.log(summaryColor(`\n${summaryIcon} ${fileErrorCount} ${problemText}\n`));
}
// Overall summary (ESLint-style)
const filesWithErrors = allFiles.size;
const totalProblems = totalErrors + totalWarnings;
const problemText = totalProblems === 1 ? 'problem' : 'problems';
const fileText = filesWithErrors === 1 ? 'file' : 'files';
const summaryColor = totalErrors > 0 ? chalk_1.default.red.bold : chalk_1.default.yellow.bold;
const summaryIcon = totalErrors > 0 ? '✖' : '⚠';
console.log(summaryColor(`${summaryIcon} ${totalProblems} ${problemText} (${totalErrors} errors, ${totalWarnings} warnings)`));
console.log(chalk_1.default.dim(` ${filesWithErrors} ${fileText} checked`));
return {
totalErrors,
totalWarnings,
schemaErrors: schemaErrors.length,
referenceErrors: referenceErrors.length,
parseErrors: parseErrors.length,
filesChecked: allFiles.size,
filesWithErrors,
};
};
exports.reportErrors = reportErrors;
const groupParseErrorsByFile = (errors) => {
const grouped = new Map();
for (const error of errors) {
const file = error.file.relativePath;
if (!grouped.has(file)) {
grouped.set(file, []);
}
grouped.get(file).push(error);
}
return grouped;
};
//# sourceMappingURL=index.js.map