eslint-plugin-json-schema-validator
Version:
ESLint plugin that validates data using JSON Schema Validator.
412 lines (411 loc) • 13.4 kB
JavaScript
"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;
}