mirror of
https://gitlab.com/nightlycommit/twing.git
synced 2025-01-18 08:46:50 +02:00
Resolve issue #609
This commit is contained in:
parent
77727ba1ad
commit
e55bb6a14c
@ -47,7 +47,6 @@ export type {TwingApplyNode, TwingApplyNodeAttributes, TwingApplyNodeChildren} f
|
||||
export type {TwingAutoEscapeNode, TwingAutoEscapeNodeAttributes} from "./lib/node/auto-escape";
|
||||
export type {TwingBlockNode, TwingBlockNodeAttributes} from "./lib/node/block";
|
||||
export type {TwingBlockReferenceNode, TwingBlockReferenceNodeAttributes} from "./lib/node/block-reference";
|
||||
export type {TwingBodyNode} from "./lib/node/body";
|
||||
export type {TwingCheckSecurityNode, TwingCheckSecurityNodeAttributes} from "./lib/node/check-security";
|
||||
export type {TwingCheckToStringNode} from "./lib/node/check-to-string";
|
||||
export type {TwingCommentNode, TwingCommentNodeAttributes} from "./lib/node/comment";
|
||||
@ -75,13 +74,11 @@ export type {TwingTextNode, TwingBaseTextNode, TwingBaseTextNodeAttributes} from
|
||||
export type {TwingTraitNode} from "./lib/node/trait";
|
||||
export type {TwingVerbatimNode} from "./lib/node/verbatim";
|
||||
export type {TwingWithNode, TwingWithNodeAttributes, TwingWithNodeChildren} from "./lib/node/with";
|
||||
export type {TwingWrapperNode, TwingWrapperNodeChildren} from "./lib/node/wrapper";
|
||||
|
||||
export {createApplyNode} from "./lib/node/apply";
|
||||
export {createAutoEscapeNode} from "./lib/node/auto-escape";
|
||||
export {createBlockNode} from "./lib/node/block";
|
||||
export {createBlockReferenceNode} from "./lib/node/block-reference";
|
||||
export {createBodyNode} from "./lib/node/body";
|
||||
export {createCheckSecurityNode} from "./lib/node/check-security";
|
||||
export {createCheckToStringNode} from "./lib/node/check-to-string";
|
||||
export {createCommentNode} from "./lib/node/comment";
|
||||
@ -104,7 +101,6 @@ export {createTextNode} from "./lib/node/text";
|
||||
export {createTraitNode} from "./lib/node/trait";
|
||||
export {createVerbatimNode} from "./lib/node/verbatim";
|
||||
export {createWithNode} from "./lib/node/with";
|
||||
export {createWrapperNode} from "./lib/node/wrapper";
|
||||
|
||||
// node/expression
|
||||
export type {TwingBaseArrayNode, TwingArrayNode} from "./lib/node/expression/array";
|
||||
@ -237,6 +233,9 @@ export type {TwingIncludeNode, TwingIncludeNodeChildren} from "./lib/node/includ
|
||||
export {createEmbedNode} from "./lib/node/include/embed";
|
||||
export {createIncludeNode} from "./lib/node/include/include";
|
||||
|
||||
// node executors
|
||||
export {executeNode, type TwingNodeExecutor} from "./lib/node-executor";
|
||||
|
||||
// tag handlers
|
||||
export type {TwingTagHandler, TwingTokenParser} from "./lib/tag-handler";
|
||||
|
||||
@ -308,7 +307,7 @@ export {createExtensionSet} from "./lib/extension-set";
|
||||
export {createFilter} from "./lib/filter";
|
||||
export {createFunction} from "./lib/function";
|
||||
export {createLexer} from "./lib/lexer";
|
||||
export {createBaseNode, getChildren, getChildrenCount} from "./lib/node";
|
||||
export {createBaseNode, createNode, getChildren, getChildrenCount} from "./lib/node";
|
||||
export {createOperator} from "./lib/operator";
|
||||
export {createSandboxSecurityPolicy} from "./lib/sandbox/security-policy";
|
||||
export {createSource} from "./lib/source";
|
||||
|
@ -345,9 +345,7 @@ export const createEnvironment = (
|
||||
},
|
||||
leaveNode: (node) => {
|
||||
if (node.type === "template") {
|
||||
const autoEscapeNode = createAutoEscapeNode(strategy, node.children.body.children.content, node.line, node.column, 'foo');
|
||||
|
||||
node.children.body.children.content = autoEscapeNode;
|
||||
node.children.body = createAutoEscapeNode(strategy, node.children.body, node.line, node.column);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -3,6 +3,7 @@ import type {TwingContext} from "./context";
|
||||
import type {TwingOutputBuffer} from "./output-buffer";
|
||||
import type {TwingSourceMapRuntime} from "./source-map-runtime";
|
||||
import type {TwingNumberFormat} from "./environment";
|
||||
import type {TwingNodeExecutor} from "./node-executor";
|
||||
|
||||
export type TwingExecutionContext = {
|
||||
aliases: TwingTemplateAliases;
|
||||
@ -12,6 +13,7 @@ export type TwingExecutionContext = {
|
||||
dateFormat: string;
|
||||
dateIntervalFormat: string;
|
||||
isStrictVariables: boolean;
|
||||
nodeExecutor: TwingNodeExecutor;
|
||||
numberFormat: TwingNumberFormat;
|
||||
outputBuffer: TwingOutputBuffer;
|
||||
sandboxed: boolean;
|
||||
|
@ -34,7 +34,7 @@ export const include: TwingCallable<[
|
||||
ignoreMissing,
|
||||
sandboxed
|
||||
): Promise<TwingMarkup> => {
|
||||
const {template, charset, context, outputBuffer, sourceMapRuntime} = executionContext;
|
||||
const {template, charset, context, nodeExecutor, outputBuffer, sourceMapRuntime} = executionContext;
|
||||
const from = template.name;
|
||||
|
||||
if (!isPlainObject(variables) && !isTraversable(variables)) {
|
||||
@ -73,6 +73,7 @@ export const include: TwingCallable<[
|
||||
return template.render(
|
||||
createContext(variables),
|
||||
{
|
||||
nodeExecutor,
|
||||
outputBuffer,
|
||||
sandboxed,
|
||||
sourceMapRuntime: sourceMapRuntime || undefined
|
||||
|
18
src/lib/helpers/get-key-value-pairs.ts
Normal file
18
src/lib/helpers/get-key-value-pairs.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type {TwingBaseArrayNode} from "../node/expression/array";
|
||||
import {TwingConstantNode} from "../node/expression/constant";
|
||||
import {TwingBaseExpressionNode} from "../node/expression";
|
||||
|
||||
const array_chunk = require('locutus/php/array/array_chunk');
|
||||
|
||||
export const getKeyValuePairs = (
|
||||
node: TwingBaseArrayNode<any>
|
||||
): Array<{
|
||||
key: TwingConstantNode,
|
||||
value: TwingBaseExpressionNode
|
||||
}> => {
|
||||
const chunks: Array<[key: TwingConstantNode, value: TwingBaseExpressionNode]> = array_chunk(Object.values(node.children), 2);
|
||||
|
||||
return chunks.map(([key, value]) => {
|
||||
return {key, value};
|
||||
});
|
||||
};
|
204
src/lib/node-executor.ts
Normal file
204
src/lib/node-executor.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import type {TwingBaseNode} from "./node";
|
||||
import type {TwingExecutionContext} from "./execution-context";
|
||||
import {executeBinaryNode} from "./node-executor/expression/binary";
|
||||
import {executeTemplateNode} from "./node-executor/template";
|
||||
import {executePrintNode} from "./node-executor/print";
|
||||
import {executeTextNode} from "./node-executor/text";
|
||||
import {executeCallNode} from "./node-executor/expression/call";
|
||||
import {executeMethodCall} from "./node-executor/expression/method-call";
|
||||
import {executeAssignmentNode} from "./node-executor/expression/assignment";
|
||||
import {executeImportNode} from "./node-executor/import";
|
||||
import {executeParentFunction} from "./node-executor/expression/parent-function";
|
||||
import {executeBlockFunction} from "./node-executor/expression/block-function";
|
||||
import {executeBlockReferenceNode} from "./node-executor/block-reference";
|
||||
import {executeUnaryNode} from "./node-executor/expression/unary";
|
||||
import {executeArrayNode} from "./node-executor/expression/array";
|
||||
import {executeHashNode} from "./node-executor/expression/hash";
|
||||
import {executeAttributeAccessorNode} from "./node-executor/expression/attribute-accessor";
|
||||
import {executeNameNode} from "./node-executor/expression/name";
|
||||
import {executeSetNode} from "./node-executor/set";
|
||||
import {executeIfNode} from "./node-executor/if";
|
||||
import {executeForNode} from "./node-executor/for";
|
||||
import {executeForLoopNode} from "./node-executor/for-loop";
|
||||
import {executeCheckToStringNode} from "./node-executor/check-to-string";
|
||||
import {executeConditionalNode} from "./node-executor/expression/conditional";
|
||||
import {executeEmbedNode} from "./node-executor/include/embed";
|
||||
import {executeIncludeNode} from "./node-executor/include/include";
|
||||
import {executeWithNode} from "./node-executor/with";
|
||||
import {executeSpacelessNode} from "./node-executor/spaceless";
|
||||
import {executeApplyNode} from "./node-executor/apply";
|
||||
import {executeEscapeNode} from "./node-executor/expression/escape";
|
||||
import {executeArrowFunctionNode} from "./node-executor/expression/arrow-function";
|
||||
import {executeSandboxNode} from "./node-executor/sandbox";
|
||||
import {executeDoNode} from "./node-executor/do";
|
||||
import {executeDeprecatedNode} from "./node-executor/deprecated";
|
||||
import {executeSpreadNode} from "./node-executor/expression/spread";
|
||||
import {executeCheckSecurityNode} from "./node-executor/check-security";
|
||||
import {executeFlushNode} from "./node-executor/flush";
|
||||
import {createRuntimeError} from "./error/runtime";
|
||||
import {executeConstantNode} from "./node-executor/constant";
|
||||
import {executeLineNode} from "./node-executor/line";
|
||||
import {executeCommentNode} from "./node-executor/comment";
|
||||
import {executeBaseNode} from "./node-executor/base";
|
||||
|
||||
export type TwingNodeExecutor<Node extends TwingBaseNode = TwingBaseNode> = (
|
||||
node: Node,
|
||||
executionContext: TwingExecutionContext
|
||||
) => Promise<any>;
|
||||
|
||||
const binaryNodeTypes = ["add", "and", "bitwise_and", "bitwise_or", "bitwise_xor", "concatenate", "divide", "divide_and_floor", "ends_with", "has_every", "has_some", "is_equal_to", "is_greater_than", "is_greater_than_or_equal_to", "is_in", "is_less_than", "is_less_than_or_equal_to", "is_not_equal_to", "is_not_in", "matches", "modulo", "multiply", "or", "power", "range", "spaceship", "starts_with", "subtract"];
|
||||
|
||||
const isABinaryNode = (node: TwingBaseNode): boolean => {
|
||||
return binaryNodeTypes.includes(node.type);
|
||||
};
|
||||
|
||||
const unaryNodeTypes = ["negative", "not", "positive"];
|
||||
|
||||
const isAUnaryNode = (node: TwingBaseNode): boolean => {
|
||||
return unaryNodeTypes.includes(node.type);
|
||||
};
|
||||
|
||||
const callNodeTypes = ["filter", "function", "test"];
|
||||
|
||||
const isACallNode = (node: TwingBaseNode): boolean => {
|
||||
return callNodeTypes.includes(node.type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute the passed node against the passed execution context.
|
||||
*
|
||||
* @param node The node to execute
|
||||
* @param executionContext The context the node is executed against
|
||||
*/
|
||||
export const executeNode: TwingNodeExecutor = (node, executionContext) => {
|
||||
let executor: TwingNodeExecutor<any>;
|
||||
|
||||
if (isABinaryNode(node)) {
|
||||
executor = executeBinaryNode;
|
||||
}
|
||||
else if (isACallNode(node)) {
|
||||
executor = executeCallNode;
|
||||
}
|
||||
else if (isAUnaryNode(node)) {
|
||||
executor = executeUnaryNode;
|
||||
}
|
||||
else if (node.type === null) {
|
||||
executor = executeBaseNode;
|
||||
}
|
||||
else if (node.type === "apply") {
|
||||
executor = executeApplyNode;
|
||||
}
|
||||
else if (node.type === "array") {
|
||||
executor = executeArrayNode;
|
||||
}
|
||||
else if (node.type === "arrow_function") {
|
||||
executor = executeArrowFunctionNode;
|
||||
}
|
||||
else if (node.type === "assignment") {
|
||||
executor = executeAssignmentNode;
|
||||
}
|
||||
else if (node.type === "attribute_accessor") {
|
||||
executor = executeAttributeAccessorNode;
|
||||
}
|
||||
else if (node.type === "block_function") {
|
||||
executor = executeBlockFunction;
|
||||
}
|
||||
else if (node.type === "block_reference") {
|
||||
executor = executeBlockReferenceNode;
|
||||
}
|
||||
else if (node.type === "check_security") {
|
||||
executor = executeCheckSecurityNode;
|
||||
}
|
||||
else if (node.type === "check_to_string") {
|
||||
executor = executeCheckToStringNode;
|
||||
}
|
||||
else if (node.type === "comment") {
|
||||
executor = executeCommentNode;
|
||||
}
|
||||
else if (node.type === "conditional") {
|
||||
executor = executeConditionalNode;
|
||||
}
|
||||
else if (node.type === "constant") {
|
||||
executor = executeConstantNode;
|
||||
}
|
||||
else if (node.type === "deprecated") {
|
||||
executor = executeDeprecatedNode;
|
||||
}
|
||||
else if (node.type === "do") {
|
||||
executor = executeDoNode;
|
||||
}
|
||||
else if (node.type === "embed") {
|
||||
executor = executeEmbedNode;
|
||||
}
|
||||
else if (node.type === "escape") {
|
||||
executor = executeEscapeNode;
|
||||
}
|
||||
else if (node.type === "flush") {
|
||||
executor = executeFlushNode;
|
||||
}
|
||||
else if (node.type === "for") {
|
||||
executor = executeForNode;
|
||||
}
|
||||
else if (node.type === "for_loop") {
|
||||
executor = executeForLoopNode;
|
||||
}
|
||||
else if (node.type === "hash") {
|
||||
executor = executeHashNode;
|
||||
}
|
||||
else if (node.type === "if") {
|
||||
executor = executeIfNode;
|
||||
}
|
||||
else if (node.type === "import") {
|
||||
executor = executeImportNode;
|
||||
}
|
||||
else if (node.type === "include") {
|
||||
executor = executeIncludeNode;
|
||||
}
|
||||
else if (node.type === "line") {
|
||||
executor = executeLineNode;
|
||||
}
|
||||
else if (node.type === "method_call") {
|
||||
executor = executeMethodCall;
|
||||
}
|
||||
else if (node.type === "name") {
|
||||
executor = executeNameNode;
|
||||
}
|
||||
else if (node.type === "nullish_coalescing") {
|
||||
executor = executeConditionalNode;
|
||||
}
|
||||
else if (node.type === "parent_function") {
|
||||
executor = executeParentFunction;
|
||||
}
|
||||
else if (node.type === "print") {
|
||||
executor = executePrintNode;
|
||||
}
|
||||
else if (node.type === "sandbox") {
|
||||
executor = executeSandboxNode;
|
||||
}
|
||||
else if (node.type === "set") {
|
||||
executor = executeSetNode;
|
||||
}
|
||||
else if (node.type === "spaceless") {
|
||||
executor = executeSpacelessNode;
|
||||
}
|
||||
else if (node.type === "spread") {
|
||||
executor = executeSpreadNode;
|
||||
}
|
||||
else if (node.type === "template") {
|
||||
executor = executeTemplateNode;
|
||||
}
|
||||
else if (node.type === "text") {
|
||||
executor = executeTextNode;
|
||||
}
|
||||
else if (node.type === "verbatim") {
|
||||
executor = executeTextNode;
|
||||
}
|
||||
else if (node.type === "with") {
|
||||
executor = executeWithNode;
|
||||
}
|
||||
else {
|
||||
return Promise.reject(createRuntimeError(`Unrecognized node of type "${node.type}"`, node));
|
||||
}
|
||||
|
||||
return executor(node, executionContext);
|
||||
};
|
31
src/lib/node-executor/apply.ts
Normal file
31
src/lib/node-executor/apply.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingApplyNode} from "../node/apply";
|
||||
import {getKeyValuePairs} from "../helpers/get-key-value-pairs";
|
||||
import {createFilterNode} from "../node/expression/call/filter";
|
||||
import {createConstantNode} from "../node/expression/constant";
|
||||
|
||||
export const executeApplyNode: TwingNodeExecutor<TwingApplyNode> = (node, executionContext) => {
|
||||
const {outputBuffer, nodeExecutor: execute} = executionContext;
|
||||
const {body, filters} = node.children;
|
||||
const {line, column} = node;
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
return execute(body, executionContext)
|
||||
.then(async () => {
|
||||
let content = outputBuffer.getAndClean();
|
||||
|
||||
const keyValuePairs = getKeyValuePairs(filters);
|
||||
|
||||
while (keyValuePairs.length > 0) {
|
||||
const {key, value: filterArguments} = keyValuePairs.pop()!;
|
||||
|
||||
const filterName = key.attributes.value as string;
|
||||
const filterNode = createFilterNode(createConstantNode(content, line, column), filterName, filterArguments, line, column);
|
||||
|
||||
content = await execute(filterNode, executionContext);
|
||||
}
|
||||
|
||||
outputBuffer.echo(content);
|
||||
});
|
||||
};
|
12
src/lib/node-executor/base.ts
Normal file
12
src/lib/node-executor/base.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
|
||||
export const executeBaseNode: TwingNodeExecutor = async (node, executionContext) => {
|
||||
const output: Array<any> = [];
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
for (const [, child] of Object.entries(node.children)) {
|
||||
output.push(await execute(child, executionContext));
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
29
src/lib/node-executor/block-reference.ts
Normal file
29
src/lib/node-executor/block-reference.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingBlockReferenceNode} from "../node/block-reference";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
|
||||
export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNode> = (node, executionContext) => {
|
||||
const {
|
||||
template,
|
||||
context,
|
||||
outputBuffer,
|
||||
blocks,
|
||||
nodeExecutor: execute,
|
||||
sandboxed,
|
||||
sourceMapRuntime
|
||||
} = executionContext;
|
||||
const {name} = node.attributes;
|
||||
|
||||
const renderBlock = getTraceableMethod(template.renderBlock, node.line, node.column, template.name);
|
||||
|
||||
return renderBlock(
|
||||
name,
|
||||
context.clone(),
|
||||
outputBuffer,
|
||||
blocks,
|
||||
true,
|
||||
sandboxed,
|
||||
execute,
|
||||
sourceMapRuntime
|
||||
).then(outputBuffer.echo);
|
||||
};
|
42
src/lib/node-executor/check-security.ts
Normal file
42
src/lib/node-executor/check-security.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingCheckSecurityNode} from "../node/check-security";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedFilterError,
|
||||
TwingSandboxSecurityNotAllowedFilterError
|
||||
} from "../sandbox/security-not-allowed-filter-error";
|
||||
import {TwingSandboxSecurityNotAllowedFunctionError} from "../sandbox/security-not-allowed-function-error";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedTagError,
|
||||
TwingSandboxSecurityNotAllowedTagError
|
||||
} from "../sandbox/security-not-allowed-tag-error";
|
||||
|
||||
export const executeCheckSecurityNode: TwingNodeExecutor<TwingCheckSecurityNode> = (node, executionContext) => {
|
||||
const {template, sandboxed} = executionContext;
|
||||
const {usedTags, usedFunctions, usedFilters} = node.attributes;
|
||||
|
||||
try {
|
||||
sandboxed && template.checkSecurity(
|
||||
[...usedTags.keys()],
|
||||
[...usedFilters.keys()],
|
||||
[...usedFunctions.keys()]
|
||||
);
|
||||
} catch (error: any) {
|
||||
const supplementError = (error: TwingSandboxSecurityNotAllowedFilterError | TwingSandboxSecurityNotAllowedFunctionError | TwingSandboxSecurityNotAllowedTagError) => {
|
||||
error.source = template.name;
|
||||
|
||||
if (isASandboxSecurityNotAllowedTagError(error)) {
|
||||
error.location = usedTags.get(error.tagName);
|
||||
} else if (isASandboxSecurityNotAllowedFilterError(error)) {
|
||||
error.location = usedFilters.get(error.filterName)
|
||||
} else {
|
||||
error.location = usedFunctions.get(error.functionName);
|
||||
}
|
||||
}
|
||||
|
||||
supplementError(error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
29
src/lib/node-executor/check-to-string.ts
Normal file
29
src/lib/node-executor/check-to-string.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingCheckToStringNode} from "../node/check-to-string";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
|
||||
export const executeCheckToStringNode: TwingNodeExecutor<TwingCheckToStringNode> = (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute, sandboxed} = executionContext;
|
||||
const {value: valueNode} = node.children;
|
||||
|
||||
return execute(valueNode, executionContext)
|
||||
.then((value) => {
|
||||
if (sandboxed) {
|
||||
const assertToStringAllowed = getTraceableMethod((value: any) => {
|
||||
if ((value !== null) && (typeof value === 'object')) {
|
||||
try {
|
||||
template.checkMethodAllowed(value, 'toString');
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(value);
|
||||
}, valueNode.line, valueNode.column, template.name)
|
||||
|
||||
return assertToStringAllowed(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
};
|
6
src/lib/node-executor/comment.ts
Normal file
6
src/lib/node-executor/comment.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingCommentNode} from "../node/comment";
|
||||
|
||||
export const executeCommentNode: TwingNodeExecutor<TwingCommentNode> = () => {
|
||||
return Promise.resolve();
|
||||
};
|
6
src/lib/node-executor/constant.ts
Normal file
6
src/lib/node-executor/constant.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingConstantNode} from "../node/expression/constant";
|
||||
|
||||
export const executeConstantNode: TwingNodeExecutor<TwingConstantNode> = (node) => {
|
||||
return Promise.resolve(node.attributes.value);
|
||||
};
|
12
src/lib/node-executor/deprecated.ts
Normal file
12
src/lib/node-executor/deprecated.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingDeprecatedNode} from "../node/deprecated";
|
||||
|
||||
export const executeDeprecatedNode: TwingNodeExecutor<TwingDeprecatedNode> = (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute} = executionContext;
|
||||
const {message} = node.children;
|
||||
|
||||
return execute(message, executionContext)
|
||||
.then((message) => {
|
||||
console.warn(`${message} ("${template.name}" at line ${node.line}, column ${node.column})`);
|
||||
});
|
||||
};
|
6
src/lib/node-executor/do.ts
Normal file
6
src/lib/node-executor/do.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingDoNode} from "../node/do";
|
||||
|
||||
export const executeDoNode: TwingNodeExecutor<TwingDoNode> = (node, executionContext) => {
|
||||
return executionContext.nodeExecutor(node.children.body, executionContext);
|
||||
};
|
22
src/lib/node-executor/expression/array.ts
Normal file
22
src/lib/node-executor/expression/array.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import {type TwingBaseArrayNode} from "../../node/expression/array";
|
||||
import type {TwingNode} from "../../node";
|
||||
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
|
||||
|
||||
export const executeArrayNode: TwingNodeExecutor<TwingBaseArrayNode<any>> = async (baseNode, executionContext) => {
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
const keyValuePairs = getKeyValuePairs(baseNode);
|
||||
const array: Array<any> = [];
|
||||
|
||||
for (const {value: valueNode} of keyValuePairs) {
|
||||
const value = await execute(valueNode, executionContext);
|
||||
|
||||
if ((valueNode as TwingNode).type === "spread") {
|
||||
array.push(...value);
|
||||
} else {
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
22
src/lib/node-executor/expression/arrow-function.ts
Normal file
22
src/lib/node-executor/expression/arrow-function.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingArrowFunctionNode} from "../../node/expression/arrow-function";
|
||||
|
||||
export const executeArrowFunctionNode: TwingNodeExecutor<TwingArrowFunctionNode> = (node, executionContext) => {
|
||||
const {context, nodeExecutor: execute} = executionContext;
|
||||
const {body, names} = node.children;
|
||||
const assignmentNodes = Object.values(names.children);
|
||||
|
||||
return Promise.resolve((...functionArgs: Array<any>): Promise<any> => {
|
||||
let index = 0;
|
||||
|
||||
for (const assignmentNode of assignmentNodes) {
|
||||
const {name} = assignmentNode.attributes;
|
||||
|
||||
context.set(name, functionArgs[index]);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return execute(body, executionContext);
|
||||
});
|
||||
};
|
6
src/lib/node-executor/expression/assignment.ts
Normal file
6
src/lib/node-executor/expression/assignment.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingAssignmentNode} from "../../node/expression/assignment";
|
||||
|
||||
export const executeAssignmentNode: TwingNodeExecutor<TwingAssignmentNode> = (node) => {
|
||||
return Promise.resolve(node.attributes.name);
|
||||
};
|
30
src/lib/node-executor/expression/attribute-accessor.ts
Normal file
30
src/lib/node-executor/expression/attribute-accessor.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingAttributeAccessorNode} from "../../node/expression/attribute-accessor";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
import {getAttribute} from "../../helpers/get-attribute";
|
||||
|
||||
export const executeAttributeAccessorNode: TwingNodeExecutor<TwingAttributeAccessorNode> = (node, executionContext) => {
|
||||
const {template, sandboxed, isStrictVariables, nodeExecutor: execute} = executionContext;
|
||||
const {target, attribute, arguments: methodArguments} = node.children;
|
||||
const {type, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
|
||||
|
||||
return Promise.all([
|
||||
execute(target, executionContext),
|
||||
execute(attribute, executionContext),
|
||||
execute(methodArguments, executionContext)
|
||||
]).then(([target, attribute, methodArguments]) => {
|
||||
const traceableGetAttribute = getTraceableMethod(getAttribute, node.line, node.column, template.name);
|
||||
|
||||
return traceableGetAttribute(
|
||||
template,
|
||||
target,
|
||||
attribute,
|
||||
methodArguments,
|
||||
type,
|
||||
shouldTestExistence,
|
||||
shouldIgnoreStrictCheck || null,
|
||||
sandboxed,
|
||||
isStrictVariables
|
||||
)
|
||||
})
|
||||
};
|
165
src/lib/node-executor/expression/binary.ts
Normal file
165
src/lib/node-executor/expression/binary.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingBaseBinaryNode} from "../../node/expression/binary";
|
||||
import {compare} from "../../helpers/compare";
|
||||
import {concatenate} from "../../helpers/concatenate";
|
||||
import {every, isAMapLike, some} from "../../helpers/map-like";
|
||||
import {isIn} from "../../helpers/is-in";
|
||||
import {parseRegularExpression} from "../../helpers/parse-regular-expression";
|
||||
import {createRange} from "../../helpers/create-range";
|
||||
import {createRuntimeError} from "../../error/runtime";
|
||||
|
||||
export const executeBinaryNode: TwingNodeExecutor<TwingBaseBinaryNode<any>> = async (node, executionContext) => {
|
||||
const {left, right} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
switch (node.type) {
|
||||
case "add": {
|
||||
return await execute(left, executionContext) + await execute(right, executionContext);
|
||||
}
|
||||
case "and": {
|
||||
return !!(await execute(left, executionContext) && await execute(right, executionContext));
|
||||
}
|
||||
case "bitwise_and": {
|
||||
return await execute(left, executionContext) & await execute(right, executionContext);
|
||||
}
|
||||
case "bitwise_or": {
|
||||
return await execute(left, executionContext) | await execute(right, executionContext);
|
||||
}
|
||||
case "bitwise_xor": {
|
||||
return await execute(left, executionContext) ^ await execute(right, executionContext);
|
||||
}
|
||||
case "concatenate": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
return concatenate(leftValue, rightValue);
|
||||
}
|
||||
case "divide": {
|
||||
return await execute(left, executionContext) / await execute(right, executionContext);
|
||||
}
|
||||
case "divide_and_floor": {
|
||||
return Math.floor(await execute(left, executionContext) / await execute(right, executionContext));
|
||||
}
|
||||
case "ends_with": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
|
||||
if (typeof leftValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
if (typeof rightValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rightValue.length < 1 || leftValue.endsWith(rightValue);
|
||||
}
|
||||
case "has_every": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
if (typeof rightValue !== "function") {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return every(leftValue, rightValue);
|
||||
}
|
||||
case "has_some": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
if (typeof rightValue !== "function") {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return some(leftValue, rightValue);
|
||||
}
|
||||
case "is_equal_to": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
return compare(leftValue, rightValue);
|
||||
}
|
||||
case "is_greater_than": {
|
||||
return await execute(left, executionContext) > await execute(right, executionContext);
|
||||
}
|
||||
case "is_greater_than_or_equal_to": {
|
||||
return await execute(left, executionContext) >= await execute(right, executionContext);
|
||||
}
|
||||
case "is_in": {
|
||||
return isIn(await execute(left, executionContext), await execute(right, executionContext));
|
||||
}
|
||||
case "is_less_than": {
|
||||
return await execute(left, executionContext) < await execute(right, executionContext);
|
||||
}
|
||||
case "is_less_than_or_equal_to": {
|
||||
return await execute(left, executionContext) <= await execute(right, executionContext);
|
||||
}
|
||||
case "is_not_equal_to": {
|
||||
return Promise.resolve(!compare(await execute(left, executionContext), await execute(right, executionContext)))
|
||||
}
|
||||
case "is_not_in": {
|
||||
return Promise.resolve(!isIn(await execute(left, executionContext), await execute(right, executionContext)))
|
||||
}
|
||||
case "matches": {
|
||||
return parseRegularExpression(
|
||||
await execute(right, executionContext)
|
||||
).test(
|
||||
await execute(left, executionContext)
|
||||
);
|
||||
}
|
||||
case "modulo": {
|
||||
return await execute(left, executionContext) % await execute(right, executionContext);
|
||||
}
|
||||
case "multiply": {
|
||||
return await execute(left, executionContext) * await execute(right, executionContext);
|
||||
}
|
||||
case "or": {
|
||||
return !!(await execute(left, executionContext) || await execute(right, executionContext));
|
||||
}
|
||||
case "power": {
|
||||
return Math.pow(await execute(left, executionContext), await execute(right, executionContext));
|
||||
}
|
||||
case "range": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
return createRange(leftValue, rightValue, 1);
|
||||
}
|
||||
case "spaceship": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
return compare(leftValue, rightValue) ? 0 : (leftValue < rightValue ? -1 : 1);
|
||||
}
|
||||
case "starts_with": {
|
||||
const leftValue = await execute(left, executionContext);
|
||||
|
||||
if (typeof leftValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rightValue = await execute(right, executionContext);
|
||||
|
||||
if (typeof rightValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rightValue.length < 1 || leftValue.startsWith(rightValue);
|
||||
}
|
||||
case "subtract": {
|
||||
return await execute(left, executionContext) - await execute(right, executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(createRuntimeError(`Unrecognized binary node of type "${node.type}"`, node));
|
||||
};
|
45
src/lib/node-executor/expression/block-function.ts
Normal file
45
src/lib/node-executor/expression/block-function.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingBlockFunctionNode} from "../../node/expression/block-function";
|
||||
import {TwingTemplate} from "../../template";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = async (node, executionContext) => {
|
||||
const {template, context, nodeExecutor: execute, outputBuffer, blocks, sandboxed, sourceMapRuntime} = executionContext;
|
||||
const {template: templateNode, name: blockNameNode} = node.children;
|
||||
|
||||
const blockName = await execute(blockNameNode, executionContext);
|
||||
|
||||
let resolveTemplate: Promise<TwingTemplate>;
|
||||
|
||||
if (templateNode) {
|
||||
const templateName = await execute(templateNode, executionContext);
|
||||
|
||||
const loadTemplate = getTraceableMethod(
|
||||
template.loadTemplate,
|
||||
templateNode.line,
|
||||
templateNode.column,
|
||||
template.name
|
||||
);
|
||||
|
||||
resolveTemplate = loadTemplate(templateName);
|
||||
} else {
|
||||
resolveTemplate = Promise.resolve(template)
|
||||
}
|
||||
|
||||
return resolveTemplate
|
||||
.then<Promise<boolean | string>>((executionContextOfTheBlock) => {
|
||||
if (node.attributes.shouldTestExistence) {
|
||||
const hasBlock = getTraceableMethod(executionContextOfTheBlock.hasBlock, node.line, node.column, template.name);
|
||||
|
||||
return hasBlock(blockName, context.clone(), outputBuffer, blocks, sandboxed, execute);
|
||||
} else {
|
||||
const renderBlock = getTraceableMethod(executionContextOfTheBlock.renderBlock, node.line, node.column, template.name);
|
||||
|
||||
if (templateNode) {
|
||||
return renderBlock(blockName, context.clone(), outputBuffer, new Map(), false, sandboxed, execute, sourceMapRuntime);
|
||||
} else {
|
||||
return renderBlock(blockName, context.clone(), outputBuffer, blocks, true, sandboxed, execute, sourceMapRuntime);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
180
src/lib/node-executor/expression/call.ts
Normal file
180
src/lib/node-executor/expression/call.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingBaseCallNode} from "../../node/expression/call";
|
||||
import {TwingCallableArgument, TwingCallableWrapper} from "../../callable-wrapper";
|
||||
import {createRuntimeError} from "../../error/runtime";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
import {TwingArrayNode} from "../../node/expression/array";
|
||||
import {TwingBaseNode} from "../../node";
|
||||
import {createConstantNode, TwingConstantNode} from "../../node/expression/constant";
|
||||
import {TwingBaseExpressionNode} from "../../node/expression";
|
||||
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
|
||||
|
||||
const array_merge = require('locutus/php/array/array_merge');
|
||||
const snakeCase = require('snake-case');
|
||||
|
||||
const normalizeName = (name: string) => {
|
||||
return snakeCase(name).toLowerCase();
|
||||
};
|
||||
|
||||
const getArguments = (
|
||||
node: TwingBaseCallNode<any>,
|
||||
argumentsNode: TwingArrayNode,
|
||||
acceptedArguments: Array<TwingCallableArgument>,
|
||||
isVariadic: boolean
|
||||
): Array<TwingBaseNode> => {
|
||||
const callType = node.type;
|
||||
const callName = node.attributes.operatorName;
|
||||
const parameters: Map<string | number, {
|
||||
key: TwingConstantNode;
|
||||
value: TwingBaseExpressionNode;
|
||||
}> = new Map();
|
||||
|
||||
let named = false;
|
||||
|
||||
const keyPairs = getKeyValuePairs(argumentsNode);
|
||||
|
||||
for (let {key, value} of keyPairs) {
|
||||
let name = key.attributes.value as string | number;
|
||||
|
||||
if (typeof name === "string") {
|
||||
named = true;
|
||||
name = normalizeName(name);
|
||||
}
|
||||
else if (named) {
|
||||
throw createRuntimeError(`Positional arguments cannot be used after named arguments for ${callType} "${callName}".`, node);
|
||||
}
|
||||
|
||||
parameters.set(name, {
|
||||
key,
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
const callableParameters = acceptedArguments;
|
||||
const names: Array<string> = [];
|
||||
|
||||
let optionalArguments: Array<string | TwingConstantNode> = [];
|
||||
let arguments_: Array<TwingBaseNode> = [];
|
||||
let position = 0;
|
||||
|
||||
for (const callableParameter of callableParameters) {
|
||||
const name = '' + normalizeName(callableParameter.name);
|
||||
|
||||
names.push(name);
|
||||
|
||||
const parameter = parameters.get(name);
|
||||
|
||||
if (parameter) {
|
||||
if (parameters.has(position)) {
|
||||
throw createRuntimeError(`Argument "${name}" is defined twice for ${callType} "${callName}".`, node);
|
||||
}
|
||||
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(parameter.value);
|
||||
parameters.delete(name);
|
||||
optionalArguments = [];
|
||||
}
|
||||
else {
|
||||
const parameter = parameters.get(position);
|
||||
|
||||
if (parameter) {
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(parameter.value);
|
||||
parameters.delete(position);
|
||||
optionalArguments = [];
|
||||
++position;
|
||||
}
|
||||
else if (callableParameter.defaultValue !== undefined) {
|
||||
arguments_.push(createConstantNode(callableParameter.defaultValue, node.line, node.column));
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`Value for argument "${name}" is required for ${callType} "${callName}".`, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isVariadic) {
|
||||
const resolvedKeys: Array<any> = [];
|
||||
const arbitraryArguments: Array<TwingBaseExpressionNode> = [];
|
||||
|
||||
for (const [key, value] of parameters) {
|
||||
arbitraryArguments.push(value.value);
|
||||
|
||||
resolvedKeys.push(key);
|
||||
}
|
||||
|
||||
for (const key of resolvedKeys) {
|
||||
parameters.delete(key);
|
||||
}
|
||||
|
||||
if (arbitraryArguments.length) {
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(...arbitraryArguments);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.size > 0) {
|
||||
const unknownParameter = [...parameters.values()][0];
|
||||
|
||||
throw createRuntimeError(`Unknown argument${parameters.size > 1 ? 's' : ''} "${[...parameters.keys()].join('", "')}" for ${callType} "${callName}(${names.join(', ')})".`, unknownParameter.key);
|
||||
}
|
||||
|
||||
return arguments_;
|
||||
}
|
||||
|
||||
export const executeCallNode: TwingNodeExecutor<TwingBaseCallNode<any>> = async (node, executionContext) => {
|
||||
const {type} = node;
|
||||
const {template, nodeExecutor: execute} = executionContext
|
||||
const {operatorName} = node.attributes;
|
||||
|
||||
let callableWrapper: TwingCallableWrapper | null;
|
||||
|
||||
switch (type) {
|
||||
case "filter":
|
||||
callableWrapper = template.getFilter(operatorName);
|
||||
break;
|
||||
|
||||
case "function":
|
||||
callableWrapper = template.getFunction(operatorName);
|
||||
break;
|
||||
|
||||
// for some reason, using `case "test"` makes the compiler assume that callableWrapper is used
|
||||
// before it is assigned a value; this is probably a bug of the compiler
|
||||
default:
|
||||
callableWrapper = template.getTest(operatorName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (callableWrapper === null) {
|
||||
throw createRuntimeError(`Unknown ${type} "${operatorName}".`, node);
|
||||
}
|
||||
|
||||
const {operand, arguments: callArguments} = node.children;
|
||||
|
||||
const argumentNodes = getArguments(
|
||||
node,
|
||||
callArguments,
|
||||
callableWrapper.acceptedArguments,
|
||||
callableWrapper.isVariadic
|
||||
);
|
||||
|
||||
const actualArguments: Array<any> = [];
|
||||
|
||||
actualArguments.push(...callableWrapper!.nativeArguments);
|
||||
|
||||
if (operand) {
|
||||
actualArguments.push(await execute(operand, executionContext));
|
||||
}
|
||||
|
||||
const providedArguments = await Promise.all([
|
||||
...argumentNodes.map((node) => execute(node, executionContext))
|
||||
]);
|
||||
|
||||
actualArguments.push(...providedArguments);
|
||||
|
||||
const traceableCallable = getTraceableMethod(callableWrapper.callable, node.line, node.column, template.name);
|
||||
|
||||
return traceableCallable(executionContext, ...actualArguments).then((value) => {
|
||||
return value;
|
||||
});
|
||||
};
|
9
src/lib/node-executor/expression/conditional.ts
Normal file
9
src/lib/node-executor/expression/conditional.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingBaseConditionalNode} from "../../node/expression/conditional";
|
||||
|
||||
export const executeConditionalNode: TwingNodeExecutor<TwingBaseConditionalNode<any>> = async (node, executionContext) => {
|
||||
const {expr1, expr2, expr3} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
return (await execute(expr1, executionContext)) ? execute(expr2, executionContext) : execute(expr3, executionContext);
|
||||
};
|
16
src/lib/node-executor/expression/escape.ts
Normal file
16
src/lib/node-executor/expression/escape.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingEscapeNode} from "../../node/expression/escape";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export const executeEscapeNode: TwingNodeExecutor<TwingEscapeNode> = (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute} = executionContext;
|
||||
const {strategy} = node.attributes;
|
||||
const {body} = node.children;
|
||||
|
||||
return execute(body, executionContext)
|
||||
.then((value) => {
|
||||
const escape = getTraceableMethod(template.escape, node.line, node.column, template.name);
|
||||
|
||||
return escape(value, strategy, null, true);
|
||||
});
|
||||
};
|
27
src/lib/node-executor/expression/hash.ts
Normal file
27
src/lib/node-executor/expression/hash.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingHashNode} from "../../node/expression/hash";
|
||||
import type {TwingNode} from "../../node";
|
||||
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
|
||||
|
||||
export const executeHashNode: TwingNodeExecutor<TwingHashNode> = async (node, executionContext) => {
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
const keyValuePairs = getKeyValuePairs(node);
|
||||
const hash: Map<any, any> = new Map();
|
||||
|
||||
for (const {key: keyNode, value: valueNode} of keyValuePairs) {
|
||||
const [key, value] = await Promise.all([
|
||||
execute(keyNode, executionContext),
|
||||
execute(valueNode, executionContext)
|
||||
]);
|
||||
|
||||
if ((valueNode as TwingNode).type === "spread") {
|
||||
for (const [valueKey, valueValue] of value as Map<any, any>) {
|
||||
hash.set(valueKey, valueValue);
|
||||
}
|
||||
} else {
|
||||
hash.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
54
src/lib/node-executor/expression/method-call.ts
Normal file
54
src/lib/node-executor/expression/method-call.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingTemplate, TwingTemplateMacroHandler} from "../../template";
|
||||
import {createRuntimeError} from "../../error/runtime";
|
||||
import type {TwingMethodCallNode} from "../../node/expression/method-call";
|
||||
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
|
||||
|
||||
export const executeMethodCall: TwingNodeExecutor<TwingMethodCallNode> = async (node, executionContext) => {
|
||||
const {template, context, outputBuffer, aliases, nodeExecutor: execute, sandboxed, sourceMapRuntime} = executionContext;
|
||||
const {methodName, shouldTestExistence} = node.attributes;
|
||||
const {operand, arguments: methodArguments} = node.children;
|
||||
|
||||
if (shouldTestExistence) {
|
||||
return (aliases.get(operand.attributes.name) as TwingTemplate).hasMacro(methodName);
|
||||
} else {
|
||||
const keyValuePairs = getKeyValuePairs(methodArguments);
|
||||
|
||||
const macroArguments: Array<any> = [];
|
||||
|
||||
for (const {value: valueNode} of keyValuePairs) {
|
||||
const value = await execute(valueNode, executionContext);
|
||||
|
||||
macroArguments.push(value);
|
||||
}
|
||||
|
||||
// by nature, the alias exists - the parser only creates a method call node when the name _is_ an alias.
|
||||
const macroTemplate = aliases.get(operand.attributes.name)!;
|
||||
|
||||
const getHandler = (template: TwingTemplate): Promise<TwingTemplateMacroHandler | null> => {
|
||||
const macroHandler = template.macroHandlers.get(methodName);
|
||||
|
||||
if (macroHandler) {
|
||||
return Promise.resolve(macroHandler);
|
||||
} else {
|
||||
return template.getParent(context, outputBuffer, sandboxed, execute)
|
||||
.then((parent) => {
|
||||
if (parent) {
|
||||
return getHandler(parent);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return getHandler(macroTemplate)
|
||||
.then((handler) => {
|
||||
if (handler) {
|
||||
return handler(outputBuffer, sandboxed, sourceMapRuntime, execute, ...macroArguments);
|
||||
} else {
|
||||
throw createRuntimeError(`Macro "${methodName}" is not defined in template "${macroTemplate.name}".`, node, template.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
31
src/lib/node-executor/expression/name.ts
Normal file
31
src/lib/node-executor/expression/name.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingNameNode} from "../../node/expression/name";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
import {getContextValue} from "../../helpers/get-context-value";
|
||||
|
||||
export const executeNameNode: TwingNodeExecutor<TwingNameNode> = (node, {
|
||||
template,
|
||||
context,
|
||||
charset,
|
||||
isStrictVariables
|
||||
}) => {
|
||||
const {name, isAlwaysDefined, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
|
||||
|
||||
const traceableGetContextValue = getTraceableMethod(
|
||||
getContextValue,
|
||||
node.line,
|
||||
node.column,
|
||||
template.name
|
||||
);
|
||||
|
||||
return traceableGetContextValue(
|
||||
charset,
|
||||
template.name,
|
||||
isStrictVariables,
|
||||
context,
|
||||
name,
|
||||
isAlwaysDefined,
|
||||
shouldIgnoreStrictCheck,
|
||||
shouldTestExistence
|
||||
);
|
||||
};
|
11
src/lib/node-executor/expression/parent-function.ts
Normal file
11
src/lib/node-executor/expression/parent-function.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingParentFunctionNode} from "../../node/expression/parent-function";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export const executeParentFunction: TwingNodeExecutor<TwingParentFunctionNode> = (node, executionContext) => {
|
||||
const {template, context, nodeExecutor: execute, outputBuffer, sandboxed, sourceMapRuntime,} = executionContext;
|
||||
const {name} = node.attributes;
|
||||
const renderParentBlock = getTraceableMethod(template.renderParentBlock, node.line, node.column, template.name);
|
||||
|
||||
return renderParentBlock(name, context, outputBuffer, sandboxed, execute, sourceMapRuntime);
|
||||
};
|
9
src/lib/node-executor/expression/spread.ts
Normal file
9
src/lib/node-executor/expression/spread.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingSpreadNode} from "../../node/expression/spread";
|
||||
|
||||
export const executeSpreadNode: TwingNodeExecutor<TwingSpreadNode> = (node, executionContext) => {
|
||||
const {iterable} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
return execute(iterable, executionContext);
|
||||
};
|
22
src/lib/node-executor/expression/unary.ts
Normal file
22
src/lib/node-executor/expression/unary.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type {TwingNodeExecutor} from "../../node-executor";
|
||||
import type {TwingBaseUnaryNode} from "../../node/expression/unary";
|
||||
import {createRuntimeError} from "../../error/runtime";
|
||||
|
||||
export const executeUnaryNode: TwingNodeExecutor<TwingBaseUnaryNode<any>> = (node, executionContext) => {
|
||||
const {operand} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
switch (node.type) {
|
||||
case "negative": {
|
||||
return execute(operand, executionContext).then((value) => -(value));
|
||||
}
|
||||
case "not": {
|
||||
return execute(operand, executionContext).then((value) => !(value));
|
||||
}
|
||||
case "positive": {
|
||||
return execute(operand, executionContext).then((value) => +(value));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(createRuntimeError(`Unrecognized unary node of type "${node.type}"`, node));
|
||||
};
|
8
src/lib/node-executor/flush.ts
Normal file
8
src/lib/node-executor/flush.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingFlushNode} from "../node/flush";
|
||||
|
||||
export const executeFlushNode: TwingNodeExecutor<TwingFlushNode> = (_node, {outputBuffer}) => {
|
||||
outputBuffer.flush();
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
26
src/lib/node-executor/for-loop.ts
Normal file
26
src/lib/node-executor/for-loop.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingForLoopNode} from "../node/for-loop";
|
||||
|
||||
export const executeForLoopNode: TwingNodeExecutor<TwingForLoopNode> = (node, executionContext) => {
|
||||
const {hasAnElse, hasAnIf} = node.attributes;
|
||||
|
||||
const {context} = executionContext;
|
||||
|
||||
if (hasAnElse) {
|
||||
context.set('_iterated', true);
|
||||
}
|
||||
|
||||
const loop: Map<string, any> = context.get('loop');
|
||||
|
||||
loop.set('index0', loop.get('index0') + 1);
|
||||
loop.set('index', loop.get('index') + 1);
|
||||
loop.set('first', false);
|
||||
|
||||
if (!hasAnIf && loop.has('length')) {
|
||||
loop.set('revindex0', loop.get('revindex0') - 1);
|
||||
loop.set('revindex', loop.get('revindex') - 1);
|
||||
loop.set('last', loop.get('revindex0') === 0);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
83
src/lib/node-executor/for.ts
Normal file
83
src/lib/node-executor/for.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingForNode} from "../node/for";
|
||||
import {TwingContext} from "../context";
|
||||
import {ensureTraversable} from "../helpers/ensure-traversable";
|
||||
import {count} from "../helpers/count";
|
||||
import {iterate} from "../helpers/iterate";
|
||||
|
||||
export const executeForNode: TwingNodeExecutor<TwingForNode> = async (forNode, executionContext) => {
|
||||
const {context, nodeExecutor: execute} = executionContext;
|
||||
const {
|
||||
sequence: sequenceNode,
|
||||
body,
|
||||
else: elseNode,
|
||||
valueTarget: targetValueNode,
|
||||
keyTarget: targetKeyNode
|
||||
} = forNode.children;
|
||||
const {hasAnIf} = forNode.attributes;
|
||||
|
||||
context.set('_parent', context.clone());
|
||||
|
||||
const executedSequence: TwingContext<any, any> | any = await execute(sequenceNode, executionContext);
|
||||
|
||||
let sequence = ensureTraversable(executedSequence);
|
||||
|
||||
if (sequence === context) {
|
||||
context.set('_seq', context.clone());
|
||||
} else {
|
||||
context.set('_seq', sequence);
|
||||
}
|
||||
|
||||
if (elseNode) {
|
||||
context.set('_iterated', false);
|
||||
}
|
||||
|
||||
context.set('loop', new Map([
|
||||
['parent', context.get('_parent')],
|
||||
['index0', 0],
|
||||
['index', 1],
|
||||
['first', true],
|
||||
]));
|
||||
|
||||
if (!hasAnIf) {
|
||||
const length = count(context.get('_seq'));
|
||||
|
||||
const loop: Map<string, any> = context.get('loop');
|
||||
|
||||
loop.set('revindex0', length - 1);
|
||||
loop.set('revindex', length);
|
||||
loop.set('length', length);
|
||||
loop.set('last', (length === 1));
|
||||
}
|
||||
|
||||
const targetKey = await execute(targetKeyNode, executionContext);
|
||||
const targetValue = await execute(targetValueNode, executionContext);
|
||||
|
||||
await iterate(context.get('_seq'), async (key, value) => {
|
||||
context.set(targetKey, key);
|
||||
context.set(targetValue, value);
|
||||
|
||||
await execute(body, executionContext);
|
||||
});
|
||||
|
||||
if (elseNode) {
|
||||
if (context.get('_iterated') === false) {
|
||||
await execute(elseNode, executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
const parent = context.get('_parent');
|
||||
|
||||
context.delete('_seq');
|
||||
context.delete('_iterated');
|
||||
context.delete(targetKeyNode.attributes.name);
|
||||
context.delete(targetValueNode.attributes.name);
|
||||
context.delete('_parent');
|
||||
context.delete('loop');
|
||||
|
||||
for (const [key, value] of parent) {
|
||||
if (!context.has(key)) {
|
||||
context.set(key, value);
|
||||
}
|
||||
}
|
||||
};
|
30
src/lib/node-executor/if.ts
Normal file
30
src/lib/node-executor/if.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingIfNode} from "../node/if";
|
||||
import {getChildrenCount} from "../node";
|
||||
import {evaluate} from "../helpers/evaluate";
|
||||
|
||||
export const executeIfNode: TwingNodeExecutor<TwingIfNode> = async (node, executionContext) => {
|
||||
const {tests: testsNode, else: elseNode} = node.children;
|
||||
const count = getChildrenCount(testsNode);
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
let index: number = 0;
|
||||
|
||||
while (index < count) {
|
||||
const condition = testsNode.children[index];
|
||||
const conditionResult = await execute(condition, executionContext);
|
||||
|
||||
if (evaluate(conditionResult)) {
|
||||
// the condition is satisfied, we execute the belonging body and return the result
|
||||
const body = testsNode.children[index + 1];
|
||||
|
||||
return execute(body, executionContext);
|
||||
}
|
||||
|
||||
index += 2;
|
||||
}
|
||||
|
||||
if (elseNode !== undefined) {
|
||||
return execute(elseNode, executionContext);
|
||||
}
|
||||
};
|
30
src/lib/node-executor/import.ts
Normal file
30
src/lib/node-executor/import.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingImportNode} from "../node/import";
|
||||
import type {TwingTemplate} from "../template";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
import type {TwingNameNode} from "../node/expression/name";
|
||||
|
||||
export const executeImportNode: TwingNodeExecutor<TwingImportNode> = async (node, executionContext) => {
|
||||
const {template, aliases, nodeExecutor: execute,} = executionContext;
|
||||
const {alias: aliasNode, templateName: templateNameNode} = node.children;
|
||||
|
||||
const {global} = node.attributes;
|
||||
|
||||
let aliasValue: TwingTemplate;
|
||||
|
||||
if (templateNameNode.type === "name" && (templateNameNode as TwingNameNode).attributes.name === '_self') {
|
||||
aliasValue = template;
|
||||
} else {
|
||||
const templateName = await execute(templateNameNode, executionContext);
|
||||
|
||||
const loadTemplate = getTraceableMethod(template.loadTemplate, node.line, node.column, template.name);
|
||||
|
||||
aliasValue = await loadTemplate(templateName);
|
||||
}
|
||||
|
||||
aliases.set(aliasNode.attributes.name, aliasValue);
|
||||
|
||||
if (global) {
|
||||
template.aliases.set(aliasNode.attributes.name, aliasValue);
|
||||
}
|
||||
};
|
30
src/lib/node-executor/include.ts
Normal file
30
src/lib/node-executor/include.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {TwingBaseIncludeNode} from "../node/include";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
import {include} from "../extension/core/functions/include";
|
||||
import {TwingExecutionContext} from "../execution-context";
|
||||
import {TwingTemplate} from "../template";
|
||||
|
||||
export const executeBaseIncludeNode = async (
|
||||
node: TwingBaseIncludeNode<any>,
|
||||
executionContext: TwingExecutionContext,
|
||||
getTemplate: (executionContext: TwingExecutionContext) => Promise<TwingTemplate | null | Array<TwingTemplate | null>>
|
||||
) => {
|
||||
const {nodeExecutor: execute, outputBuffer, sandboxed, template} = executionContext;
|
||||
const {variables} = node.children;
|
||||
const {only, ignoreMissing} = node.attributes;
|
||||
|
||||
const templatesToInclude = await getTemplate(executionContext);
|
||||
|
||||
const traceableInclude = getTraceableMethod(include, node.line, node.column, template.name);
|
||||
|
||||
const output = await traceableInclude(
|
||||
executionContext,
|
||||
templatesToInclude,
|
||||
await execute(variables, executionContext),
|
||||
!only,
|
||||
ignoreMissing,
|
||||
sandboxed
|
||||
);
|
||||
|
||||
outputBuffer.echo(output);
|
||||
};
|
14
src/lib/node-executor/include/embed.ts
Normal file
14
src/lib/node-executor/include/embed.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingEmbedNode} from "../../node/include/embed";
|
||||
import {executeBaseIncludeNode} from "../include";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export const executeEmbedNode: TwingNodeExecutor<TwingEmbedNode> = (node, executionContext) => {
|
||||
return executeBaseIncludeNode(node, executionContext, ({template}) => {
|
||||
const {index} = node.attributes;
|
||||
|
||||
const loadTemplate = getTraceableMethod(template.loadEmbeddedTemplate, node.line, node.column, template.name);
|
||||
|
||||
return loadTemplate(index);
|
||||
})
|
||||
};
|
11
src/lib/node-executor/include/include.ts
Normal file
11
src/lib/node-executor/include/include.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {TwingNodeExecutor} from "../../node-executor";
|
||||
import {TwingIncludeNode} from "../../node/include/include";
|
||||
import {executeBaseIncludeNode} from "../include";
|
||||
|
||||
export const executeIncludeNode: TwingNodeExecutor<TwingIncludeNode> = (node, executionContext) => {
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
return executeBaseIncludeNode(node, executionContext, (executionContext) => {
|
||||
return execute(node.children.expression, executionContext);
|
||||
})
|
||||
};
|
6
src/lib/node-executor/line.ts
Normal file
6
src/lib/node-executor/line.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingLineNode} from "../node/line";
|
||||
|
||||
export const executeLineNode: TwingNodeExecutor<TwingLineNode> = () => {
|
||||
return Promise.resolve();
|
||||
};
|
19
src/lib/node-executor/print.ts
Normal file
19
src/lib/node-executor/print.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingPrintNode} from "../node/print";
|
||||
|
||||
export const executePrintNode: TwingNodeExecutor<TwingPrintNode> = (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute, outputBuffer, sourceMapRuntime} = executionContext;
|
||||
|
||||
sourceMapRuntime?.enterSourceMapBlock(node.line, node.column, node.type, template.source, outputBuffer);
|
||||
|
||||
return execute(node.children.expression, executionContext)
|
||||
.then((result) => {
|
||||
if (Array.isArray(result)) {
|
||||
result = 'Array';
|
||||
}
|
||||
|
||||
outputBuffer.echo(result);
|
||||
|
||||
sourceMapRuntime?.leaveSourceMapBlock(outputBuffer);
|
||||
});
|
||||
};
|
12
src/lib/node-executor/sandbox.ts
Normal file
12
src/lib/node-executor/sandbox.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingSandboxNode} from "../node/sandbox";
|
||||
|
||||
export const executeSandboxNode: TwingNodeExecutor<TwingSandboxNode> = (node, executionContext) => {
|
||||
const {body} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
return execute(body, {
|
||||
...executionContext,
|
||||
sandboxed: true
|
||||
});
|
||||
};
|
34
src/lib/node-executor/set.ts
Normal file
34
src/lib/node-executor/set.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingSetNode} from "../node/set";
|
||||
|
||||
export const executeSetNode: TwingNodeExecutor<TwingSetNode> = async (node, executionContext) => {
|
||||
const {context, nodeExecutor: execute, outputBuffer} = executionContext;
|
||||
const {names: namesNode, values: valuesNode} = node.children;
|
||||
const {captures} = node.attributes;
|
||||
|
||||
const names: Array<string> = await execute(namesNode, executionContext);
|
||||
|
||||
if (captures) {
|
||||
outputBuffer.start();
|
||||
|
||||
await execute(valuesNode, executionContext);
|
||||
|
||||
const value = outputBuffer.getAndClean();
|
||||
|
||||
for (const name of names) {
|
||||
context.set(name, value);
|
||||
}
|
||||
} else {
|
||||
const values: Array<any> = await execute(valuesNode, executionContext);
|
||||
|
||||
let index = 0;
|
||||
|
||||
for (const name of names) {
|
||||
const value = values[index];
|
||||
|
||||
context.set(name, value);
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
};
|
16
src/lib/node-executor/spaceless.ts
Normal file
16
src/lib/node-executor/spaceless.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingSpacelessNode} from "../node/spaceless";
|
||||
|
||||
export const executeSpacelessNode: TwingNodeExecutor<TwingSpacelessNode> = (node, executionContext) => {
|
||||
const {outputBuffer} = executionContext;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
return execute(node.children.body, executionContext)
|
||||
.then(() => {
|
||||
const content = outputBuffer.getAndClean().replace(/>\s+</g, '><').trim();
|
||||
|
||||
outputBuffer.echo(content);
|
||||
});
|
||||
};
|
16
src/lib/node-executor/template.ts
Normal file
16
src/lib/node-executor/template.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingTemplateNode} from "../node/template";
|
||||
|
||||
export const executeTemplateNode: TwingNodeExecutor<TwingTemplateNode> = (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute, outputBuffer, sourceMapRuntime} = executionContext;
|
||||
const {securityCheck, body} = node.children;
|
||||
|
||||
return execute(securityCheck, executionContext)
|
||||
.then(() => {
|
||||
sourceMapRuntime?.enterSourceMapBlock(node.line, node.column, node.type, template.source, outputBuffer);
|
||||
|
||||
return execute(body, executionContext).then(() => {
|
||||
sourceMapRuntime?.leaveSourceMapBlock(outputBuffer);
|
||||
});
|
||||
});
|
||||
};
|
14
src/lib/node-executor/text.ts
Normal file
14
src/lib/node-executor/text.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type {TwingNodeExecutor} from "../node-executor";
|
||||
import type {TwingTextNode} from "../node/text";
|
||||
|
||||
export const executeTextNode: TwingNodeExecutor<TwingTextNode> = (textNode, executionContext) => {
|
||||
const {template, outputBuffer, sourceMapRuntime} = executionContext;
|
||||
|
||||
sourceMapRuntime?.enterSourceMapBlock(textNode.line, textNode.column, textNode.type, template.source, outputBuffer);
|
||||
|
||||
outputBuffer.echo(textNode.attributes.data);
|
||||
|
||||
sourceMapRuntime?.leaveSourceMapBlock(outputBuffer);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
42
src/lib/node-executor/with.ts
Normal file
42
src/lib/node-executor/with.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingWithNode} from "../node/with";
|
||||
import {createContext, TwingContext} from "../context";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
import {mergeIterables} from "../helpers/merge-iterables";
|
||||
import {iteratorToMap} from "../helpers/iterator-to-map";
|
||||
|
||||
export const executeWithNode: TwingNodeExecutor<TwingWithNode> = async (node, executionContext) => {
|
||||
const {template, nodeExecutor: execute, context} = executionContext;
|
||||
const {variables: variablesNode, body} = node.children;
|
||||
const {only} = node.attributes;
|
||||
|
||||
let scopedContext: TwingContext<any, any>;
|
||||
|
||||
if (variablesNode) {
|
||||
const variables = await execute(variablesNode, executionContext);
|
||||
|
||||
if (typeof variables !== "object") {
|
||||
throw createRuntimeError(`Variables passed to the "with" tag must be a hash.`, node, template.name);
|
||||
}
|
||||
|
||||
if (only) {
|
||||
scopedContext = createContext();
|
||||
} else {
|
||||
scopedContext = context.clone();
|
||||
}
|
||||
|
||||
scopedContext = createContext(mergeIterables(
|
||||
scopedContext,
|
||||
iteratorToMap(variables)
|
||||
))
|
||||
} else {
|
||||
scopedContext = context.clone();
|
||||
}
|
||||
|
||||
scopedContext.set('_parent', context.clone());
|
||||
|
||||
await execute(body, {
|
||||
...executionContext,
|
||||
context: scopedContext
|
||||
});
|
||||
};
|
@ -10,10 +10,11 @@ import {cloneMethodCallNode} from "../node/expression/method-call";
|
||||
import {TwingBaseExpressionNode} from "../node/expression";
|
||||
import {createParsingError} from "../error/parsing";
|
||||
import {createTestNode, TwingTestNode} from "../node/expression/call/test";
|
||||
import {createArrayNode, getKeyValuePairs} from "../node/expression/array";
|
||||
import {createArrayNode} from "../node/expression/array";
|
||||
import {createConditionalNode} from "../node/expression/conditional";
|
||||
import {TwingFilterNode} from "../node/expression/call/filter";
|
||||
import type {TwingNode} from "../node";
|
||||
import {getKeyValuePairs} from "../helpers/get-key-value-pairs";
|
||||
|
||||
export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
const enteredNodes: Array<TwingBaseExpressionNode> = [];
|
||||
|
@ -2,7 +2,6 @@ import {TwingBaseNode, getChildren, TwingNode} from "../node";
|
||||
import {createCheckSecurityNode} from "../node/check-security";
|
||||
import {createCheckToStringNode} from "../node/check-to-string";
|
||||
import {createNodeVisitor, TwingNodeVisitor} from "../node-visitor";
|
||||
import type {TwingWrapperNode} from "../node/wrapper";
|
||||
|
||||
export const createSandboxNodeVisitor = (): TwingNodeVisitor => {
|
||||
let tags: Map<string, TwingBaseNode>;
|
||||
@ -103,7 +102,7 @@ export const createSandboxNodeVisitor = (): TwingNodeVisitor => {
|
||||
};
|
||||
|
||||
const wrapArrayNode = <T extends TwingNode>(node: T, name: keyof T["children"]) => {
|
||||
const args: TwingWrapperNode = (node.children as any)[name]; // todo: check with TS team with we have to cast children as any
|
||||
const args: TwingBaseNode = (node.children as any)[name]; // todo: check with TS team with we have to cast children as any
|
||||
|
||||
for (const [name] of getChildren(args)) {
|
||||
wrapNode(args, name);
|
||||
|
@ -3,7 +3,6 @@ import type {TwingPrintNode} from "./node/print";
|
||||
import type {TwingBlockReferenceNode} from "./node/block-reference";
|
||||
import type {TwingTextNode} from "./node/text";
|
||||
import type {TwingAutoEscapeNode} from "./node/auto-escape";
|
||||
import type {TwingBodyNode} from "./node/body";
|
||||
import type {TwingCheckSecurityNode} from "./node/check-security";
|
||||
import type {TwingCheckToStringNode} from "./node/check-to-string";
|
||||
import type {TwingCommentNode} from "./node/comment";
|
||||
@ -29,15 +28,12 @@ import type {TwingIfNode} from "./node/if";
|
||||
import type {TwingMethodCallNode} from "./node/expression/method-call";
|
||||
import type {TwingEscapeNode} from "./node/expression/escape";
|
||||
import type {TwingApplyNode} from "./node/apply";
|
||||
import type {TwingExecutionContext} from "./execution-context";
|
||||
import type {TwingWrapperNode} from "./node/wrapper";
|
||||
|
||||
export type TwingNode =
|
||||
| TwingApplyNode
|
||||
| TwingAutoEscapeNode
|
||||
| TwingBlockNode
|
||||
| TwingBlockReferenceNode
|
||||
| TwingBodyNode
|
||||
| TwingCheckSecurityNode
|
||||
| TwingCheckToStringNode
|
||||
| TwingCommentNode
|
||||
@ -64,7 +60,6 @@ export type TwingNode =
|
||||
| TwingTraitNode
|
||||
| TwingVerbatimNode
|
||||
| TwingWithNode
|
||||
| TwingWrapperNode
|
||||
;
|
||||
|
||||
export type TwingNodeType<T> = T extends TwingBaseNode<infer Type, any, any> ? Type : never;
|
||||
@ -85,8 +80,6 @@ export interface TwingBaseNode<
|
||||
readonly line: number;
|
||||
readonly tag: string | null;
|
||||
readonly type: Type;
|
||||
|
||||
execute(executionContext: TwingExecutionContext): Promise<any>;
|
||||
}
|
||||
|
||||
export type KeysOf<T> = T extends T ? keyof T : never;
|
||||
@ -121,15 +114,25 @@ export const createBaseNode = <
|
||||
column,
|
||||
line,
|
||||
tag,
|
||||
type,
|
||||
execute: async (executionContext) => {
|
||||
const output: Array<any> = [];
|
||||
|
||||
for (const [, child] of Object.entries(children)) {
|
||||
output.push(await child.execute(executionContext));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
type
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a node acting as a container for the passed list of indexed nodes.
|
||||
*
|
||||
* @param children The children of the created node
|
||||
* @param line The line of the created node
|
||||
* @param column The column of the created node
|
||||
* @param tag The tag of the created node
|
||||
*/
|
||||
export const createNode = <
|
||||
Children extends TwingBaseNodeChildren
|
||||
>(
|
||||
children: Children = {} as Children,
|
||||
line: number = 0,
|
||||
column: number = 0,
|
||||
tag: string | null = null
|
||||
): TwingBaseNode<null, {}, Children> => {
|
||||
return createBaseNode(null, {}, children, line, column, tag);
|
||||
};
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {createBaseNode, TwingBaseNode, TwingBaseNodeAttributes} from "../node";
|
||||
import {getKeyValuePairs, TwingArrayNode} from "./expression/array";
|
||||
import {createFilterNode} from "./expression/call/filter";
|
||||
import {createConstantNode} from "./expression/constant";
|
||||
import {TwingArrayNode} from "./expression/array";
|
||||
|
||||
export type TwingApplyNodeAttributes = TwingBaseNodeAttributes & {};
|
||||
|
||||
@ -20,39 +18,8 @@ export const createApplyNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingApplyNode => {
|
||||
const baseNode = createBaseNode("apply", {}, {
|
||||
return createBaseNode("apply", {}, {
|
||||
body,
|
||||
filters
|
||||
}, line, column, 'apply');
|
||||
|
||||
const applyNode: TwingApplyNode = {
|
||||
...baseNode,
|
||||
execute: async (executionContext) => {
|
||||
const {outputBuffer} = executionContext;
|
||||
const {body, filters} = applyNode.children;
|
||||
const {line, column} = applyNode;
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
return body.execute(executionContext)
|
||||
.then(async () => {
|
||||
let content = outputBuffer.getAndClean();
|
||||
|
||||
const keyValuePairs = getKeyValuePairs(filters);
|
||||
|
||||
while (keyValuePairs.length > 0) {
|
||||
const {key, value: filterArguments} = keyValuePairs.pop()!;
|
||||
|
||||
const filterName = key.attributes.value as string;
|
||||
const filterNode = createFilterNode(createConstantNode(content, line, column), filterName, filterArguments, line, column);
|
||||
|
||||
content = await filterNode.execute(executionContext);
|
||||
}
|
||||
|
||||
outputBuffer.echo(content);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return applyNode;
|
||||
};
|
||||
|
@ -15,15 +15,11 @@ export const createAutoEscapeNode = (
|
||||
body: TwingBaseNode,
|
||||
line: number,
|
||||
column: number,
|
||||
tag: string
|
||||
tag?: string
|
||||
): TwingAutoEscapeNode => {
|
||||
const baseNode = createBaseNode("auto_escape", {
|
||||
return createBaseNode("auto_escape", {
|
||||
strategy
|
||||
}, {
|
||||
body
|
||||
}, line, column, tag);
|
||||
|
||||
return {
|
||||
...baseNode
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {createBaseNode, TwingBaseNode, TwingBaseNodeAttributes} from "../node";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
|
||||
export type TwingBlockReferenceNodeAttributes = TwingBaseNodeAttributes & {
|
||||
name: string;
|
||||
@ -14,29 +13,7 @@ export const createBlockReferenceNode = (
|
||||
column: number,
|
||||
tag: string
|
||||
): TwingBlockReferenceNode => {
|
||||
const outputNode = createBaseNode("block_reference", {
|
||||
return createBaseNode("block_reference", {
|
||||
name
|
||||
}, {}, line, column, tag);
|
||||
|
||||
const blockReferenceNode: TwingBlockReferenceNode = {
|
||||
...outputNode,
|
||||
execute: (executionContext) => {
|
||||
const {template, context, outputBuffer, blocks, sandboxed, sourceMapRuntime} = executionContext;
|
||||
const {name} = blockReferenceNode.attributes;
|
||||
|
||||
const renderBlock = getTraceableMethod(template.renderBlock, blockReferenceNode.line, blockReferenceNode.column, template.name);
|
||||
|
||||
return renderBlock(
|
||||
name,
|
||||
context.clone(),
|
||||
outputBuffer,
|
||||
blocks,
|
||||
true,
|
||||
sandboxed,
|
||||
sourceMapRuntime
|
||||
).then(outputBuffer.echo);
|
||||
}
|
||||
};
|
||||
|
||||
return blockReferenceNode;
|
||||
};
|
||||
|
@ -16,11 +16,5 @@ export const createBlockNode = (
|
||||
column: number,
|
||||
tag: string | null = null
|
||||
): TwingBlockNode => {
|
||||
const baseNode = createBaseNode("block", {name}, {body}, line, column, tag);
|
||||
|
||||
const blockNode: TwingBlockNode = {
|
||||
...baseNode
|
||||
};
|
||||
|
||||
return blockNode;
|
||||
return createBaseNode("block", {name}, {body}, line, column, tag);
|
||||
};
|
||||
|
@ -1,15 +0,0 @@
|
||||
import {TwingBaseNode, TwingBaseNodeAttributes, createBaseNode} from "../node";
|
||||
|
||||
export interface TwingBodyNode extends TwingBaseNode<"body", TwingBaseNodeAttributes, {
|
||||
content: TwingBaseNode;
|
||||
}> {
|
||||
}
|
||||
|
||||
export const createBodyNode = (
|
||||
content: TwingBaseNode,
|
||||
line: number,
|
||||
column: number,
|
||||
tag?: string
|
||||
): TwingBodyNode => createBaseNode("body", {}, {
|
||||
content
|
||||
}, line, column, tag);
|
@ -1,18 +1,9 @@
|
||||
import {TwingBaseNode, TwingBaseNodeAttributes, createBaseNode, TwingNode} from "../node";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedFilterError,
|
||||
TwingSandboxSecurityNotAllowedFilterError
|
||||
} from "../sandbox/security-not-allowed-filter-error";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedTagError,
|
||||
TwingSandboxSecurityNotAllowedTagError
|
||||
} from "../sandbox/security-not-allowed-tag-error";
|
||||
import {TwingSandboxSecurityNotAllowedFunctionError} from "../sandbox/security-not-allowed-function-error";
|
||||
|
||||
export type TwingCheckSecurityNodeAttributes = TwingBaseNodeAttributes & {
|
||||
usedFilters: Map<string, TwingNode | string>;
|
||||
usedTags: Map<string, TwingNode | string>;
|
||||
usedFunctions: Map<string, TwingNode | string>;
|
||||
usedFilters: Map<string, TwingNode>;
|
||||
usedTags: Map<string, TwingNode>;
|
||||
usedFunctions: Map<string, TwingNode>;
|
||||
};
|
||||
|
||||
export interface TwingCheckSecurityNode extends TwingBaseNode<"check_security", TwingCheckSecurityNodeAttributes> {
|
||||
@ -25,43 +16,9 @@ export const createCheckSecurityNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingCheckSecurityNode => {
|
||||
const baseNode = createBaseNode("check_security", {
|
||||
return createBaseNode("check_security", {
|
||||
usedFilters,
|
||||
usedTags,
|
||||
usedFunctions
|
||||
}, {}, line, column);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {template, sandboxed} = executionContext;
|
||||
const {usedTags, usedFunctions, usedFilters} = baseNode.attributes;
|
||||
|
||||
try {
|
||||
sandboxed && template.checkSecurity(
|
||||
[...usedTags.keys()],
|
||||
[...usedFilters.keys()],
|
||||
[...usedFunctions.keys()]
|
||||
);
|
||||
} catch (error: any) {
|
||||
const supplementError = (error: TwingSandboxSecurityNotAllowedFilterError | TwingSandboxSecurityNotAllowedFunctionError | TwingSandboxSecurityNotAllowedTagError) => {
|
||||
error.source = template.name;
|
||||
|
||||
if (isASandboxSecurityNotAllowedTagError(error)) {
|
||||
error.location = usedTags.get(error.tagName);
|
||||
} else if (isASandboxSecurityNotAllowedFilterError(error)) {
|
||||
error.location = usedFilters.get(error.filterName)
|
||||
} else {
|
||||
error.location = usedFunctions.get(error.functionName);
|
||||
}
|
||||
}
|
||||
|
||||
supplementError(error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {TwingBaseNode, TwingBaseNodeAttributes, createBaseNode} from "../node";
|
||||
import {TwingBaseExpressionNode} from "./expression";
|
||||
import {getTraceableMethod} from "../helpers/traceable-method";
|
||||
|
||||
/**
|
||||
* Checks if casting an expression to toString() is allowed by the sandbox.
|
||||
@ -20,36 +19,7 @@ export const createCheckToStringNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingCheckToStringNode => {
|
||||
const baseNode = createBaseNode("check_to_string", {}, {
|
||||
return createBaseNode("check_to_string", {}, {
|
||||
value
|
||||
}, line, column);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {template, sandboxed} = executionContext;
|
||||
const {value: valueNode} = baseNode.children;
|
||||
|
||||
return valueNode.execute(executionContext)
|
||||
.then((value) => {
|
||||
if (sandboxed) {
|
||||
const assertToStringAllowed = getTraceableMethod((value: any) => {
|
||||
if ((value !== null) && (typeof value === 'object')) {
|
||||
try {
|
||||
template.checkMethodAllowed(value, 'toString');
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(value);
|
||||
}, valueNode.line, valueNode.column, template.name)
|
||||
|
||||
return assertToStringAllowed(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -12,11 +12,7 @@ export const createCommentNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingCommentNode => {
|
||||
const baseNode = createBaseNode("comment", {
|
||||
return createBaseNode("comment", {
|
||||
data
|
||||
}, {}, line, column);
|
||||
|
||||
return {
|
||||
...baseNode
|
||||
}
|
||||
};
|
||||
|
@ -12,22 +12,7 @@ export const createDeprecatedNode = (
|
||||
column: number,
|
||||
tag: string
|
||||
): TwingDeprecatedNode => {
|
||||
const baseNode = createBaseNode("deprecated", {}, {
|
||||
return createBaseNode("deprecated", {}, {
|
||||
message
|
||||
}, line, column, tag);
|
||||
|
||||
const deprecatedNode: TwingDeprecatedNode = {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {template} = executionContext;
|
||||
const {message} = deprecatedNode.children;
|
||||
|
||||
return message.execute(executionContext)
|
||||
.then((message) => {
|
||||
console.warn(`${message} ("${template.name}" at line ${deprecatedNode.line}, column ${deprecatedNode.column})`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return deprecatedNode;
|
||||
};
|
||||
|
@ -18,12 +18,7 @@ export const createDoNode = (
|
||||
column: number,
|
||||
tag: string | null
|
||||
): TwingDoNode => {
|
||||
const baseNode = createBaseNode("do", {}, {
|
||||
return createBaseNode("do", {}, {
|
||||
body
|
||||
}, line, column, tag);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: baseNode.children.body.execute
|
||||
};
|
||||
};
|
||||
|
@ -3,11 +3,8 @@ import {
|
||||
TwingBaseExpressionNodeAttributes,
|
||||
createBaseExpressionNode
|
||||
} from "../expression";
|
||||
import {TwingConstantNode, createConstantNode} from "./constant";
|
||||
import {createConstantNode} from "./constant";
|
||||
import {pushToRecord} from "../../helpers/record";
|
||||
import type {TwingNode} from "../../node";
|
||||
|
||||
const array_chunk = require('locutus/php/array/array_chunk');
|
||||
|
||||
export interface TwingBaseArrayNode<Type extends string> extends TwingBaseExpressionNode<Type, TwingBaseExpressionNodeAttributes, Record<number, TwingBaseExpressionNode>> {
|
||||
}
|
||||
@ -16,19 +13,6 @@ export interface TwingBaseArrayNode<Type extends string> extends TwingBaseExpres
|
||||
export interface TwingArrayNode extends TwingBaseArrayNode<"array"> {
|
||||
}
|
||||
|
||||
export const getKeyValuePairs = (
|
||||
node: TwingBaseArrayNode<any>
|
||||
): Array<{
|
||||
key: TwingConstantNode,
|
||||
value: TwingBaseExpressionNode
|
||||
}> => {
|
||||
const chunks: Array<[key: TwingConstantNode, value: TwingBaseExpressionNode]> = array_chunk(Object.values(node.children), 2);
|
||||
|
||||
return chunks.map(([key, value]) => {
|
||||
return {key, value};
|
||||
});
|
||||
};
|
||||
|
||||
export const createBaseArrayNode = <Type extends string>(
|
||||
type: Type,
|
||||
elements: Array<{
|
||||
@ -45,13 +29,7 @@ export const createBaseArrayNode = <Type extends string>(
|
||||
pushToRecord(children, value);
|
||||
}
|
||||
|
||||
const baseNode = createBaseExpressionNode(type, {}, children, line, column);
|
||||
|
||||
const node: TwingBaseArrayNode<Type> = {
|
||||
...baseNode
|
||||
};
|
||||
|
||||
return node;
|
||||
return createBaseExpressionNode(type, {}, children, line, column);
|
||||
};
|
||||
|
||||
export const createArrayNode = (
|
||||
@ -72,23 +50,6 @@ export const createArrayNode = (
|
||||
}), line, column);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: async (executionContext) => {
|
||||
const keyValuePairs = getKeyValuePairs(baseNode);
|
||||
const array: Array<any> = [];
|
||||
|
||||
for (const {value: valueNode} of keyValuePairs) {
|
||||
const value = await valueNode.execute(executionContext);
|
||||
|
||||
if ((valueNode as TwingNode).type === "spread") {
|
||||
array.push(...value);
|
||||
}
|
||||
else {
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
...baseNode
|
||||
};
|
||||
};
|
||||
|
@ -16,31 +16,8 @@ export const createArrowFunctionNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingArrowFunctionNode => {
|
||||
const baseNode = createBaseExpressionNode("arrow_function", {}, {
|
||||
return createBaseExpressionNode("arrow_function", {}, {
|
||||
body,
|
||||
names
|
||||
}, line, column);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {context} = executionContext;
|
||||
const {body} = baseNode.children;
|
||||
const assignmentNodes = Object.values(baseNode.children.names.children);
|
||||
|
||||
return Promise.resolve((...functionArgs: Array<any>): Promise<any> => {
|
||||
let index = 0;
|
||||
|
||||
for (const assignmentNode of assignmentNodes) {
|
||||
const {name} = assignmentNode.attributes;
|
||||
|
||||
context.set(name, functionArgs[index]);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return body.execute(executionContext);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -14,16 +14,7 @@ export const createAssignmentNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingAssignmentNode => {
|
||||
const baseNode = createBaseNode("assignment", {
|
||||
return createBaseNode("assignment", {
|
||||
name
|
||||
}, {}, line, column);
|
||||
|
||||
const assignmentNode: TwingAssignmentNode = {
|
||||
...baseNode,
|
||||
execute: () => {
|
||||
return Promise.resolve(assignmentNode.attributes.name);
|
||||
}
|
||||
};
|
||||
|
||||
return assignmentNode;
|
||||
};
|
||||
|
@ -3,8 +3,6 @@ import {
|
||||
TwingBaseExpressionNodeAttributes,
|
||||
createBaseExpressionNode, TwingExpressionNode
|
||||
} from "../expression";
|
||||
import {getAttribute} from "../../helpers/get-attribute";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export type TwingAttributeAccessorCallType = "any" | "array" | "method";
|
||||
|
||||
@ -32,7 +30,7 @@ export const createAttributeAccessorNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingAttributeAccessorNode => {
|
||||
const baseNode = createBaseExpressionNode("attribute_accessor", {
|
||||
return createBaseExpressionNode("attribute_accessor", {
|
||||
isOptimizable: true,
|
||||
type,
|
||||
shouldTestExistence: false
|
||||
@ -41,37 +39,6 @@ export const createAttributeAccessorNode = (
|
||||
attribute,
|
||||
arguments: methodArguments
|
||||
}, line, column);
|
||||
|
||||
const attributeAccessorNode: TwingAttributeAccessorNode = {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {template, sandboxed, isStrictVariables} = executionContext;
|
||||
const {target, attribute, arguments: methodArguments} = attributeAccessorNode.children;
|
||||
const {type, shouldIgnoreStrictCheck, shouldTestExistence} = attributeAccessorNode.attributes;
|
||||
|
||||
return Promise.all([
|
||||
target.execute(executionContext),
|
||||
attribute.execute(executionContext),
|
||||
methodArguments.execute(executionContext)
|
||||
]).then(([target, attribute, methodArguments]) => {
|
||||
const traceableGetAttribute = getTraceableMethod(getAttribute, attributeAccessorNode.line, attributeAccessorNode.column, template.name);
|
||||
|
||||
return traceableGetAttribute(
|
||||
template,
|
||||
target,
|
||||
attribute,
|
||||
methodArguments,
|
||||
type,
|
||||
shouldTestExistence,
|
||||
shouldIgnoreStrictCheck || null,
|
||||
sandboxed,
|
||||
isStrictVariables
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return attributeAccessorNode;
|
||||
};
|
||||
|
||||
export const cloneGetAttributeNode = (
|
||||
|
@ -29,7 +29,6 @@ import {createBaseExpressionNode} from "../expression";
|
||||
import type {TwingSpaceshipNode} from "./binary/spaceship";
|
||||
import type {TwingHasEveryNode} from "./binary/has-every";
|
||||
import type {TwingHasSomeNode} from "./binary/has-some";
|
||||
import type {TwingExecutionContext} from "../../execution-context";
|
||||
|
||||
export type TwingBinaryNode =
|
||||
| TwingAddNode
|
||||
@ -87,13 +86,6 @@ export const createBaseBinaryNode = <Type extends string>(
|
||||
|
||||
export const createBinaryNodeFactory = <InstanceType extends TwingBaseBinaryNode<any>>(
|
||||
type: TwingNodeType<InstanceType>,
|
||||
definition: {
|
||||
execute: (
|
||||
left: TwingBaseExpressionNode,
|
||||
right: TwingBaseExpressionNode,
|
||||
executionContext: TwingExecutionContext
|
||||
) => Promise<any>
|
||||
}
|
||||
) => {
|
||||
const factory = (
|
||||
operands: [TwingBaseExpressionNode, TwingBaseExpressionNode],
|
||||
@ -104,9 +96,6 @@ export const createBinaryNodeFactory = <InstanceType extends TwingBaseBinaryNode
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
return definition.execute(baseNode.children.left, baseNode.children.right, executionContext);
|
||||
}
|
||||
} as InstanceType;
|
||||
};
|
||||
|
||||
|
@ -4,8 +4,4 @@ import {createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingAddNode extends TwingBaseBinaryNode<"add"> {
|
||||
}
|
||||
|
||||
export const createAddNode = createBinaryNodeFactory<TwingAddNode>("add", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) + await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createAddNode = createBinaryNodeFactory<TwingAddNode>("add");
|
||||
|
@ -4,8 +4,4 @@ import {createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingAndNode extends TwingBaseBinaryNode<"and"> {
|
||||
}
|
||||
|
||||
export const createAndNode = createBinaryNodeFactory<TwingAndNode>("and", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return !!(await left.execute(executionContext) && await right.execute(executionContext));
|
||||
}
|
||||
});
|
||||
export const createAndNode = createBinaryNodeFactory<TwingAndNode>("and");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingBitwiseAndNode extends TwingBaseBinaryNode<"bitwise_and"> {
|
||||
}
|
||||
|
||||
export const createBitwiseAndNode = createBinaryNodeFactory<TwingBitwiseAndNode>("bitwise_and", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) & await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createBitwiseAndNode = createBinaryNodeFactory<TwingBitwiseAndNode>("bitwise_and",);
|
||||
|
@ -4,8 +4,4 @@ import {createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingBitwiseOrNode extends TwingBaseBinaryNode<"bitwise_or"> {
|
||||
}
|
||||
|
||||
export const createBitwiseOrNode = createBinaryNodeFactory<TwingBitwiseOrNode>("bitwise_or", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) | await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createBitwiseOrNode = createBinaryNodeFactory<TwingBitwiseOrNode>("bitwise_or");
|
||||
|
@ -4,8 +4,4 @@ import {createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingBitwiseXorNode extends TwingBaseBinaryNode<"bitwise_xor"> {
|
||||
}
|
||||
|
||||
export const createBitwiseXorNode = createBinaryNodeFactory<TwingBitwiseXorNode>("bitwise_xor", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) ^ await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createBitwiseXorNode = createBinaryNodeFactory<TwingBitwiseXorNode>("bitwise_xor");
|
||||
|
@ -1,15 +1,7 @@
|
||||
import type {TwingBaseBinaryNode} from "../binary";
|
||||
import {createBinaryNodeFactory} from "../binary";
|
||||
import {concatenate} from "../../../helpers/concatenate";
|
||||
|
||||
export interface TwingConcatenateNode extends TwingBaseBinaryNode<"concatenate"> {
|
||||
}
|
||||
|
||||
export const createConcatenateNode = createBinaryNodeFactory<TwingConcatenateNode>("concatenate", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
return concatenate(leftValue, rightValue);
|
||||
}
|
||||
});
|
||||
export const createConcatenateNode = createBinaryNodeFactory<TwingConcatenateNode>("concatenate");
|
||||
|
@ -4,8 +4,4 @@ import {createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingDivideAndFloorNode extends TwingBaseBinaryNode<"divide_and_floor"> {
|
||||
}
|
||||
|
||||
export const createDivideAndFloorNode = createBinaryNodeFactory<TwingDivideAndFloorNode>("divide_and_floor", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return Math.floor(await left.execute(executionContext) / await right.execute(executionContext));
|
||||
}
|
||||
});
|
||||
export const createDivideAndFloorNode = createBinaryNodeFactory<TwingDivideAndFloorNode>("divide_and_floor");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingDivideNode extends TwingBaseBinaryNode<"divide"> {
|
||||
}
|
||||
|
||||
export const createDivideNode = createBinaryNodeFactory<TwingDivideNode>("divide", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) / await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createDivideNode = createBinaryNodeFactory<TwingDivideNode>("divide");
|
||||
|
@ -4,23 +4,4 @@ export interface TwingEndsWithNode extends TwingBaseBinaryNode<"ends_with"> {
|
||||
|
||||
}
|
||||
|
||||
export const createEndsWithNode = createBinaryNodeFactory<TwingEndsWithNode>(
|
||||
"ends_with",
|
||||
{
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
|
||||
if (typeof leftValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
if (typeof rightValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rightValue.length < 1 || leftValue.endsWith(rightValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
export const createEndsWithNode = createBinaryNodeFactory<TwingEndsWithNode>("ends_with");
|
||||
|
@ -1,26 +1,7 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {every, isAMapLike} from "../../../helpers/map-like";
|
||||
|
||||
export interface TwingHasEveryNode extends TwingBaseBinaryNode<"has_every"> {
|
||||
|
||||
}
|
||||
|
||||
export const createHasEveryNode = createBinaryNodeFactory<TwingHasEveryNode>(
|
||||
"has_every",
|
||||
{
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
if (typeof rightValue !== "function") {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
return every(leftValue, rightValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
export const createHasEveryNode = createBinaryNodeFactory<TwingHasEveryNode>("has_every");
|
||||
|
@ -1,26 +1,7 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {isAMapLike, some} from "../../../helpers/map-like";
|
||||
|
||||
export interface TwingHasSomeNode extends TwingBaseBinaryNode<"has_some"> {
|
||||
|
||||
}
|
||||
|
||||
export const createHasSomeNode = createBinaryNodeFactory<TwingHasSomeNode>(
|
||||
"has_some",
|
||||
{
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
if (typeof rightValue !== "function") {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
if (!isAMapLike(leftValue) && !Array.isArray(leftValue)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return some(leftValue, rightValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
export const createHasSomeNode = createBinaryNodeFactory<TwingHasSomeNode>("has_some");
|
||||
|
@ -1,14 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {compare} from "../../../helpers/compare";
|
||||
|
||||
export interface TwingIsEqualToNode extends TwingBaseBinaryNode<"is_equal_to"> {
|
||||
}
|
||||
|
||||
export const createIsEqualNode = createBinaryNodeFactory<TwingIsEqualToNode>("is_equal_to", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
return compare(leftValue, rightValue);
|
||||
}
|
||||
});
|
||||
export const createIsEqualNode = createBinaryNodeFactory<TwingIsEqualToNode>("is_equal_to");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingIsGreaterThanOrEqualToNode extends TwingBaseBinaryNode<"is_greater_than_or_equal_to"> {
|
||||
}
|
||||
|
||||
export const createIsGreaterThanOrEqualToNode = createBinaryNodeFactory<TwingIsGreaterThanOrEqualToNode>("is_greater_than_or_equal_to", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) >= await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createIsGreaterThanOrEqualToNode = createBinaryNodeFactory<TwingIsGreaterThanOrEqualToNode>("is_greater_than_or_equal_to");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingIsGreaterThanNode extends TwingBaseBinaryNode<"is_greater_than"> {
|
||||
}
|
||||
|
||||
export const createIsGreaterThanNode = createBinaryNodeFactory<TwingIsGreaterThanNode>("is_greater_than", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) > await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createIsGreaterThanNode = createBinaryNodeFactory<TwingIsGreaterThanNode>("is_greater_than");
|
||||
|
@ -1,11 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {isIn} from "../../../helpers/is-in";
|
||||
|
||||
export interface TwingIsInNode extends TwingBaseBinaryNode<"is_in"> {
|
||||
}
|
||||
|
||||
export const createIsInNode = createBinaryNodeFactory<TwingIsInNode>("is_in", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return isIn(await left.execute(executionContext), await right.execute(executionContext));
|
||||
}
|
||||
});
|
||||
export const createIsInNode = createBinaryNodeFactory<TwingIsInNode>("is_in");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingIsLessThanOrEqualToNode extends TwingBaseBinaryNode<"is_less_than_or_equal_to"> {
|
||||
}
|
||||
|
||||
export const createIsLessThanOrEqualToNode = createBinaryNodeFactory<TwingIsLessThanOrEqualToNode>("is_less_than_or_equal_to", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) <= await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createIsLessThanOrEqualToNode = createBinaryNodeFactory<TwingIsLessThanOrEqualToNode>("is_less_than_or_equal_to");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingIsLessThanNode extends TwingBaseBinaryNode<"is_less_than"> {
|
||||
}
|
||||
|
||||
export const createIsLessThanNode = createBinaryNodeFactory<TwingIsLessThanNode>("is_less_than", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) < await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createIsLessThanNode = createBinaryNodeFactory<TwingIsLessThanNode>("is_less_than");
|
||||
|
@ -1,11 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {compare} from "../../../helpers/compare";
|
||||
|
||||
export interface TwingIsNotEqualToNode extends TwingBaseBinaryNode<"is_not_equal_to"> {
|
||||
}
|
||||
|
||||
export const createIsNotEqualToNode = createBinaryNodeFactory<TwingIsNotEqualToNode>("is_not_equal_to", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return Promise.resolve(!compare(await left.execute(executionContext), await right.execute(executionContext)))
|
||||
}
|
||||
});
|
||||
export const createIsNotEqualToNode = createBinaryNodeFactory<TwingIsNotEqualToNode>("is_not_equal_to");
|
||||
|
@ -1,11 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {isIn} from "../../../helpers/is-in";
|
||||
|
||||
export interface TwingIsNotInNode extends TwingBaseBinaryNode<"is_not_in"> {
|
||||
}
|
||||
|
||||
export const createIsNotInNode = createBinaryNodeFactory<TwingIsNotInNode>("is_not_in", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return Promise.resolve(!isIn(await left.execute(executionContext), await right.execute(executionContext)))
|
||||
}
|
||||
});
|
||||
export const createIsNotInNode = createBinaryNodeFactory<TwingIsNotInNode>("is_not_in");
|
||||
|
@ -1,15 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {parseRegularExpression} from "../../../helpers/parse-regular-expression";
|
||||
|
||||
export interface TwingMatchesNode extends TwingBaseBinaryNode<"matches"> {
|
||||
}
|
||||
|
||||
export const createMatchesNode = createBinaryNodeFactory<TwingMatchesNode>("matches", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return parseRegularExpression(
|
||||
await right.execute(executionContext)
|
||||
).test(
|
||||
await left.execute(executionContext)
|
||||
);
|
||||
}
|
||||
});
|
||||
export const createMatchesNode = createBinaryNodeFactory<TwingMatchesNode>("matches");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingModuloNode extends TwingBaseBinaryNode<"modulo"> {
|
||||
}
|
||||
|
||||
export const createModuloNode = createBinaryNodeFactory<TwingModuloNode>("modulo", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) % await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createModuloNode = createBinaryNodeFactory<TwingModuloNode>("modulo");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingMultiplyNode extends TwingBaseBinaryNode<"multiply"> {
|
||||
}
|
||||
|
||||
export const createMultiplyNode = createBinaryNodeFactory<TwingMultiplyNode>("multiply", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) * await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createMultiplyNode = createBinaryNodeFactory<TwingMultiplyNode>("multiply");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingOrNode extends TwingBaseBinaryNode<"or"> {
|
||||
}
|
||||
|
||||
export const createOrNode = createBinaryNodeFactory<TwingOrNode>("or", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return !!(await left.execute(executionContext) || await right.execute(executionContext));
|
||||
}
|
||||
});
|
||||
export const createOrNode = createBinaryNodeFactory<TwingOrNode>("or");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingPowerNode extends TwingBaseBinaryNode<"power"> {
|
||||
}
|
||||
|
||||
export const createPowerNode = createBinaryNodeFactory<TwingPowerNode>("power", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return Math.pow(await left.execute(executionContext), await right.execute(executionContext));
|
||||
}
|
||||
});
|
||||
export const createPowerNode = createBinaryNodeFactory<TwingPowerNode>("power");
|
||||
|
@ -1,14 +1,6 @@
|
||||
import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
import {createRange} from "../../../helpers/create-range";
|
||||
|
||||
export interface TwingRangeNode extends TwingBaseBinaryNode<"range"> {
|
||||
}
|
||||
|
||||
export const createRangeNode = createBinaryNodeFactory<TwingRangeNode>("range", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
return createRange(leftValue, rightValue, 1);
|
||||
}
|
||||
});
|
||||
export const createRangeNode = createBinaryNodeFactory<TwingRangeNode>("range");
|
||||
|
@ -1,15 +1,7 @@
|
||||
import type {TwingBaseBinaryNode} from "../binary";
|
||||
import {createBinaryNodeFactory} from "../binary";
|
||||
import {compare} from "../../../helpers/compare";
|
||||
|
||||
export interface TwingSpaceshipNode extends TwingBaseBinaryNode<"spaceship"> {
|
||||
}
|
||||
|
||||
export const createSpaceshipNode = createBinaryNodeFactory<TwingSpaceshipNode>("spaceship", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
return compare(leftValue, rightValue) ? 0 : (leftValue < rightValue ? -1 : 1);
|
||||
}
|
||||
});
|
||||
export const createSpaceshipNode = createBinaryNodeFactory<TwingSpaceshipNode>("spaceship");
|
||||
|
@ -3,20 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingStartsWithNode extends TwingBaseBinaryNode<"starts_with"> {
|
||||
}
|
||||
|
||||
export const createStartsWithNode = createBinaryNodeFactory<TwingStartsWithNode>("starts_with", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
const leftValue = await left.execute(executionContext);
|
||||
|
||||
if (typeof leftValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rightValue = await right.execute(executionContext);
|
||||
|
||||
if (typeof rightValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rightValue.length < 1 || leftValue.startsWith(rightValue);
|
||||
}
|
||||
});
|
||||
export const createStartsWithNode = createBinaryNodeFactory<TwingStartsWithNode>("starts_with");
|
||||
|
@ -3,8 +3,4 @@ import {TwingBaseBinaryNode, createBinaryNodeFactory} from "../binary";
|
||||
export interface TwingSubtractNode extends TwingBaseBinaryNode<"subtract"> {
|
||||
}
|
||||
|
||||
export const createSubtractNode = createBinaryNodeFactory<TwingSubtractNode>("subtract", {
|
||||
execute: async (left, right, executionContext) => {
|
||||
return await left.execute(executionContext) - await right.execute(executionContext);
|
||||
}
|
||||
});
|
||||
export const createSubtractNode = createBinaryNodeFactory<TwingSubtractNode>("subtract");
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {TwingBaseExpressionNode, TwingBaseExpressionNodeAttributes, createBaseExpressionNode} from "../expression";
|
||||
import {TwingBaseNode} from "../../node";
|
||||
import {TwingTemplate} from "../../template";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export type TwingBlockFunctionNodeAttributes = TwingBaseExpressionNodeAttributes & {
|
||||
shouldTestExistence: boolean;
|
||||
@ -30,55 +28,9 @@ export const createBlockFunctionNode = (
|
||||
children.template = template;
|
||||
}
|
||||
|
||||
const baseNode = createBaseExpressionNode("block_function", {
|
||||
return createBaseExpressionNode("block_function", {
|
||||
shouldTestExistence: false
|
||||
}, children, line, column, tag);
|
||||
|
||||
const blockFunctionNode: TwingBlockFunctionNode = {
|
||||
...baseNode,
|
||||
execute: async (executionContext) => {
|
||||
const {template, context, outputBuffer, blocks, sandboxed, sourceMapRuntime} = executionContext;
|
||||
const {template: templateNode, name: blockNameNode} = blockFunctionNode.children;
|
||||
|
||||
const blockName = await blockNameNode.execute(executionContext);
|
||||
|
||||
let resolveTemplate: Promise<TwingTemplate>;
|
||||
|
||||
if (templateNode) {
|
||||
const templateName = await templateNode.execute(executionContext);
|
||||
|
||||
const loadTemplate = getTraceableMethod(
|
||||
template.loadTemplate,
|
||||
templateNode.line,
|
||||
templateNode.column,
|
||||
template.name
|
||||
);
|
||||
|
||||
resolveTemplate = loadTemplate(templateName);
|
||||
} else {
|
||||
resolveTemplate = Promise.resolve(template)
|
||||
}
|
||||
|
||||
return resolveTemplate
|
||||
.then<Promise<boolean | string>>((executionContextOfTheBlock) => {
|
||||
if (blockFunctionNode.attributes.shouldTestExistence) {
|
||||
const hasBlock = getTraceableMethod(executionContextOfTheBlock.hasBlock, blockFunctionNode.line, blockFunctionNode.column, template.name);
|
||||
|
||||
return hasBlock(blockName, context.clone(), outputBuffer, blocks, sandboxed);
|
||||
} else {
|
||||
const renderBlock = getTraceableMethod(executionContextOfTheBlock.renderBlock, blockFunctionNode.line, blockFunctionNode.column, template.name);
|
||||
|
||||
if (templateNode) {
|
||||
return renderBlock(blockName, context.clone(), outputBuffer, new Map(), false, sandboxed, sourceMapRuntime);
|
||||
} else {
|
||||
return renderBlock(blockName, context.clone(), outputBuffer, blocks, true, sandboxed, sourceMapRuntime);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return blockFunctionNode;
|
||||
};
|
||||
|
||||
export const cloneBlockReferenceExpressionNode = (
|
||||
|
@ -4,17 +4,10 @@ import {
|
||||
createBaseExpressionNode
|
||||
} from "../expression";
|
||||
import {TwingBaseNode} from "../../node";
|
||||
import {TwingConstantNode, createConstantNode} from "./constant";
|
||||
import {TwingArrayNode, getKeyValuePairs} from "./array";
|
||||
import {TwingCallableArgument, TwingCallableWrapper} from "../../callable-wrapper";
|
||||
import {TwingArrayNode} from "./array";
|
||||
import type {TwingFilterNode} from "./call/filter";
|
||||
import type {TwingFunctionNode} from "./call/function";
|
||||
import type {TwingTestNode} from "./call/test";
|
||||
import {createRuntimeError} from "../../error/runtime";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
const array_merge = require('locutus/php/array/array_merge');
|
||||
const snakeCase = require('snake-case');
|
||||
|
||||
export type TwingCallNode =
|
||||
| TwingFilterNode
|
||||
@ -51,174 +44,7 @@ export const createBaseCallNode = <Type extends "filter" | "function" | "test">(
|
||||
children.operand = operand;
|
||||
}
|
||||
|
||||
const baseNode: TwingBaseExpressionNode<typeof type, TwingBaseCallNodeAttributes, typeof children> = createBaseExpressionNode(type, {
|
||||
return createBaseExpressionNode(type, {
|
||||
operatorName
|
||||
}, children, line, column);
|
||||
|
||||
const normalizeName = (name: string) => {
|
||||
return snakeCase(name).toLowerCase();
|
||||
};
|
||||
|
||||
const getArguments = (
|
||||
argumentsNode: TwingArrayNode,
|
||||
acceptedArguments: Array<TwingCallableArgument>,
|
||||
isVariadic: boolean
|
||||
): Array<TwingBaseNode> => {
|
||||
const callType = type;
|
||||
const callName = baseNode.attributes.operatorName;
|
||||
const parameters: Map<string | number, {
|
||||
key: TwingConstantNode;
|
||||
value: TwingBaseExpressionNode;
|
||||
}> = new Map();
|
||||
|
||||
let named = false;
|
||||
|
||||
const keyPairs = getKeyValuePairs(argumentsNode);
|
||||
|
||||
for (let {key, value} of keyPairs) {
|
||||
let name = key.attributes.value as string | number;
|
||||
|
||||
if (typeof name === "string") {
|
||||
named = true;
|
||||
name = normalizeName(name);
|
||||
}
|
||||
else if (named) {
|
||||
throw createRuntimeError(`Positional arguments cannot be used after named arguments for ${callType} "${callName}".`, baseNode);
|
||||
}
|
||||
|
||||
parameters.set(name, {
|
||||
key,
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
const callableParameters = acceptedArguments;
|
||||
const names: Array<string> = [];
|
||||
|
||||
let optionalArguments: Array<string | TwingConstantNode> = [];
|
||||
let arguments_: Array<TwingBaseNode> = [];
|
||||
let position = 0;
|
||||
|
||||
for (const callableParameter of callableParameters) {
|
||||
const name = '' + normalizeName(callableParameter.name);
|
||||
|
||||
names.push(name);
|
||||
|
||||
const parameter = parameters.get(name);
|
||||
|
||||
if (parameter) {
|
||||
if (parameters.has(position)) {
|
||||
throw createRuntimeError(`Argument "${name}" is defined twice for ${callType} "${callName}".`, baseNode);
|
||||
}
|
||||
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(parameter.value);
|
||||
parameters.delete(name);
|
||||
optionalArguments = [];
|
||||
}
|
||||
else {
|
||||
const parameter = parameters.get(position);
|
||||
|
||||
if (parameter) {
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(parameter.value);
|
||||
parameters.delete(position);
|
||||
optionalArguments = [];
|
||||
++position;
|
||||
}
|
||||
else if (callableParameter.defaultValue !== undefined) {
|
||||
arguments_.push(createConstantNode(callableParameter.defaultValue, line, column));
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`Value for argument "${name}" is required for ${callType} "${callName}".`, baseNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isVariadic) {
|
||||
const resolvedKeys: Array<any> = [];
|
||||
const arbitraryArguments: Array<TwingBaseExpressionNode> = [];
|
||||
|
||||
for (const [key, value] of parameters) {
|
||||
arbitraryArguments.push(value.value);
|
||||
|
||||
resolvedKeys.push(key);
|
||||
}
|
||||
|
||||
for (const key of resolvedKeys) {
|
||||
parameters.delete(key);
|
||||
}
|
||||
|
||||
if (arbitraryArguments.length) {
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
arguments_.push(...arbitraryArguments);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.size > 0) {
|
||||
const unknownParameter = [...parameters.values()][0];
|
||||
|
||||
throw createRuntimeError(`Unknown argument${parameters.size > 1 ? 's' : ''} "${[...parameters.keys()].join('", "')}" for ${callType} "${callName}(${names.join(', ')})".`, unknownParameter.key);
|
||||
}
|
||||
|
||||
return arguments_;
|
||||
}
|
||||
|
||||
const baseCallNode: TwingBaseCallNode<typeof type> = {
|
||||
...baseNode,
|
||||
execute: async (executionContext) => {
|
||||
const {template} = executionContext
|
||||
const {operatorName} = baseCallNode.attributes;
|
||||
|
||||
let callableWrapper: TwingCallableWrapper | null;
|
||||
|
||||
switch (type) {
|
||||
case "filter":
|
||||
callableWrapper = template.getFilter(operatorName);
|
||||
break;
|
||||
|
||||
case "function":
|
||||
callableWrapper = template.getFunction(operatorName);
|
||||
break;
|
||||
|
||||
// for some reason, using `case "test"` makes the compiler assume that callableWrapper is used
|
||||
// before it is assigned a value; this is probably a bug of the compiler
|
||||
default:
|
||||
callableWrapper = template.getTest(operatorName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (callableWrapper === null) {
|
||||
throw createRuntimeError(`Unknown ${type} "${operatorName}".`, baseNode);
|
||||
}
|
||||
|
||||
const {operand, arguments: callArguments} = baseCallNode.children;
|
||||
|
||||
const argumentNodes = getArguments(
|
||||
callArguments,
|
||||
callableWrapper.acceptedArguments,
|
||||
callableWrapper.isVariadic
|
||||
);
|
||||
|
||||
const actualArguments: Array<any> = [];
|
||||
|
||||
actualArguments.push(...callableWrapper!.nativeArguments);
|
||||
|
||||
if (operand) {
|
||||
actualArguments.push(await operand.execute(executionContext));
|
||||
}
|
||||
|
||||
const providedArguments = await Promise.all([
|
||||
...argumentNodes.map((node) => node.execute(executionContext))
|
||||
]);
|
||||
|
||||
actualArguments.push(...providedArguments);
|
||||
|
||||
const traceableCallable = getTraceableMethod(callableWrapper.callable, baseCallNode.line, baseCallNode.column, template.name);
|
||||
|
||||
return traceableCallable(executionContext, ...actualArguments);
|
||||
}
|
||||
};
|
||||
|
||||
return baseCallNode;
|
||||
};
|
||||
|
@ -12,7 +12,5 @@ export const createFilterNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingFilterNode => {
|
||||
const node = createBaseCallNode("filter", filterName, operand, filterArguments, line, column);
|
||||
|
||||
return node;
|
||||
return createBaseCallNode("filter", filterName, operand, filterArguments, line, column);
|
||||
};
|
||||
|
@ -10,7 +10,5 @@ export const createFunctionNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingFunctionNode => {
|
||||
const node = createBaseCallNode("function", functionName, null, functionArguments, line, column);
|
||||
|
||||
return node;
|
||||
return createBaseCallNode("function", functionName, null, functionArguments, line, column);
|
||||
};
|
||||
|
@ -12,7 +12,5 @@ export const createTestNode = (
|
||||
line: number,
|
||||
column: number
|
||||
): TwingTestNode => {
|
||||
const node = createBaseCallNode("test", testName, operand, testArguments, line, column);
|
||||
|
||||
return node;
|
||||
return createBaseCallNode("test", testName, operand, testArguments, line, column);
|
||||
};
|
||||
|
@ -23,18 +23,9 @@ export const createBaseConditionalNode = <Type extends string>(
|
||||
line: number,
|
||||
column: number
|
||||
): TwingBaseConditionalNode<Type> => {
|
||||
const baseNode = createBaseExpressionNode(type, {}, {
|
||||
return createBaseExpressionNode(type, {}, {
|
||||
expr1, expr2, expr3
|
||||
}, line, column);
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
execute: async (executionContext) => {
|
||||
const {expr1, expr2, expr3} = baseNode.children;
|
||||
|
||||
return (await expr1.execute(executionContext)) ? expr2.execute(executionContext) : expr3.execute(executionContext);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const createConditionalNode = (
|
||||
|
@ -15,16 +15,7 @@ export const createConstantNode = <Value extends string | number | boolean | nul
|
||||
line: number,
|
||||
column: number
|
||||
): TwingConstantNode<Value> => {
|
||||
const parent = createBaseExpressionNode("constant", {
|
||||
return createBaseExpressionNode("constant", {
|
||||
value
|
||||
}, {}, line, column);
|
||||
|
||||
const constantNode: TwingConstantNode<Value> = {
|
||||
...parent,
|
||||
execute: () => {
|
||||
return Promise.resolve(constantNode.attributes.value);
|
||||
}
|
||||
};
|
||||
|
||||
return constantNode;
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {TwingBaseNodeAttributes, TwingBaseNode} from "../../node";
|
||||
import {TwingBaseExpressionNode, createBaseExpressionNode} from "../expression";
|
||||
import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
|
||||
export interface TwingEscapeNodeAttributes extends TwingBaseNodeAttributes {
|
||||
strategy: string;
|
||||
@ -15,27 +14,9 @@ export const createEscapeNode = (
|
||||
body: TwingBaseNode,
|
||||
strategy: string
|
||||
): TwingEscapeNode => {
|
||||
const baseNode = createBaseExpressionNode("escape", {
|
||||
return createBaseExpressionNode("escape", {
|
||||
strategy
|
||||
}, {
|
||||
body
|
||||
}, body.line, body.column);
|
||||
|
||||
const escapeNode: TwingEscapeNode = {
|
||||
...baseNode,
|
||||
execute: (executionContext) => {
|
||||
const {template} = executionContext;
|
||||
const {strategy} = escapeNode.attributes;
|
||||
const {body} = escapeNode.children;
|
||||
|
||||
return body.execute(executionContext)
|
||||
.then((value) => {
|
||||
const escape = getTraceableMethod(template.escape, escapeNode.line, escapeNode.column, template.name);
|
||||
|
||||
return escape(value, strategy, null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return escapeNode;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user