twing
Version:
First-class Twig engine for Node.js
141 lines (140 loc) • 5.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEscaperNodeVisitor = void 0;
const print_1 = require("../node/print");
const do_1 = require("../node/do");
const conditional_1 = require("../node/expression/conditional");
const escape_1 = require("../node/expression/escape");
const node_visitor_1 = require("../node-visitor");
const createEscaperNodeVisitor = () => {
const safes = new Map();
const statusStack = [];
let blocks = new Map();
const analyze = (node) => {
let isSafe = safes.get(node);
if (isSafe === undefined) {
if (node.type === "constant") {
// constants are safe by definition
isSafe = true;
}
else if (node.type === "block_function") {
// blocks function is safe by definition
isSafe = true;
}
else if (node.type === "parent_function") {
// parent function is safe by definition
isSafe = true;
}
else if (node.type === "conditional") {
// intersect safeness of both operands
const { expr2, expr3 } = node.children;
isSafe = intersectSafe(analyze(expr2), analyze(expr3));
}
else {
isSafe = node.type === "method_call";
}
safes.set(node, isSafe);
}
return isSafe;
};
const intersectSafe = (a, b) => {
return a && b;
};
const needEscaping = () => {
if (statusStack.length) {
return statusStack[statusStack.length - 1];
}
return false;
};
const getEscapeNode = (type, node) => {
return (0, escape_1.createEscapeNode)(node, type);
};
const enterNode = (node) => {
if (node.type === "template") {
blocks = new Map();
}
else if (node.type === "auto_escape") {
const { strategy } = node.attributes;
statusStack.push(strategy);
}
else if (node.type === "block") {
const blockStatus = blocks.get(node.attributes.name);
statusStack.push(blockStatus !== undefined ? blockStatus : needEscaping());
}
return node;
};
const leaveNode = (node) => {
if (node.type === "template") {
blocks = new Map();
}
else if (node.type === "print") {
const type = needEscaping();
if (type !== false) {
const expression = node.children.expression;
if ((expression.type === "conditional" || expression.type === "nullish_coalescing") && shouldUnwrapConditional(expression)) {
return (0, do_1.createDoNode)(unwrapConditional(expression, type), expression.line, expression.column, null);
}
return escapePrintNode(node, type);
}
}
if (node.type === "auto_escape" || node.type === "block") {
statusStack.pop();
if (node.type === "auto_escape") {
return node.children.body;
}
}
else if (node.type === "block_reference") {
blocks.set(node.attributes.name, needEscaping());
}
return node;
};
const shouldUnwrapConditional = (expression) => {
const { expr2, expr3 } = expression.children;
const expr2IsSafe = isSafe(expr2);
const expr3IsSafe = isSafe(expr3);
return expr2IsSafe !== expr3IsSafe;
};
const unwrapConditional = (expression, type) => {
// convert "echo a ? b : c" to "a ? echo b : echo c" recursively
let { children } = expression;
let expr1 = children.expr1;
let expr2 = children.expr2;
let expr3 = children.expr3;
const wrapPrintNodeExpression = (node) => {
const { expression } = node.children;
if (isSafe(expression)) {
return node;
}
return (0, print_1.createPrintNode)(getEscapeNode(type, expression), node.line, node.column);
};
if (expr2.type === "conditional" && shouldUnwrapConditional(expr2)) {
expr2 = unwrapConditional(expr2, type);
}
else {
expr2 = wrapPrintNodeExpression((0, print_1.createPrintNode)(expr2, expr2.line, expr2.column));
}
if (expr3.type === "conditional" && shouldUnwrapConditional(expr3)) {
expr3 = unwrapConditional(expr3, type);
}
else {
expr3 = wrapPrintNodeExpression((0, print_1.createPrintNode)(expr3, expr3.line, expr3.column));
}
return (0, conditional_1.createConditionalNode)(expr1, expr2, expr3, expression.line, expression.column);
};
const escapePrintNode = (node, type) => {
const { expression } = node.children;
if (isSafe(expression)) {
return node;
}
return (0, print_1.createPrintNode)(getEscapeNode(type, expression), node.line, node.column);
};
const isSafe = (expression) => {
let safe = safes.get(expression);
if (safe === undefined) {
safe = analyze(expression);
}
return safe;
};
return (0, node_visitor_1.createNodeVisitor)(enterNode, leaveNode);
};
exports.createEscaperNodeVisitor = createEscaperNodeVisitor;