UNPKG

eslint-plugin-json-schema-validator

Version:
412 lines (411 loc) 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.analyzeJsAST = void 0; const utils_1 = require("./utils"); const utils_2 = require("./utils"); const UNKNOWN = Symbol("unknown value"); const EMPTY_MAP = Object.freeze(new Map()); const UNKNOWN_PATH_DATA = { data: UNKNOWN, children: EMPTY_MAP }; function analyzeJsAST(node, rootRange, context) { const data = getPathData(node, context); if (data.data === UNKNOWN) { return null; } const pathData = Object.assign({ key: rootRange }, data); const result = { object: data.data, pathData, }; return result; } exports.analyzeJsAST = analyzeJsAST; const CALC_UNARY = { "+": (v) => Number(v), "-": (v) => -v, "!": (v) => !v, "~": (v) => ~v, typeof: (v) => typeof v, void: () => undefined, 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) => Math.pow(v1, v2), }; const VISITORS = { ObjectExpression(node, context) { const data = {}; const children = new Map(); for (const prop of node.properties) { if (prop.type === "Property") { const keyName = utils_2.getStaticPropertyName(prop, context); if (keyName != null) { const propData = getPathData(prop.value, context); if (propData.data !== UNKNOWN) { data[keyName] = propData.data; children.set(keyName, Object.assign({ 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 = 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), Object.assign({ key: element.range }, propData)); } else { data[index] = UNKNOWN; children.set(String(index), UNKNOWN); } } } else { data[index] = undefined; 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: undefined, children: EMPTY_MAP, }); } } return { data, children, }; }, Identifier(node, context) { const init = utils_1.findInitNode(context, node); if (init == null) { const evalData = utils_1.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; 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 = utils_2.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; } const data = calc(argData.data); return { data, children: EMPTY_MAP, }; }, BinaryExpression(node, context) { 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; } const data = calc(leftData.data, rightData.data); return { 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; } const rightData = getPathData(node.right, context); return rightData; }, AssignmentExpression(node, context) { const rightData = getPathData(node.right, context); return rightData; }, 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 = utils_2.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 = utils_1.getStaticValue(context, node); if (!evalData) { return UNKNOWN_PATH_DATA; } return { data: evalData.value, children: EMPTY_MAP, }; }, NewExpression(node, context) { const evalData = utils_1.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_PATH_DATA; } expressions.push(data.data); } let data = node.quasis[0].value.cooked; for (let i = 0; i < expressions.length; ++i) { data += expressions[i]; data += node.quasis[i + 1].value.cooked; } 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); const data = String.raw(strings, ...expressions); return { data, 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; }, }; function getPathData(node, context) { const visitor = VISITORS[node.type]; if (visitor) { return visitor(node, context); } return UNKNOWN_PATH_DATA; }