Skip to content

Commit

Permalink
XS⚠️ ◾ 🦺 Validator - Updated validator to flag fields with missing sp…
Browse files Browse the repository at this point in the history
…ace (#9531)

* 🦺 updated rule validator to flag frontmatter with missing white space after field

* 🐛 Updated validator to ignore index file at the base of categories

* 🎨 Made error message for spacing lower case to be consistent with other errors

* 🔇 removed unused onWarning callback function

* 🔇 Removed unused filesChanged console log

* made error message clearer

* added quotes around field for missing spaces error message

* 🗑️ deleted unused "match" variable

---------

Co-authored-by: Calinator444 <[email protected]>
  • Loading branch information
Calinator444 and Calinator444 authored Nov 4, 2024
1 parent b284b83 commit 2010128
Showing 1 changed file with 84 additions and 56 deletions.
140 changes: 84 additions & 56 deletions scripts/frontmatter-validator/frontmatter-validator.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const fs = require("fs");
const ajv = require("ajv");
const yaml = require("js-yaml");
const addFormats = require("ajv-formats");
const ajvErrors = require("ajv-errors");
const fs = require('fs');
const ajv = require('ajv');
const yaml = require('js-yaml');
const addFormats = require('ajv-formats');
const ajvErrors = require('ajv-errors');

let allErrors = [];

const schemas = {
rule: loadSchema("schema/rule-schema.json"),
category: loadSchema("schema/category-schema.json"),
top_category: loadSchema("schema/top-category-schema.json"),
rule: loadSchema('schema/rule-schema.json'),
category: loadSchema('schema/category-schema.json'),
top_category: loadSchema('schema/top-category-schema.json'),
};

const validator = initializeValidator();
Expand All @@ -29,83 +29,114 @@ function initializeValidator() {
function loadSchema(schemaPath) {
const args = process.argv.slice(2);
let fullPath = `scripts/frontmatter-validator/${schemaPath}`;
fullPath = args.includes("--file")
fullPath = args.includes('--file')
? `scripts/frontmatter-validator/${schemaPath}`
: schemaPath;

// todo fix for non file input
const json = JSON.parse(fs.readFileSync(fullPath, "utf8"));
const json = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
return json;
}

function matchSchema(filePath) {
const isRule = filePath.endsWith("rule.md");
function determineCategory(filePath) {
const isRule = filePath.endsWith('rule.md');
const isInCategories =
filePath.includes("/categories") &&
!filePath.endsWith("/categories/index.md");
const isIndexFile = filePath.endsWith("index.md");

filePath.includes('/categories') &&
!filePath.endsWith('/categories/index.md');
const isIndexFile = filePath.endsWith('index.md');
if (isRule) {
return validator.getSchema("rule");
return 'rule';
}

if (isInCategories) {
if (!isIndexFile) {
return validator.getSchema("category");
return 'category';
} else {
return validator.getSchema("top_category");
return 'top_category';
}
}
}

function getMissingSpaceErrors(frontmatterContents, schema) {
const errors = [];
if (!frontmatterContents) return [];
for (const field of Object.keys(schema.properties)) {
const regex = new RegExp(`[\\s^](${field}:)\\S`);
if (frontmatterContents.match(regex)) {
errors.push(
`A space is missing after the ':' in the field '${field}'. Please add a space between the ':' and the '${field}' value`
);
}
}
return errors;
}

function validateFrontmatter(filePath) {
if (!fs.existsSync(filePath) || filePath.indexOf(".github") !== -1) {
if (filePath && filePath.endsWith('/categories/index.md')) return;
if (!fs.existsSync(filePath) || filePath.indexOf('.github') !== -1) {
return; // Skip if file does not exist or is in .github directory
}

const fileContents = fs.readFileSync(filePath, "utf8");
const frontmatter = parseFrontmatter(filePath, fileContents);
const validate = matchSchema(filePath);
const fileContents = fs.readFileSync(filePath, 'utf8');
const frontmatterContents = extractFrontMatter(fileContents);
if (frontmatterContents === undefined) {
allErrors.push({
filePath,
fileErrors: ["missing '---'"],
});
return;
}
const ruleType = determineCategory(filePath);
const missingSpaceErrors = getMissingSpaceErrors(
frontmatterContents,
schemas[ruleType]
);
const frontmatter = parseFrontmatterToJson(frontmatterContents);
const validate = validator.getSchema(ruleType);
const isValid = validate(frontmatter);

if (!isValid && validate.errors) {
let fileErrors = validate.errors
.filter(
(error) =>
error.keyword === "errorMessage" || error.keyword === "required"
error.keyword === 'errorMessage' || error.keyword === 'required'
)
.map((error) => error.message);

if (fileErrors.length > 0) {
allErrors.push({ filePath, fileErrors });
if (fileErrors.length > 0 || missingSpaceErrors.length) {
allErrors.push({
filePath,
fileErrors: [...fileErrors, ...missingSpaceErrors],
});
}
}
}

function parseFrontmatter(filePath, fileContents) {
if (!fileContents) return {};

function parseFrontmatterToJson(fileContents) {
if (!fileContents) {
return {};
}
try {
const frontmatterMatch = /^---([\s\S]*?)---/.exec(fileContents);
const frontmatterString = frontmatterMatch[1];
const frontmatter = yaml.load(frontmatterString, {
return yaml.load(fileContents, {
schema: yaml.JSON_SCHEMA,
});
return frontmatter;
} catch (error) {
allErrors.push({
filePath,
fileErrors: ["missing '---'"],
});
} catch (e) {
return undefined;
}
}

function extractFrontMatter(fileContents) {
if (!fileContents) return undefined;
let frontmatterString;
const frontmatterMatch = /^---([\s\S]*?)---/.exec(fileContents);

if (!frontmatterMatch) return undefined;
frontmatterString = frontmatterMatch[1];
return frontmatterString;
}

function validateFiles(fileListPath) {
const fileContents = fs.readFileSync(fileListPath, "utf8");
const filePaths = fileContents.trim().split("\n");
const fileContents = fs.readFileSync(fileListPath, 'utf8');
const filePaths = fileContents.trim().split('\n');

filePaths.forEach((file) => {
if (file.endsWith(".md")) {
if (file.endsWith('.md')) {
validateFrontmatter(file);
}
});
Expand All @@ -114,38 +145,35 @@ function validateFiles(fileListPath) {
function main() {
const args = process.argv.slice(2);

if (args.includes("--file")) {
const fileListIndex = args.indexOf("--file") + 1;
if (args.includes('--file')) {
const fileListIndex = args.indexOf('--file') + 1;
const fileListPath = args[fileListIndex];
validateFiles(fileListPath);
} else {
const filesChanged = args[0];
if (filesChanged) {
const filePaths = filesChanged
.split(",")
.filter((file) => file.endsWith(".md"))
.split(',')
.filter((file) => file.endsWith('.md'))
.map((file) => `../../${file}`);
filePaths.forEach(validateFrontmatter);
}
}

if (allErrors.length === 0) {
console.log("No frontmatter validation errors found.");
console.log('No frontmatter validation errors found.');
return;
}

allErrors.forEach(({ filePath, fileErrors }) => {
console.log(
`## Rule: [${filePath}](https://github.com/SSWConsulting/SSW.Rules.Content/tree/main/${filePath})\n`
);
console.log("Issues:");
console.log('Issues Detected:');
fileErrors.forEach((issue) => console.log(`- **${issue}**`));
console.log("\n");
console.log("\n");
console.log('\n');
console.log('\n');
});
process.exit(1);

process.exit(1);
}

main();

0 comments on commit 2010128

Please sign in to comment.