src/generators/template/expressions/text.js
import {
BINDING_CHILD_NODE_INDEX_KEY,
BINDING_EVALUATE_KEY,
BINDING_TYPE_KEY,
EXPRESSION_TYPES,
TEXT_EXPRESSION_TYPE,
} from '../constants.js'
import {
createArrayString,
transformExpression,
wrapASTInFunctionWithScope,
} from '../utils.js'
import {
nullNode,
simplePropertyNode,
} from '../../../utils/custom-ast-nodes.js'
import { builders } from '../../../utils/build-types.js'
import encodeHTMLEntities from '../../../utils/html-entities/encode.js'
import { isCommentString } from '../checks.js'
import { isLiteral } from '../../../utils/ast-nodes-checks.js'
import trimEnd from '../../../utils/trim-end.js'
import trimStart from '../../../utils/trim-start.js'
import unescapeChar from '../../../utils/unescape-char.js'
/**
* Generate the pure immutable string chunks from a RiotParser.Node.Text
* @param {RiotParser.Node.Text} node - riot parser text node
* @param {string} sourceCode sourceCode - source code
* @returns {Array} array containing the immutable string chunks
*/
function generateLiteralStringChunksFromNode(node, sourceCode) {
return (
node.expressions
.reduce((chunks, expression, index) => {
const start = index ? node.expressions[index - 1].end : node.start
const string = encodeHTMLEntities(
sourceCode.substring(start, expression.start),
)
// trimStart the first string
chunks.push(index === 0 ? trimStart(string) : string)
// add the tail to the string
if (index === node.expressions.length - 1)
chunks.push(
encodeHTMLEntities(
trimEnd(sourceCode.substring(expression.end, node.end)),
),
)
return chunks
}, [])
// comments are not supported here
.filter((str) => !isCommentString(str))
.map((str) => (node.unescape ? unescapeChar(str, node.unescape) : str))
)
}
/**
* Simple bindings might contain multiple expressions like for example: "{foo} and {bar}"
* This helper aims to merge them in a template literal if it's necessary
* @param {RiotParser.Node} node - riot parser node
* @param {string} sourceFile - original tag file
* @param {string} sourceCode - original tag source code
* @returns { Object } a template literal expression object
*/
export function mergeNodeExpressions(node, sourceFile, sourceCode) {
if (node.parts.length === 1)
return transformExpression(node.expressions[0], sourceFile, sourceCode)
const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode)
const stringsArray = pureStringChunks
.reduce((acc, str, index) => {
const expr = node.expressions[index]
return [
...acc,
builders.literal(str),
expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode(),
]
}, [])
// filter the empty literal expressions
.filter((expr) => !isLiteral(expr) || expr.value)
return createArrayString(stringsArray)
}
/**
* Create a text expression
* @param {RiotParser.Node.Text} sourceNode - text node to parse
* @param {string} sourceFile - source file path
* @param {string} sourceCode - original source
* @param {number} childNodeIndex - position of the child text node in its parent children nodes
* @returns {AST.Node} object containing the expression binding keys
*/
export default function createTextExpression(
sourceNode,
sourceFile,
sourceCode,
childNodeIndex,
) {
return builders.objectExpression([
simplePropertyNode(
BINDING_TYPE_KEY,
builders.memberExpression(
builders.identifier(EXPRESSION_TYPES),
builders.identifier(TEXT_EXPRESSION_TYPE),
false,
),
),
simplePropertyNode(
BINDING_CHILD_NODE_INDEX_KEY,
builders.literal(childNodeIndex),
),
simplePropertyNode(
BINDING_EVALUATE_KEY,
wrapASTInFunctionWithScope(
mergeNodeExpressions(sourceNode, sourceFile, sourceCode),
),
),
])
}