opentelemetry.io/gulp-src/validate-registry.js

181 lines
4.9 KiB
JavaScript

const gulp = require('gulp');
const { taskArgs } = require('./_util');
const through2 = require('through2');
const yaml = require('js-yaml');
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const addErrors = require('ajv-errors');
const defaultGlobs = ['data/registry/*.yml'];
let numFilesProcessed = 0,
numFilesWithIssues = 0;
let debugFlag = false;
const schema = require('../data/registry-schema.json');
delete schema['cspell:ignore'];
const ajv = new Ajv({
allErrors: true,
});
addFormats(ajv);
addErrors(ajv);
const validate = ajv.compile(schema);
function logFiles(debug) {
return through2.obj(function (file, enc, cb) {
if (debug) {
console.log('Processing file:', file.path);
}
cb(null, file);
});
}
function createSourceMap(yamlText, yamlData) {
const lines = yamlText.split('\n');
const sourceMap = {};
function traverse(node, path = '', startLine = 0) {
if (Array.isArray(node)) {
node.forEach((item, index) => {
const newPath = `${path}/${index}`;
const line = findArrayElementLine(lines, path, index, startLine);
if (line !== -1) {
sourceMap['/' + newPath] = line + 1; // line numbers are 1-based
traverse(item, newPath, line);
}
});
} else if (typeof node === 'object' && node !== null) {
for (const key in node) {
const newPath = path ? `${path}/${key}` : key;
const line = findKeyLine(lines, key, startLine);
if (line !== -1) {
sourceMap['/' + newPath] = line + 1; // line numbers are 1-based
traverse(node[key], newPath, line + 1);
}
}
} else if (node === null) {
sourceMap['/' + path] = startLine;
}
}
function findKeyLine(lines, key, startLine) {
const regex = new RegExp(`^\\s*-?\\s*${key}:`);
for (let i = startLine; i < lines.length; i++) {
if (regex.test(lines[i])) {
return i;
}
}
return -1;
}
function findArrayElementLine(lines, path, index, startLine) {
// const parentPath = path ? path.split('.').join('\\.') : '';
const regex = new RegExp(`^\\s*-`);
let currentIndex = -1;
for (let i = startLine; i < lines.length; i++) {
const trimmedLine = lines[i].trim();
if (regex.test(trimmedLine)) {
currentIndex++;
if (currentIndex === index) {
return i;
}
}
}
return -1;
}
traverse(yamlData);
return sourceMap;
}
function getLineNumber(sourceMap, yamlPath) {
return sourceMap[yamlPath] || null;
}
function validateRegistryEntry(file, enc, cb) {
const registryEntry = yaml.load(file.contents.toString(), {
schema: yaml.JSON_SCHEMA,
});
const sourceMap = createSourceMap(file.contents.toString(), registryEntry);
const valid = validate(registryEntry);
if (!valid) {
// some validation issues let to warning/notices not errors, so we need to check for those
let hasErrors = false;
for (const error of validate.errors) {
const lineNumber = getLineNumber(sourceMap, error.instancePath);
if (!lineNumber) {
console.log(registryEntry);
console.log(yaml.load(file.contents.toString()));
console.log(sourceMap);
console.log(error.instancePath);
}
let logLevel = 'error';
hasErrors = true;
if (process.env.GITHUB_ACTIONS) {
console.log(
`::${logLevel} file=${file.path},line=${lineNumber},endLine=${lineNumber},title=Registry Schema Validation::${error.message}`,
);
} else if (debugFlag) {
console.log(error);
console.error(
`${logLevel} in ${file.path}:${lineNumber}: ${error.message}`,
);
}
}
if (hasErrors) {
numFilesWithIssues++;
}
}
numFilesProcessed++;
cb(null, file);
}
function validateRegistry() {
const argv = taskArgs().options({
glob: {
alias: 'g',
type: 'array',
description:
'Globs of files to run through json schema validation. List flag more than once for multiple values.',
default: defaultGlobs,
},
debug: {
type: 'boolean',
description: 'Output debugging information.',
default: false,
},
}).argv;
if (argv.info) {
// Info about options was already displayed by yargs.help().
return Promise.resolve();
}
const globs = argv.glob;
if (argv.debug) {
console.log('Globs being used:', globs);
}
debugFlag = argv.debug;
return gulp
.src(globs, { followSymlinks: false })
.pipe(logFiles(argv.debug))
.pipe(through2.obj(validateRegistryEntry))
.on('end', () => {
const fileOrFiles = 'file' + (numFilesProcessed == 1 ? '' : 's');
const msg = `Processed ${numFilesProcessed} ${fileOrFiles}, ${numFilesWithIssues} had issues.`;
if (numFilesWithIssues > 0) {
throw new Error(msg);
} else {
console.log(msg);
}
});
}
gulp.task('validate-registry', validateRegistry);