From dd6fe21ac73cb7df8de713f106a644a824005eee Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 27 Sep 2023 22:48:01 -0700 Subject: [PATCH] Convert JSON schema validation to use @hyperjump/json-schema, validate against specific schema version, newly validate schema definition, add missing "$schema" property. --- package.json | 2 +- schema/build-config-schema.js | 3 +- schema/markdownlint-config-schema.json | 3 +- test/markdownlint-test.js | 121 ++++++++++++++++--------- 4 files changed, 84 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index cc2bdeba..5c2640af 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "devDependencies": { "@babel/core": "7.23.0", "@babel/preset-env": "7.22.20", + "@hyperjump/json-schema": "1.6.0", "ava": "5.3.1", "babel-loader": "9.1.3", "c8": "8.0.1", @@ -91,7 +92,6 @@ "strip-json-comments": "5.0.1", "terser-webpack-plugin": "5.3.9", "toml": "3.0.0", - "tv4": "1.3.0", "typescript": "5.2.2", "webpack": "5.88.2", "webpack-cli": "5.1.4", diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index f4cc13a4..582263ca 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -8,7 +8,8 @@ const rules = require("../lib/rules"); // Schema scaffolding const schema = { - "title": "Markdownlint configuration schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "markdownlint configuration schema", "type": "object", "properties": { "default": { diff --git a/schema/markdownlint-config-schema.json b/schema/markdownlint-config-schema.json index 03fa000b..6e3d3c1d 100644 --- a/schema/markdownlint-config-schema.json +++ b/schema/markdownlint-config-schema.json @@ -1,5 +1,6 @@ { - "title": "Markdownlint configuration schema", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "markdownlint configuration schema", "type": "object", "properties": { "default": { diff --git a/test/markdownlint-test.js b/test/markdownlint-test.js index ae5c167e..bc66eaab 100644 --- a/test/markdownlint-test.js +++ b/test/markdownlint-test.js @@ -10,7 +10,6 @@ const pluginInline = require("markdown-it-for-inline"); const pluginSub = require("markdown-it-sub"); const pluginSup = require("markdown-it-sup"); const test = require("ava").default; -const tv4 = require("tv4"); const { "exports": packageExports, homepage, version } = require("../package.json"); const markdownlint = require("../lib/markdownlint"); @@ -19,6 +18,9 @@ const rules = require("../lib/rules"); const customRules = require("./rules/rules.js"); 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 configSchemaStrict = { ...configSchema, @@ -911,8 +913,13 @@ test("readme", async(t) => { t.true(!tagLeft, "Undocumented tag " + tagLeft + "."); }); -test("validateJsonUsingConfigSchemaStrict", (t) => { +test("validateJsonUsingConfigSchemaStrict", async(t) => { 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 = /^[\s\S]*[\s\S]*$/; const ignoreFiles = new Set([ @@ -922,32 +929,38 @@ test("validateJsonUsingConfigSchemaStrict", (t) => { "test/invalid-ul-style-style.md", "test/wrong-types-in-config-file.md" ]); - return import("globby") - .then((module) => module.globby([ - "*.md", - "doc/*.md", - "helpers/*.md", - "micromark/*.md", - "schema/*.md", - "test/*.md" - ])) - .then((files) => { - const testFiles = files.filter((file) => !ignoreFiles.has(file)); - for (const file of testFiles) { - const data = fs.readFileSync(file, "utf8"); - if (configRe.test(data)) { - const config = data.replace(configRe, "$1"); - t.true( - // @ts-ignore - tv4.validate(JSON.parse(config), configSchemaStrict), - file + "\n" + JSON.stringify(tv4.error, null, 2)); - } - } - }); + const { globby } = await import("globby"); + const files = await globby([ + "*.md", + "doc/*.md", + "helpers/*.md", + "micromark/*.md", + "schema/*.md", + "test/*.md" + ]); + const testFiles = files.filter((file) => !ignoreFiles.has(file)); + for (const file of testFiles) { + const data = fs.readFileSync(file, "utf8"); + if (configRe.test(data)) { + const config = data.replace(configRe, "$1"); + const result = validateConfigSchema(JSON.parse(config), "BASIC"); + t.true( + result.valid, + `${file}\n${JSON.stringify(result, null, 2)}` + ); + } + } }); -test("validateConfigSchemaAllowsUnknownProperties", (t) => { +test("validateConfigSchemaAllowsUnknownProperties", async(t) => { 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 = [ { "property": true @@ -959,36 +972,56 @@ test("validateConfigSchemaAllowsUnknownProperties", (t) => { } ]; for (const testCase of testCases) { + const defaultResult = + // eslint-disable-next-line no-await-in-loop + await validate(configSchemaUri, testCase, "BASIC"); t.true( - // @ts-ignore - tv4.validate(testCase, configSchema), - "Unknown property blocked by default: " + JSON.stringify(testCase)); + defaultResult.valid, + "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( - // @ts-ignore - tv4.validate(testCase, configSchemaStrict), - "Unknown property allowed when strict: " + JSON.stringify(testCase)); + strictResult.valid, + "Unknown property allowed when strict: " + JSON.stringify(testCase) + ); } }); -test("validateConfigSchemaAppliesToUnknownProperties", (t) => { +test("validateConfigSchemaAppliesToUnknownProperties", async(t) => { 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, {} ]) { t.true( - // @ts-ignore - tv4.validate({ "property": allowed }, configSchema), - `Unknown property value ${allowed} blocked`); + validateConfigSchema({ "property": allowed }, "BASIC").valid, + `Unknown property value ${allowed} blocked` + ); } for (const blocked of [ 2, "string" ]) { t.false( - // @ts-ignore - tv4.validate({ "property": blocked }, configSchema), - `Unknown property value ${blocked} allowed`); + validateConfigSchema({ "property": blocked }, "BASIC").valid, + `Unknown property value ${blocked} allowed` + ); } }); test("validateConfigExampleJson", async(t) => { - t.plan(2); + t.plan(3); 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 const fileJson = ".markdownlint.jsonc"; const dataJson = fs.readFileSync( @@ -996,10 +1029,14 @@ test("validateConfigExampleJson", async(t) => { "utf8" ); const jsonObject = JSON.parse(stripJsonComments(dataJson)); + addSchema(configSchemaStrict, markdownlintConfigSchemaUri); + const result = + await validate(markdownlintConfigSchemaUri, jsonObject, "BASIC"); t.true( - // @ts-ignore - tv4.validate(jsonObject, configSchemaStrict), - fileJson + "\n" + JSON.stringify(tv4.error, null, 2)); + result.valid, + `${fileJson}\n${JSON.stringify(result, null, 2)}` + ); + // Validate YAML const fileYaml = ".markdownlint.yaml"; const dataYaml = fs.readFileSync(