UNPKG

twing

Version:

First-class Twig engine for Node.js

141 lines (140 loc) 5.42 kB
"use strict"; 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;