Convert JSON schema validation to use @hyperjump/json-schema, validate against specific schema version, newly validate schema definition, add missing "$schema" property.

This commit is contained in:
David Anson 2023-09-27 22:48:01 -07:00
parent e07eb4bf73
commit dd6fe21ac7
4 changed files with 84 additions and 45 deletions

View file

@ -70,6 +70,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "7.23.0", "@babel/core": "7.23.0",
"@babel/preset-env": "7.22.20", "@babel/preset-env": "7.22.20",
"@hyperjump/json-schema": "1.6.0",
"ava": "5.3.1", "ava": "5.3.1",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",
"c8": "8.0.1", "c8": "8.0.1",
@ -91,7 +92,6 @@
"strip-json-comments": "5.0.1", "strip-json-comments": "5.0.1",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.9",
"toml": "3.0.0", "toml": "3.0.0",
"tv4": "1.3.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"webpack": "5.88.2", "webpack": "5.88.2",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",

View file

@ -8,7 +8,8 @@ const rules = require("../lib/rules");
// Schema scaffolding // Schema scaffolding
const schema = { const schema = {
"title": "Markdownlint configuration schema", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "markdownlint configuration schema",
"type": "object", "type": "object",
"properties": { "properties": {
"default": { "default": {

View file

@ -1,5 +1,6 @@
{ {
"title": "Markdownlint configuration schema", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "markdownlint configuration schema",
"type": "object", "type": "object",
"properties": { "properties": {
"default": { "default": {

View file

@ -10,7 +10,6 @@ const pluginInline = require("markdown-it-for-inline");
const pluginSub = require("markdown-it-sub"); const pluginSub = require("markdown-it-sub");
const pluginSup = require("markdown-it-sup"); const pluginSup = require("markdown-it-sup");
const test = require("ava").default; const test = require("ava").default;
const tv4 = require("tv4");
const { "exports": packageExports, homepage, version } = const { "exports": packageExports, homepage, version } =
require("../package.json"); require("../package.json");
const markdownlint = require("../lib/markdownlint"); const markdownlint = require("../lib/markdownlint");
@ -19,6 +18,9 @@ const rules = require("../lib/rules");
const customRules = require("./rules/rules.js"); const customRules = require("./rules/rules.js");
const configSchema = require("../schema/markdownlint-config-schema.json"); const configSchema = require("../schema/markdownlint-config-schema.json");
const jsonSchemaVersion = "http://json-schema.org/draft-07/schema#";
// eslint-disable-next-line max-len
const markdownlintConfigSchemaUri = "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json";
const deprecatedRuleNames = new Set(constants.deprecatedRuleNames); const deprecatedRuleNames = new Set(constants.deprecatedRuleNames);
const configSchemaStrict = { const configSchemaStrict = {
...configSchema, ...configSchema,
@ -911,8 +913,13 @@ test("readme", async(t) => {
t.true(!tagLeft, "Undocumented tag " + tagLeft + "."); t.true(!tagLeft, "Undocumented tag " + tagLeft + ".");
}); });
test("validateJsonUsingConfigSchemaStrict", (t) => { test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(161); t.plan(161);
const { addSchema, validate } =
// eslint-disable-next-line n/file-extension-in-import
await import("@hyperjump/json-schema/draft-07");
addSchema(configSchemaStrict, markdownlintConfigSchemaUri);
const validateConfigSchema = await validate(markdownlintConfigSchemaUri);
const configRe = const configRe =
/^[\s\S]*<!-- markdownlint-configure-file ([\s\S]*) -->[\s\S]*$/; /^[\s\S]*<!-- markdownlint-configure-file ([\s\S]*) -->[\s\S]*$/;
const ignoreFiles = new Set([ const ignoreFiles = new Set([
@ -922,32 +929,38 @@ test("validateJsonUsingConfigSchemaStrict", (t) => {
"test/invalid-ul-style-style.md", "test/invalid-ul-style-style.md",
"test/wrong-types-in-config-file.md" "test/wrong-types-in-config-file.md"
]); ]);
return import("globby") const { globby } = await import("globby");
.then((module) => module.globby([ const files = await globby([
"*.md", "*.md",
"doc/*.md", "doc/*.md",
"helpers/*.md", "helpers/*.md",
"micromark/*.md", "micromark/*.md",
"schema/*.md", "schema/*.md",
"test/*.md" "test/*.md"
])) ]);
.then((files) => { const testFiles = files.filter((file) => !ignoreFiles.has(file));
const testFiles = files.filter((file) => !ignoreFiles.has(file)); for (const file of testFiles) {
for (const file of testFiles) { const data = fs.readFileSync(file, "utf8");
const data = fs.readFileSync(file, "utf8"); if (configRe.test(data)) {
if (configRe.test(data)) { const config = data.replace(configRe, "$1");
const config = data.replace(configRe, "$1"); const result = validateConfigSchema(JSON.parse(config), "BASIC");
t.true( t.true(
// @ts-ignore result.valid,
tv4.validate(JSON.parse(config), configSchemaStrict), `${file}\n${JSON.stringify(result, null, 2)}`
file + "\n" + JSON.stringify(tv4.error, null, 2)); );
} }
} }
});
}); });
test("validateConfigSchemaAllowsUnknownProperties", (t) => { test("validateConfigSchemaAllowsUnknownProperties", async(t) => {
t.plan(4); t.plan(4);
const { addSchema, validate } =
// eslint-disable-next-line n/file-extension-in-import
await import("@hyperjump/json-schema/draft-07");
const configSchemaUri = "https://example.com/configSchema";
addSchema(configSchema, configSchemaUri);
const configSchemaStrictUri = "https://example.com/configSchemaStrict";
addSchema(configSchemaStrict, configSchemaStrictUri);
const testCases = [ const testCases = [
{ {
"property": true "property": true
@ -959,36 +972,56 @@ test("validateConfigSchemaAllowsUnknownProperties", (t) => {
} }
]; ];
for (const testCase of testCases) { for (const testCase of testCases) {
const defaultResult =
// eslint-disable-next-line no-await-in-loop
await validate(configSchemaUri, testCase, "BASIC");
t.true( t.true(
// @ts-ignore defaultResult.valid,
tv4.validate(testCase, configSchema), "Unknown property blocked by default: " + JSON.stringify(testCase)
"Unknown property blocked by default: " + JSON.stringify(testCase)); );
const strictResult =
// eslint-disable-next-line no-await-in-loop
await validate(configSchemaStrictUri, testCase, "BASIC");
t.false( t.false(
// @ts-ignore strictResult.valid,
tv4.validate(testCase, configSchemaStrict), "Unknown property allowed when strict: " + JSON.stringify(testCase)
"Unknown property allowed when strict: " + JSON.stringify(testCase)); );
} }
}); });
test("validateConfigSchemaAppliesToUnknownProperties", (t) => { test("validateConfigSchemaAppliesToUnknownProperties", async(t) => {
t.plan(4); t.plan(4);
const { addSchema, validate } =
// eslint-disable-next-line n/file-extension-in-import
await import("@hyperjump/json-schema/draft-07");
addSchema(configSchema, markdownlintConfigSchemaUri);
const validateConfigSchema = await validate(markdownlintConfigSchemaUri);
for (const allowed of [ true, {} ]) { for (const allowed of [ true, {} ]) {
t.true( t.true(
// @ts-ignore validateConfigSchema({ "property": allowed }, "BASIC").valid,
tv4.validate({ "property": allowed }, configSchema), `Unknown property value ${allowed} blocked`
`Unknown property value ${allowed} blocked`); );
} }
for (const blocked of [ 2, "string" ]) { for (const blocked of [ 2, "string" ]) {
t.false( t.false(
// @ts-ignore validateConfigSchema({ "property": blocked }, "BASIC").valid,
tv4.validate({ "property": blocked }, configSchema), `Unknown property value ${blocked} allowed`
`Unknown property value ${blocked} allowed`); );
} }
}); });
test("validateConfigExampleJson", async(t) => { test("validateConfigExampleJson", async(t) => {
t.plan(2); t.plan(3);
const { "default": stripJsonComments } = await import("strip-json-comments"); const { "default": stripJsonComments } = await import("strip-json-comments");
// Validate schema
const { addSchema, validate } =
// eslint-disable-next-line n/file-extension-in-import
await import("@hyperjump/json-schema/draft-07");
const schemaResult =
await validate(jsonSchemaVersion, configSchema, "BASIC");
t.true(schemaResult.valid);
// Validate JSONC // Validate JSONC
const fileJson = ".markdownlint.jsonc"; const fileJson = ".markdownlint.jsonc";
const dataJson = fs.readFileSync( const dataJson = fs.readFileSync(
@ -996,10 +1029,14 @@ test("validateConfigExampleJson", async(t) => {
"utf8" "utf8"
); );
const jsonObject = JSON.parse(stripJsonComments(dataJson)); const jsonObject = JSON.parse(stripJsonComments(dataJson));
addSchema(configSchemaStrict, markdownlintConfigSchemaUri);
const result =
await validate(markdownlintConfigSchemaUri, jsonObject, "BASIC");
t.true( t.true(
// @ts-ignore result.valid,
tv4.validate(jsonObject, configSchemaStrict), `${fileJson}\n${JSON.stringify(result, null, 2)}`
fileJson + "\n" + JSON.stringify(tv4.error, null, 2)); );
// Validate YAML // Validate YAML
const fileYaml = ".markdownlint.yaml"; const fileYaml = ".markdownlint.yaml";
const dataYaml = fs.readFileSync( const dataYaml = fs.readFileSync(