UNPKG

eslint-plugin-json-schema-validator

Version:
1,592 lines (1,575 loc) 50.3 kB
import { n as __require, t as __exportAll } from "./chunk-D3JzZLW2.mjs"; import { t as get } from "./http-VZVO3ok-.mjs"; import * as jsoncESLintParser from "jsonc-eslint-parser"; import { getStaticJSONValue } from "jsonc-eslint-parser"; import * as yamlESLintParser from "yaml-eslint-parser"; import { getStaticYAMLValue } from "yaml-eslint-parser"; import * as tomlESLintParser from "toml-eslint-parser"; import { getStaticTOMLValue } from "toml-eslint-parser"; import path, { dirname } from "path"; import { minimatch } from "minimatch"; import * as eslintUtils from "@eslint-community/eslint-utils"; import debugBuilder from "debug"; import fs from "fs"; import { draft7 } from "json-schema-migrate-x"; import { fileURLToPath } from "url"; import { createRequire } from "module"; import { createSyncFn } from "synckit"; import Ajv from "ajv"; import v6Schema from "ajv/lib/refs/json-schema-draft-06.json" with { type: "json" }; import { toCompatCreate } from "eslint-json-compat-utils"; //#region src/utils/index.ts /** * Define the rule. * @param ruleName ruleName * @param rule rule module */ function createRule(ruleName, rule) { return { meta: { ...rule.meta, docs: { ...rule.meta.docs, url: `https://ota-meshi.github.io/eslint-plugin-json-schema-validator/rules/${ruleName}.html`, ruleId: `json-schema-validator/${ruleName}`, ruleName } }, create(context) { const sourceCode = context.sourceCode; const filename = context.filename; const visitor = rule.create(context, { customBlock: false, filename }); if (typeof sourceCode.parserServices.defineCustomBlocksVisitor === "function" && path.extname(filename) === ".vue") return compositingVisitors(visitor, sourceCode.parserServices.defineCustomBlocksVisitor(context, jsoncESLintParser, { target(lang, block) { if (lang) return /^json[5c]?$/i.test(lang); return block.name === "i18n"; }, create(blockContext) { return rule.create(blockContext, { customBlock: true, filename: getBlockFileName(blockContext.parserServices.customBlock, "json") }); } }), sourceCode.parserServices.defineCustomBlocksVisitor(context, yamlESLintParser, { target: ["yaml", "yml"], create(blockContext) { return rule.create(blockContext, { customBlock: true, filename: getBlockFileName(blockContext.parserServices.customBlock, "yaml") }); } }), sourceCode.parserServices.defineCustomBlocksVisitor(context, tomlESLintParser, { target: ["toml"], create(blockContext) { return rule.create(blockContext, { customBlock: true, filename: getBlockFileName(blockContext.parserServices.customBlock, "toml") }); } })); return visitor; /** Get file name of block */ function getBlockFileName(customBlock, langFallback) { const attrs = {}; for (const attr of customBlock.startTag.attributes) if (!attr.directive) attrs[attr.key.name] = attr.value?.value ?? null; const ext = attrs.lang || langFallback; let attrQuery = ""; for (const [key, val] of Object.entries(attrs)) { if ([ "id", "index", "src", "type" ].includes(key)) continue; attrQuery += `&${key}=${val}`; } return `${filename}/${`${customBlock.name}.${ext}`}?vue&type=custom&blockType=${customBlock.name}${attrQuery}`; } } }; } /** * Compositing visitors */ function compositingVisitors(visitor, ...visitors) { for (const v of visitors) for (const key in v) if (visitor[key]) { const o = visitor[key]; visitor[key] = (...args) => { o(...args); v[key](...args); }; } else visitor[key] = v[key]; return visitor; } //#endregion //#region src/utils/ast/json.ts const TRAVERSE_TARGET_TYPE$2 = new Set([ "Program", "JSONExpressionStatement", "JSONObjectExpression", "JSONArrayExpression" ]); const GET_JSON_NODES = { Program(node, _paths) { return { value: node.body[0] }; }, JSONExpressionStatement(node, _paths) { return { value: node.expression }; }, JSONObjectExpression(node, paths) { const path = String(paths.shift()); for (const prop of node.properties) if (prop.key.type === "JSONIdentifier") { if (prop.key.name === path) return { key: () => prop.key.range, value: prop.value }; } else if (String(prop.key.value) === path) return { key: () => prop.key.range, value: prop.value }; throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); }, JSONArrayExpression(node, paths) { const path = String(paths.shift()); for (let index = 0; index < node.elements.length; index++) { if (String(index) !== path) continue; const element = node.elements[index]; if (element) return { value: element }; return { key: (sourceCode) => { const before = node.elements.slice(0, index).reverse().find((n) => n != null); let tokenIndex = before ? node.elements.indexOf(before) : -1; let token = before ? sourceCode.getTokenAfter(before) : sourceCode.getFirstToken(node); while (tokenIndex < index) { tokenIndex++; token = sourceCode.getTokenAfter(token); } return [sourceCode.getTokenBefore(token).range[1], token.range[0]]; }, value: null }; } throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); } }; /** * Get node from path */ function getJSONNodeFromPath(node, [ ...paths]) { let data = { key: (sourceCode) => { const dataNode = node.body[0].expression; if (dataNode.type === "JSONObjectExpression" || dataNode.type === "JSONArrayExpression") return sourceCode.getFirstToken(dataNode).range; return dataNode.range; }, value: node }; while (paths.length && data.value) { if (!isTraverseTarget$2(data.value)) throw new Error(`Unexpected node type: ${data.value.type}`); data = GET_JSON_NODES[data.value.type](data.value, paths); } return data; } /** * Checks whether given node is traverse target. */ function isTraverseTarget$2(node) { return TRAVERSE_TARGET_TYPE$2.has(node.type); } //#endregion //#region src/utils/ast/yaml.ts const TRAVERSE_TARGET_TYPE$1 = new Set([ "Program", "YAMLDocument", "YAMLMapping", "YAMLSequence", "YAMLAlias", "YAMLWithMeta" ]); const GET_YAML_NODES = { Program(node, paths) { if (node.body.length <= 1) return { value: node.body[0] }; const path = String(paths.shift()); for (let index = 0; index < node.body.length; index++) { if (String(index) !== path) continue; return { value: node.body[index] }; } throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); }, YAMLDocument(node, _paths) { if (node.content) return { value: node.content }; return { key: () => { return node.range; }, value: null }; }, YAMLMapping(node, paths) { const path = String(paths.shift()); for (const pair of node.pairs) if (String(pair.key ? getStaticYAMLValue(pair.key) : null) === path) return { key: (sourceCode) => { if (pair.key) return pair.key.range; return sourceCode.getFirstToken(pair).range; }, value: pair.value }; throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); }, YAMLSequence(node, paths) { const path = String(paths.shift()); for (let index = 0; index < node.entries.length; index++) { if (String(index) !== path) continue; const entry = node.entries[index]; if (entry) return { value: entry }; return { key: (sourceCode) => { const before = node.entries.slice(0, index).reverse().find((n) => n != null); let hyphenTokenElementIndex; let hyphenToken; if (!before) { hyphenTokenElementIndex = 0; hyphenToken = sourceCode.getFirstToken(node); } else { hyphenTokenElementIndex = node.entries.indexOf(before) + 1; hyphenToken = sourceCode.getTokenAfter(before); } while (hyphenTokenElementIndex < index) { hyphenTokenElementIndex++; hyphenToken = sourceCode.getTokenAfter(hyphenToken); } return hyphenToken.range; }, value: null }; } throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); }, YAMLAlias(node, paths) { paths.length = 0; return { value: node }; }, YAMLWithMeta(node, paths) { if (node.value) return { value: node.value }; throw new Error(`Unexpected state: [${paths.join(", ")}]`); } }; /** * Get node from path */ function getYAMLNodeFromPath(node, [ ...paths]) { let data = { key: (sourceCode) => { const doc = node.body[0]; if (node.body.length > 1) return (sourceCode.getFirstToken(doc) || doc).range; const dataNode = doc.content; if (dataNode == null) return (sourceCode.getFirstToken(doc) || doc).range; if (dataNode.type === "YAMLMapping" || dataNode.type === "YAMLSequence") return sourceCode.getFirstToken(dataNode).range; return dataNode.range; }, value: node }; while (paths.length && data.value) { if (!isTraverseTarget$1(data.value)) throw new Error(`Unexpected node type: ${data.value.type}`); data = GET_YAML_NODES[data.value.type](data.value, paths); } return data; } /** * Checks whether given node is traverse target. */ function isTraverseTarget$1(node) { return TRAVERSE_TARGET_TYPE$1.has(node.type); } //#endregion //#region src/utils/ast/toml.ts var MatchType = /* @__PURE__ */ function(MatchType) { MatchType[MatchType["notMatch"] = 0] = "notMatch"; MatchType[MatchType["match"] = 1] = "match"; MatchType[MatchType["beginsMatch"] = 2] = "beginsMatch"; MatchType[MatchType["subMatch"] = 3] = "subMatch"; return MatchType; }(MatchType || {}); const TRAVERSE_TARGET_TYPE = new Set(["TOMLArray", "TOMLInlineTable"]); const GET_TOML_NODES = { TOMLArray(node, paths) { const path = String(paths.shift()); for (let index = 0; index < node.elements.length; index++) { if (String(index) !== path) continue; return { value: node.elements[index] }; } throw new Error(`Unexpected state: [${[path, ...paths].join(", ")}]`); }, TOMLInlineTable(node, paths) { for (const body of node.body) { const keys = getStaticTOMLValue(body.key); const m = getMatchType(paths, keys); if (m === MatchType.match) { paths.length = 0; return { value: body.key }; } if (m === MatchType.subMatch) { paths.length = 0; return { value: body.key }; } if (m === MatchType.beginsMatch) { for (let index = 0; index < keys.length; index++) paths.shift(); return { key: () => body.key.range, value: body.value }; } } throw new Error(`Unexpected state: [${paths.join(", ")}]`); } }; /** * Get node from path */ function getTOMLNodeFromPath(node, paths) { const topLevelTable = node.body[0]; if (!paths.length) return { key: (sourceCode) => (sourceCode.getFirstToken(topLevelTable) || topLevelTable).range, value: topLevelTable }; for (const body of topLevelTable.body) if (body.type === "TOMLKeyValue") { const result = getTOMLNodeFromPathForKeyValue(body, paths); if (result) return result; } else { const m = getMatchType(paths, body.resolvedKey); if (m === MatchType.match) return { value: body.key }; if (m === MatchType.subMatch) return { value: body.key }; if (m === MatchType.beginsMatch) { const nextKeys = paths.slice(body.resolvedKey.length); for (const keyVal of body.body) { const result = getTOMLNodeFromPathForKeyValue(keyVal, nextKeys); if (result) return result; } } } throw new Error(`Unexpected state: [${paths.join(", ")}]`); } /** * Get node from path for KeyValue node */ function getTOMLNodeFromPathForKeyValue(node, paths) { const keys = getStaticTOMLValue(node.key); const m = getMatchType(paths, keys); if (m === MatchType.match) return { value: node.key }; if (m === MatchType.subMatch) return { value: node.key }; if (m === MatchType.beginsMatch) { const nextKeys = paths.slice(keys.length); return getTOMLNodeFromPathForContent(node.value, nextKeys); } return null; } /** * Get node from path for content node */ function getTOMLNodeFromPathForContent(node, [ ...paths]) { let data = { value: node }; while (paths.length && data.value) { if (!isTraverseTarget(data.value)) throw new Error(`Unexpected node type: ${data.value.type}`); data = GET_TOML_NODES[data.value.type](data.value, paths); } return data; } /** * Checks whether given node is traverse target. */ function isTraverseTarget(node) { return TRAVERSE_TARGET_TYPE.has(node.type); } /** * Checks if the given key is a prefix match. */ function getMatchType(paths, keys) { if (keys.length <= paths.length) { if (!keys.every((key, index) => String(key) === String(paths[index]))) return MatchType.notMatch; return keys.length === paths.length ? MatchType.match : MatchType.beginsMatch; } return paths.every((path, index) => String(path) === String(keys[index])) ? MatchType.subMatch : MatchType.notMatch; } //#endregion //#region src/utils/ast/js/utils.ts /** * Gets the property name of a given node. */ function getStaticPropertyName(node, context) { let key; if (node.type === "Property") { key = node.key; if (!node.computed) { if (key.type === "Identifier") return key.name; } } else if (node.type === "MemberExpression") { key = node.property; if (!node.computed) { if (key.type === "Identifier") return key.name; return null; } } else return null; if (key.type === "Literal" || key.type === "TemplateLiteral") return getStringLiteralValue(key); if (key.type === "Identifier") { const init = findInitNode(context, key); if (init) { if (init.node.type === "Literal" || init.node.type === "TemplateLiteral") return getStringLiteralValue(init.node); } } return null; } /** * Gets the string of a given node. */ function getStringLiteralValue(node) { if (node.type === "Literal") { if (node.value == null) { if (node.bigint != null) return String(node.bigint); } return String(node.value); } if (node.type === "TemplateLiteral") { if (node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0].value.cooked; } return null; } /** * Find the variable of a given name. */ function findVariable(context, node) { return eslintUtils.findVariable(getScope(context, node), node); } /** * Get the value of a given node if it's a static value. */ function getStaticValue(context, node) { return eslintUtils.getStaticValue(node, getScope(context, node)); } /** * Find the node that initial value. */ function findInitNode(context, node) { const variable = findVariable(context, node); if (!variable) return null; if (variable.defs.length === 1) { const def = variable.defs[0]; if (def.type === "Variable" && def.parent.kind === "const" && def.node.init) { let init = def.node.init; const reads = variable.references.filter((ref) => ref.isRead()).map((ref) => ref.identifier); if (init.type === "Identifier") { const data = findInitNode(context, init); if (!data) return null; init = data.node; reads.push(...data.reads); } return { node: init, reads }; } } return null; } /** * Gets the scope for the current node */ function getScope(context, currentNode) { const inner = currentNode.type !== "Program"; const scopeManager = context.sourceCode.scopeManager; let node = currentNode; for (; node; node = node.parent || null) { const scope = scopeManager.acquire(node, inner); if (scope) { if (scope.type === "function-expression-name") return scope.childScopes[0]; return scope; } } return scopeManager.scopes[0]; } //#endregion //#region src/utils/ast/js/index.ts const UNKNOWN = Symbol("unknown value"); const EMPTY_MAP = Object.freeze(/* @__PURE__ */ new Map()); const UNKNOWN_PATH_DATA = { data: UNKNOWN, children: EMPTY_MAP }; const UNKNOWN_STRING_PATH_DATA = { data: "UNKNOWN", children: EMPTY_MAP }; /** * Analyze JavaScript AST */ function analyzeJsAST(node, rootRange, context) { const data = getPathData(node, context); if (data.data === UNKNOWN) return null; const pathData = { key: rootRange, ...data }; return { object: data.data, pathData }; } const CALC_UNARY = { "+": (v) => Number(v), "-": (v) => -v, "!": (v) => !v, "~": (v) => ~v, typeof: (v) => typeof v, void: () => void 0, delete: null }; const CALC_BINARY = { "==": (v1, v2) => v1 == v2, "!=": (v1, v2) => v1 != v2, "===": (v1, v2) => v1 === v2, "!==": (v1, v2) => v1 !== v2, "<": (v1, v2) => v1 < v2, "<=": (v1, v2) => v1 <= v2, ">": (v1, v2) => v1 > v2, ">=": (v1, v2) => v1 >= v2, "<<": (v1, v2) => v1 << v2, ">>": (v1, v2) => v1 >> v2, ">>>": (v1, v2) => v1 >>> v2, "+": (v1, v2) => v1 + v2, "-": (v1, v2) => v1 - v2, "*": (v1, v2) => v1 * v2, "/": (v1, v2) => v1 / v2, "%": (v1, v2) => v1 % v2, "|": (v1, v2) => v1 | v2, "^": (v1, v2) => v1 ^ v2, "&": (v1, v2) => v1 & v2, in: (v1, v2) => v1 in v2, instanceof: (v1, v2) => v1 instanceof v2, "**": (v1, v2) => v1 ** v2 }; const VISITORS = { ObjectExpression(node, context) { const data = {}; const children = /* @__PURE__ */ new Map(); for (const prop of node.properties) if (prop.type === "Property") { const keyName = getStaticPropertyName(prop, context); if (keyName != null) { const propData = getPathData(prop.value, context); if (propData.data !== UNKNOWN) { data[keyName] = propData.data; children.set(keyName, { key: prop.key.range, ...propData }); } else { data[keyName] = UNKNOWN; children.set(keyName, UNKNOWN); } } } else if (prop.type === "SpreadElement") { const propData = getPathData(prop.argument, context); propData.children.forEach((val, key) => { data[key] = propData.data[key]; children.set(key, val); }); } return { data, children }; }, ArrayExpression(node, context) { const data = []; const children = /* @__PURE__ */ new Map(); for (let index = 0; index < node.elements.length; index++) { const element = node.elements[index]; if (element) { if (element.type !== "SpreadElement") { const propData = getPathData(element, context); if (propData.data !== UNKNOWN) { data[index] = propData.data; children.set(String(index), { key: element.range, ...propData }); } else { data[index] = UNKNOWN; children.set(String(index), UNKNOWN); } } } else { data[index] = void 0; children.set(String(index), { key: (sourceCode) => { const before = node.elements.slice(0, index).reverse().find((n) => n != null); let tokenIndex = before ? node.elements.indexOf(before) : -1; let token = before ? sourceCode.getTokenAfter(before) : sourceCode.getFirstToken(node); while (tokenIndex < index) { tokenIndex++; token = sourceCode.getTokenAfter(token); } return [sourceCode.getTokenBefore(token).range[1], token.range[0]]; }, data: void 0, children: EMPTY_MAP }); } } return { data, children }; }, Identifier(node, context) { const init = findInitNode(context, node); if (init == null) { const evalData = getStaticValue(context, node); if (evalData != null) return { data: evalData.value, children: EMPTY_MAP }; return UNKNOWN_PATH_DATA; } const data = getPathData(init.node, context); if (typeof data.data === "object" && data.data != null) for (const readId of init.reads) { const props = getWriteProps(readId); if (props == null) continue; let objData = data; let obj = data.data; while (props.length) { const prop = props.shift(); const child = objData.children.get(prop); if (child) { if (child === UNKNOWN) break; const nextObj = obj[prop]; if (typeof nextObj === "object" && nextObj != null) { objData = child; obj = obj[prop]; } else break; } else { obj[prop] = UNKNOWN; objData.children.set(prop, UNKNOWN); break; } } } return data; /** * Get write properties from given Identifier */ function getWriteProps(id) { if (!id.parent || id.parent.type !== "MemberExpression" || id.parent.object !== id) return null; const results = []; let mem = id.parent; while (mem) { const name = getStaticPropertyName(mem, context); if (name == null) break; results.push(name); if (!mem.parent || mem.parent.type !== "MemberExpression" || mem.parent.object !== mem) break; mem = mem.parent; } if (!mem.parent || mem.parent.type !== "AssignmentExpression") return null; return results; } }, Literal(node, _context) { return { data: node.value, children: EMPTY_MAP }; }, UnaryExpression(node, context) { const argData = getPathData(node.argument, context); if (argData.data === UNKNOWN) return UNKNOWN_PATH_DATA; const calc = CALC_UNARY[node.operator]; if (!calc) return UNKNOWN_PATH_DATA; return { data: calc(argData.data), children: EMPTY_MAP }; }, BinaryExpression(node, context) { if (node.left.type === "PrivateIdentifier") return UNKNOWN_PATH_DATA; const leftData = getPathData(node.left, context); if (leftData.data === UNKNOWN) return UNKNOWN_PATH_DATA; const rightData = getPathData(node.right, context); if (rightData.data === UNKNOWN) return UNKNOWN_PATH_DATA; const calc = CALC_BINARY[node.operator]; if (!calc) return UNKNOWN_PATH_DATA; return { data: calc(leftData.data, rightData.data), children: EMPTY_MAP }; }, LogicalExpression(node, context) { const leftData = getPathData(node.left, context); if (leftData.data === UNKNOWN) return UNKNOWN_PATH_DATA; const operator = node.operator; if (operator === "||") { if (leftData.data) return leftData; } else if (operator === "&&") { if (!leftData.data) return leftData; } else if (operator === "??") { if (leftData.data != null) return leftData; } else return UNKNOWN_PATH_DATA; return getPathData(node.right, context); }, AssignmentExpression(node, context) { return getPathData(node.right, context); }, MemberExpression(node, context) { if (node.object.type === "Super") return UNKNOWN_PATH_DATA; const objectData = getPathData(node.object, context); if (objectData.data === UNKNOWN) return UNKNOWN_PATH_DATA; const propName = getStaticPropertyName(node, context); if (propName == null) return UNKNOWN_PATH_DATA; const define = objectData.children.get(propName); if (define && define !== UNKNOWN) return define; if (objectData.data != null) return { data: objectData.data[propName], children: EMPTY_MAP }; return UNKNOWN_PATH_DATA; }, ConditionalExpression(node, context) { const testData = getPathData(node.test, context); if (testData.data === UNKNOWN) return UNKNOWN_PATH_DATA; if (testData.data) return getPathData(node.consequent, context); return getPathData(node.alternate, context); }, CallExpression(node, context) { const evalData = getStaticValue(context, node); if (!evalData) { if (node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && node.callee.object.name === "require" && getStaticPropertyName(node.callee, context) === "resolve") return UNKNOWN_STRING_PATH_DATA; return UNKNOWN_PATH_DATA; } return { data: evalData.value, children: EMPTY_MAP }; }, NewExpression(node, context) { const evalData = getStaticValue(context, node); if (!evalData) return UNKNOWN_PATH_DATA; return { data: evalData.value, children: EMPTY_MAP }; }, SequenceExpression(node, context) { const last = node.expressions[node.expressions.length - 1]; return getPathData(last, context); }, TemplateLiteral(node, context) { const expressions = []; for (const e of node.expressions) { const data = getPathData(e, context); if (data.data === UNKNOWN) return UNKNOWN_STRING_PATH_DATA; expressions.push(data.data); } let data = node.quasis[0].value.cooked ?? node.quasis[0].value.raw; for (let i = 0; i < expressions.length; ++i) { data += String(expressions[i]); data += node.quasis[i + 1].value.cooked ?? node.quasis[i + 1].value.raw; } return { data, children: EMPTY_MAP }; }, TaggedTemplateExpression(node, context) { const tag = getPathData(node.tag, context); if (tag.data === UNKNOWN) return UNKNOWN_PATH_DATA; if (tag.data !== String.raw) return UNKNOWN_PATH_DATA; const expressions = []; for (const e of node.quasi.expressions) { const data = getPathData(e, context); if (data.data === UNKNOWN) return UNKNOWN_PATH_DATA; expressions.push(data.data); } const strings = node.quasi.quasis.map((q) => q.value.cooked); strings.raw = node.quasi.quasis.map((q) => q.value.raw); return { data: String.raw(strings, ...expressions), children: EMPTY_MAP }; }, UpdateExpression() { return UNKNOWN_PATH_DATA; }, ThisExpression() { return UNKNOWN_PATH_DATA; }, FunctionExpression() { return UNKNOWN_PATH_DATA; }, ArrowFunctionExpression() { return UNKNOWN_PATH_DATA; }, YieldExpression() { return UNKNOWN_PATH_DATA; }, ClassExpression() { return UNKNOWN_PATH_DATA; }, MetaProperty() { return UNKNOWN_PATH_DATA; }, AwaitExpression() { return UNKNOWN_PATH_DATA; }, ChainExpression() { return UNKNOWN_PATH_DATA; } }; /** * Get path data */ function getPathData(node, context) { const visitor = VISITORS[node.type]; if (visitor) return visitor(node, context); return UNKNOWN_PATH_DATA; } //#endregion //#region src/utils/http-client/sync-http.ts const require$1 = createRequire(import.meta.url); const ext = path.extname(fileURLToPath(import.meta.url)); const getSync = createSyncFn(require$1.resolve(`./worker${ext}`)); /** * Synchronously GET Method */ function syncGet(url, options, httpModulePath) { return getSync(url, options, httpModulePath); } //#endregion //#region package.json var name$1 = "eslint-plugin-json-schema-validator"; var version$1 = "6.2.0"; //#endregion //#region src/meta.ts var meta_exports = /* @__PURE__ */ __exportAll({ name: () => name, version: () => version }); const name = name$1; const version = version$1; //#endregion //#region src/utils/schema.ts const debug = debugBuilder("eslint-plugin-json-schema-validator:utils-schema"); const TTL = 1e3 * 60 * 60 * 24; const RELOADING = /* @__PURE__ */ new Set(); /** * Load schema data */ function loadSchema(schemaPath, context) { return loadJsonInternal(schemaPath, context, (schema_) => { const schema = schema_; draft7(schema); return schema; }); } /** * Load json data */ function loadJson(jsonPath, context) { return loadJsonInternal(jsonPath, context); } /** * Load json data. Can insert a data editing process. */ function loadJsonInternal(jsonPath, context, edit) { if (jsonPath.startsWith("http://") || jsonPath.startsWith("https://")) return loadJsonFromURL(normalizeSchemaUrl(jsonPath), context, edit); if (jsonPath.startsWith("vscode://")) { let url = `https://raw.githubusercontent.com/ota-meshi/extract-vscode-schemas/main/resources/vscode/${jsonPath.slice(9)}`; if (!url.endsWith(".json")) url = `${url}.json`; return loadJsonFromURL(url, context, (orig) => { const result = edit?.(orig) ?? orig; if (jsonPath === "vscode://schemas/settings/machine") { const target = result?.properties?.["workbench.externalUriOpeners"]?.additionalProperties?.anyOf; removeEmptyEnum(target); } else if (jsonPath === "vscode://schemas/launch") { const target = result?.properties?.compounds?.items?.properties?.configurations?.items?.oneOf; removeEmptyEnum(target); } return result; }); } const json = fs.readFileSync(path.resolve(context.cwd, jsonPath), "utf-8"); const data = JSON.parse(json); return edit ? edit(data) : data; } /** * Normalize schema URL to use the official schemastore domain. */ function normalizeSchemaUrl(url) { for (const prefix of ["https://json.schemastore.org/", "http://json.schemastore.org/"]) if (url.startsWith(prefix)) return `https://www.schemastore.org/${url.slice(prefix.length)}`; return url; } /** remove empty `enum:` schema */ function removeEmptyEnum(target) { if (!target) return; if (Array.isArray(target)) { for (const e of target) removeEmptyEnum(e); return; } if (Array.isArray(target.enum) && target.enum.length === 0) { delete target.enum; return; } if (target.type === "object" && target.properties && typeof target.properties === "object") for (const key of Object.keys(target.properties)) removeEmptyEnum(target.properties[key]); } /** * Load schema data from url */ function loadJsonFromURL(jsonPath, context, edit) { let jsonFileName = jsonPath.replace(/^https?:\/\//u, ""); if (!jsonFileName.endsWith(".json")) jsonFileName = `${jsonFileName}.json`; const jsonFilePath = path.join(dirname(fileURLToPath(import.meta.url)), `../.cached_schemastore/${jsonFileName}`); const options = context.settings?.["json-schema-validator"]?.http; const httpRequestOptions = options?.requestOptions ?? {}; const httpGetModulePath = resolvePath(options?.getModulePath, context); fs.mkdirSync(path.dirname(jsonFilePath), { recursive: true }); let data, timestamp; try { ({data, timestamp} = __require(`../.cached_schemastore/${jsonFileName}`)); } catch { try { const jsonText = fs.readFileSync(jsonFilePath, "utf-8"); ({data, timestamp} = JSON.parse(jsonText)); } catch {} } if (data != null && typeof timestamp === "number") { if (timestamp + TTL < Date.now()) { if (!RELOADING.has(jsonFilePath)) { RELOADING.add(jsonFilePath); get(jsonPath, httpRequestOptions, httpGetModulePath).then((json) => { postProcess(jsonPath, jsonFilePath, json, context, edit); RELOADING.delete(jsonFilePath); }); } } return data; } let json; try { json = syncGet(jsonPath, httpRequestOptions, httpGetModulePath); } catch (e) { debug(e.message); return null; } return postProcess(jsonPath, jsonFilePath, json, context, edit); } /** * Post process */ function postProcess(schemaUrl, jsonFilePath, json, context, edit) { let data; try { data = JSON.parse(json); } catch { context.report({ loc: { line: 1, column: 0 }, message: `Could not be parsed JSON: "${schemaUrl}"` }); return null; } if (edit) data = edit(data); fs.writeFileSync(jsonFilePath, schemaStringify({ data, timestamp: Date.now(), v: version })); if (typeof __require !== "undefined") delete __require.cache[jsonFilePath]; return data; } /** * JSON Schema to string */ function schemaStringify(schema) { return JSON.stringify(schema, (_key, value) => { return value; }); } /** * Resolve module path */ function resolvePath(modulePath, context) { if (!modulePath) return; if (modulePath.startsWith(".")) return path.join(context.cwd, modulePath); return modulePath; } //#endregion //#region src/utils/ajv.ts var ajv_default = Ajv; //#endregion //#region src/utils/validator-factory.ts const lazyRegExpEngine = (str, flags) => { let error; try { return new RegExp(str, flags); } catch (e) { error = e; } if (flags.includes("u")) return new RegExp(str, flags.replace("u", "")); throw error; }; lazyRegExpEngine.code = "new RegExp"; const ajv = new ajv_default({ allErrors: true, verbose: true, validateSchema: false, logger: false, strict: false, code: { regExp: lazyRegExpEngine } }); ajv.addMetaSchema(v6Schema); /** @see https://github.com/ajv-validator/ajv/blob/e816cd24b60068b3937dc7143beeab3fe6612391/lib/compile/util.ts#L59 */ function unescapeFragment(str) { return unescapeJsonPointer(decodeURIComponent(str)); } /** @see https://github.com/ajv-validator/ajv/blob/e816cd24b60068b3937dc7143beeab3fe6612391/lib/compile/util.ts#L72 */ function unescapeJsonPointer(str) { return str.replace(/~1/g, "/").replace(/~0/g, "~"); } /** * Compile JSON Schema */ function compile(schema, schemaPath, context) { return schemaToValidator(schema, schemaPath, context); } /** * Build validator */ function schemaToValidator(schema, schemaPath, context) { let validateSchema; let schemaObject = schema; while (true) { try { if (typeof schemaObject.$id === "string" && ajv.getSchema(schemaObject.$id.replace(/#$/u, ""))) ajv.removeSchema(schemaObject.$id.replace(/#$/u, "")); validateSchema = ajv.compile(schemaObject); } catch (e) { if ((e.message === "NOT SUPPORTED: keyword \"id\", use \"$id\" for schema ID" || /exclusive(?:Maximum|Minimum) value must be .*"number".*/u.test(e.message)) && schema === schemaObject) { schemaObject = JSON.parse(JSON.stringify(schemaObject)); draft7(schemaObject); continue; } if (resolveError(e, schemaPath, schemaObject, context)) continue; console.error(schemaPath); throw e; } break; } return (data) => { if (validateSchema(data)) return []; return validateSchema.errors.map(errorToValidateError); }; } /** * Resolve Schema Error */ function resolveError(error, baseSchemaPath, baseSchema, context) { if (error.missingRef) { let schemaPath = ""; let schemaId = ""; if (error.missingRef.startsWith("http://") || error.missingRef.startsWith("https://") || error.missingRef.startsWith("vscode://")) { const uri = new URL(error.missingRef); uri.hash = ""; schemaPath = uri.toString(); schemaId = schemaPath; } else { const ref = error.missingRef; const baseUri = new URL(baseSchema.$id || baseSchemaPath); baseUri.hash = ""; const slashIndex = baseUri.pathname.lastIndexOf("/"); if (slashIndex >= 0) baseUri.pathname = baseUri.pathname.slice(0, slashIndex + 1); const uri = new URL(`${baseUri.toString()}${ref}`); uri.hash = ""; schemaPath = uri.toString(); schemaId = ref.split("#")[0]; } if (schemaPath) { const refSchema = loadSchema(schemaPath, context); if (refSchema) { while (true) { try { ajv.addSchema(refSchema, schemaId); } catch (e) { if (resolveError(e, schemaPath, refSchema, context)) continue; throw e; } break; } return true; } } } return false; } /** * Schema error to validate error. */ function errorToValidateError(errorObject) { const error = errorObject; const instancePath = error.instancePath.startsWith("/") ? error.instancePath.slice(1) : error.instancePath; const path = instancePath ? instancePath.split("/").map(unescapeFragment) : []; if (error.keyword === "additionalProperties") { path.push(error.params.additionalProperty); return { message: `Unexpected property ${joinPath(path)}`, path }; } if (error.keyword === "propertyNames") return { message: `${joinPath(path)} property name ${JSON.stringify(error.params.propertyName)} is invalid.`, path: [...path, error.params.propertyName] }; if (error.keyword === "uniqueItems") { const baseMessage = `must NOT have duplicate items (items ## ${error.params.j} and ${error.params.i} are identical)`; return { message: `${joinPath(path)} ${baseMessage}.`, path: [...path, String(error.params.i)] }; } let baseMessage; if (error.keyword === "enum") baseMessage = `must be equal to ${joinEnums(error.params.allowedValues)}`; else if (error.keyword === "const") baseMessage = `must be equal to ${JSON.stringify(error.params.allowedValue)}`; else if (error.keyword === "not") { const schema = error.schema; const schemaKeys = Object.keys(schema); if (schemaKeys.length === 1 && schemaKeys[0] === "type") baseMessage = `must NOT be ${schema.type}`; else if (schemaKeys.length === 1 && schemaKeys[0] === "enum") baseMessage = `must NOT be equal to ${joinEnums(schema.enum)}`; else baseMessage = `must NOT be valid of define schema`; } else if (error.keyword === "type" || error.keyword === "oneOf" || error.keyword === "anyOf" || error.keyword === "minItems" || error.keyword === "maxItems" || error.keyword === "additionalItems" || error.keyword === "contains" || error.keyword === "required" || error.keyword === "maxProperties" || error.keyword === "minProperties" || error.keyword === "dependencies" || error.keyword === "pattern" || error.keyword === "maxLength" || error.keyword === "minLength" || error.keyword === "format" || error.keyword === "maximum" || error.keyword === "minimum" || error.keyword === "exclusiveMaximum" || error.keyword === "exclusiveMinimum" || error.keyword === "multipleOf" || error.keyword === "if") baseMessage = error.message; else baseMessage = error.message; if (error.propertyName) return { message: `${joinPath(path)} property name ${JSON.stringify(error.propertyName)} ${baseMessage}.`, path: [...path, error.propertyName] }; return { message: `${joinPath(path)} ${baseMessage}.`, path }; /** Join enums */ function joinEnums(enums) { const list = enums.map((v) => JSON.stringify(v)); const last = list.pop(); if (list.length) return `${list.join(", ")} or ${last}`; return last; } /** Join paths */ function joinPath(paths) { if (!paths.length) return "Root"; let result = ""; for (const p of paths) if (/^[$a-z_][\w$]*$/iu.test(p)) if (result) result += `.${p}`; else result = p; else result += `[${/^\d+$/u.test(p) ? p : JSON.stringify(p)}]`; return `"${result}"`; } } //#endregion //#region src/rules/no-invalid.ts const CATALOG_URL = "https://www.schemastore.org/api/json/catalog.json"; /** * Checks if match file */ function matchFile(filename, fileMatch) { return fileMatch.includes(path.basename(filename)) || fileMatch.some((fm) => minimatch(filename, fm, { dot: true })); } /** * Generate validator from schema path */ function schemaPathToValidator(schemaPath, context) { const schema = loadSchema(schemaPath, context); if (!schema) return null; return compile(schema, schemaPath, context); } /** * Generate validator from schema object */ function schemaObjectToValidator(schema, context) { if (!schema) return null; const schemaPath = context.cwd; return compile(schema, schemaPath, context); } /** * Report for cannot resolved schema path */ function reportCannotResolvedPath(schemaPath, context) { context.report({ loc: { line: 1, column: 0 }, message: `Specified schema could not be resolved. Path: "${schemaPath}"` }); } /** * Report for cannot resolved schema object */ function reportCannotResolvedObject(context) { context.report({ loc: { line: 1, column: 0 }, message: `Specified schema could not be resolved.` }); } const SCHEMA_KINDS = [ "$schema", "options", "catalog" ]; /** Get mergeSchemas option */ function parseMergeSchemasOption(option) { return option === true ? SCHEMA_KINDS : Array.isArray(option) ? [...option].sort((a, b) => SCHEMA_KINDS.indexOf(a) - SCHEMA_KINDS.indexOf(b)) : null; } var no_invalid_default = createRule("no-invalid", { meta: { docs: { description: "validate object with JSON Schema.", categories: ["recommended"], default: "warn" }, fixable: void 0, schema: [{ oneOf: [{ type: "string" }, { type: "object", properties: { schemas: { type: "array", items: { type: "object", properties: { name: { type: "string" }, description: { type: "string" }, fileMatch: { type: "array", items: { type: "string" }, minItems: 1 }, schema: { type: ["object", "string"] } }, additionalProperties: true, required: ["fileMatch", "schema"] } }, useSchemastoreCatalog: { type: "boolean" }, mergeSchemas: { oneOf: [{ type: "boolean" }, { type: "array", items: { type: "string", enum: [ "$schema", "catalog", "options" ] }, minItems: 2, uniqueItems: true }] } }, additionalProperties: false }] }], messages: {}, type: "suggestion" }, create: toCompatCreate((context, { filename }) => { const sourceCode = context.sourceCode; const cwd = context.cwd; const validator = createValidator(context, filename.startsWith(cwd) ? path.relative(cwd, filename) : filename); if (!validator) return {}; let existsExports = false; /** * Validate JSON Schema */ function validateData(data, resolveLoc) { const errors = validator(data); for (const error of errors) { const loc = resolveLoc(error); if (!loc) continue; context.report({ loc, message: error.message }); } } /** * Validate JS Object */ function validateJSExport(node, rootRange) { if (existsExports) return; existsExports = true; const data = analyzeJsAST(node, rootRange, context); if (data == null) return; validateData(data.object, (error) => { let target = data.pathData; for (const p of error.path) { const next = target?.children.get(p); target = typeof next === "symbol" ? void 0 : next; } const key = target?.key; const range = typeof key === "function" ? key(sourceCode) : key; if (!range) return null; return { start: sourceCode.getLocFromIndex(range[0]), end: sourceCode.getLocFromIndex(range[1]) }; }); } /** Find schema path from program */ function findSchemaPathFromJSON(node) { const rootExpr = node.body[0].expression; if (rootExpr.type !== "JSONObjectExpression") return null; for (const prop of rootExpr.properties) { if (prop.computed || (prop.key.type === "JSONIdentifier" ? prop.key.name : prop.key.value) !== "$schema") continue; return getStaticJSONValue(prop.value); } return null; } return { Program(node) { if (sourceCode.parserServices.isJSON) { const program = node; validateData(getStaticJSONValue(program), (error) => { return errorDataToLoc(getJSONNodeFromPath(program, error.path)); }); } else if (sourceCode.parserServices.isYAML) { const program = node; validateData(getStaticYAMLValue(program), (error) => { return errorDataToLoc(getYAMLNodeFromPath(program, error.path)); }); } else if (sourceCode.parserServices.isTOML) { const program = node; validateData(getStaticTOMLValue(program), (error) => { return errorDataToLoc(getTOMLNodeFromPath(program, error.path)); }); } }, ExportDefaultDeclaration(node) { if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration" || node.declaration.type === "VariableDeclaration") return; const defaultToken = sourceCode.getTokenBefore(node.declaration); validateJSExport(node.declaration, [node.range[0], defaultToken.range[1]]); }, AssignmentExpression(node) { if (node.left.type === "Identifier" && node.left.name === "exports" || node.left.type === "MemberExpression" && node.left.object.type === "Identifier" && node.left.object.name === "module" && node.left.computed === false && node.left.property.type === "Identifier" && node.left.property.name === "exports") validateJSExport(node.right, node.left.range); } }; /** * ErrorData to report location. */ function errorDataToLoc(errorData) { if (errorData.key) { const range = errorData.key(sourceCode); return { start: sourceCode.getLocFromIndex(range[0]), end: sourceCode.getLocFromIndex(range[1]) }; } return errorData.value.loc; } /** Find schema path from program */ function findSchemaPathFromYAML(node) { const rootExpr = node.body[0]?.content; if (!rootExpr || rootExpr.type !== "YAMLMapping") return null; for (const pair of rootExpr.pairs) { if (!pair.key || !pair.value || pair.key.type !== "YAMLScalar" || pair.key.value !== "$schema") continue; return getStaticYAMLValue(pair.value); } return null; } /** Find schema path from program */ function findSchemaPathFromTOML(node) { const rootExpr = node.body[0]; for (const body of rootExpr.body) { if (body.type !== "TOMLKeyValue" || body.key.keys.length !== 1) continue; const keyNode = body.key.keys[0]; if ((keyNode.type === "TOMLBare" ? keyNode.name : keyNode.value) !== "$schema") continue; return getStaticTOMLValue(body.value); } return null; } /** Find schema path from program */ function findSchemaPath(node) { let $schema = null; if (sourceCode.parserServices.isJSON) $schema = findSchemaPathFromJSON(node); else if (sourceCode.parserServices.isYAML) $schema = findSchemaPathFromYAML(node); else if (sourceCode.parserServices.isTOML) $schema = findSchemaPathFromTOML(node); return typeof $schema === "string" ? $schema.startsWith(".") ? path.resolve(path.dirname(typeof context.getPhysicalFilename === "function" ? context.getPhysicalFilename() : getPhysicalFilename(context.filename)), $schema) : $schema : null; } /** Validator from $schema */ function get$SchemaValidators(context) { const $schemaPath = findSchemaPath(sourceCode.ast); if (!$schemaPath) return null; const validator = schemaPathToValidator($schemaPath, context); if (!validator) { reportCannotResolvedPath($schemaPath, context); return null; } return [validator]; } /** Validator from catalog.json */ function getCatalogValidators(context, relativeFilename) { if (!((context.options[0] || {}).useSchemastoreCatalog !== false)) return null; const catalog = loadJson(CATALOG_URL, context); if (!catalog) return null; const validators = []; for (const schemaData of catalog.schemas) { if (!schemaData.fileMatch) continue; if (schemaData.fileMatch.some((s) => /^\*\.json$/u.test(s))) continue; if (!matchFile(relativeFilename, schemaData.fileMatch)) continue; const validator = schemaPathToValidator(schemaData.url, context); if (validator) validators.push(validator); } return validators.length ? validators : null; } /** Validator from options.schemas */ function getOptionsValidators(context, filename) { const option = context.options[0]; if (typeof option === "string") { const validator = schemaPathToValidator(option, context); return validator ? [validator] : null; } if (typeof option !== "object" || !Array.isArray(option.schemas)) return null; const validators = []; for (const schemaData of option.schemas) { if (!matchFile(filename, schemaData.fileMatch)) continue; if (typeof schemaData.schema === "string") { const validator = schemaPathToValidator(schemaData.schema, context); if (validator) validators.push(validator); else reportCannotResolvedPath(schemaData.schema, context); } else { const validator = schemaObjectToValidator(schemaData.schema, context); if (validator) validators.push(validator); else reportCannotResolvedObject(context); } } return validators.length ? validators : null; } /** Create combined validator */ function createValidator(context, filename) { const mergeSchemas = parseMergeSchemasOption(context.options[0]?.mergeSchemas); const validatorsCtx = createValidatorsContext(context, filename); if (mergeSchemas && mergeSchemas.some((kind) => validatorsCtx[kind])) { const validators = []; for (const kind of mergeSchemas) { const v = validatorsCtx[kind]; if (v) validators.push(...v); } return margeValidators(validators); } const validators = validatorsCtx.$schema || validatorsCtx.options || validatorsCtx.catalog; if (!validators) return null; return margeValidators(validators); /** Marge validators */ function margeValidators(validators) { return (data) => validators.reduce((errors, validator) => [...errors, ...validator(data)], []); } } /** Creates validators context */ function createValidatorsContext(context, filename) { let $schema = null; let options = null; let catalog = null; /** * Get a validator. Returns the value of the cache if there is one. * If there is no cache, cache and return the value obtained from the supplier function */ function get(cache, setCache, supplier) { if (cache) return cache.validators; const v = supplier(); setCache({ validators: v }); return v; } return { get $schema() { return get($schema, (c) => $schema = c, () => get$SchemaValidators(context)); }, get options() { return get(options, (c) => options = c, () => getOptionsValidators(context, filename)); }, get catalog() { return get(catalog, (c) => catalog = c, () => getCatalogValidators(context, filename)); } }; } }) }); /** * ! copied from https://github.com/mdx-js/eslint-mdx/blob/b97db2e912a416d5d40ddb78ab6c9fa1ab150c17/packages/eslint-mdx/src/helpers.ts#L28-L50 * * Given a filepath, get the nearest path that is a regular file. * The filepath provided by eslint may be a virtual filepath rather than a file * on disk. This attempts to transform a virtual path into an on-disk path */ function getPhysicalFilename(filename, child) { try { if (fs.statSync(filename).isDirectory()) return child || filename; } catch (err) { const { code } = err; if (code === "ENOTDIR" || code === "ENOENT") return getPhysicalFilename(path.dirname(filename), filename); } return filename; } //#endregion //#region src/utils/rules.ts const rules$1 = [no_invalid_default]; //#endregion //#region src/configs/flat/base.ts var base_default = [ { plugins: { get "json-schema-validator"() { return src_default; } } }, { files: [ "*.json", "**/*.json", "*.json5", "**/*.json5", "*.jsonc", "**/*.jsonc" ], languageOptions: { parser: jsoncESLintParser }, rules: { strict: "off", "no-unused-expressions": "off", "no-unused-vars": "off" } }, { files: [ "*.yaml", "**/*.yaml", "*.yml", "**/*.yml" ], languageOptions: { parser: yamlESLintParser }, rules: { "no-irregular-whitespace": "off", "no-unused-vars": "off", "spaced-comment": "off" } }, { files: ["*.toml", "**/*.toml"], languageOptions: { parser: tomlESLintParser }, rules: { "no-irregular-whitespace": "off", "spaced-comment": "off" } } ]; //#endregion //#region src/configs/flat/recommended.ts var recommended_default = [...base_default, { rules: { "json-schema-validator/no-invalid": "warn" } }]; //#endregion //#region src/index.ts const configs = { base: base_default, recommended: recommended_default, "flat/base": base_default, "flat/recommended": rec