twing
Version:
First-class Twig engine for Node.js
164 lines (163 loc) • 7.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeCallNodeSynchronously = exports.executeCallNode = void 0;
const runtime_1 = require("../../error/runtime");
const traceable_method_1 = require("../../helpers/traceable-method");
const constant_1 = require("../../node/expression/constant");
const get_key_value_pairs_1 = require("../../helpers/get-key-value-pairs");
const get_test_1 = require("../../helpers/get-test");
const get_function_1 = require("../../helpers/get-function");
const get_filter_1 = require("../../helpers/get-filter");
const array_merge = require('locutus/php/array/array_merge');
const snakeCase = require('snake-case');
const normalizeName = (name) => {
return snakeCase(name).toLowerCase();
};
const getArguments = (node, template, argumentsNode, acceptedArguments, isVariadic) => {
const callType = node.type;
const callName = node.attributes.operatorName;
const parameters = new Map();
let named = false;
const keyPairs = (0, get_key_value_pairs_1.getKeyValuePairs)(argumentsNode);
for (let { key, value } of keyPairs) {
let name = key.attributes.value;
if (typeof name === "string") {
named = true;
name = normalizeName(name);
}
else if (named) {
throw (0, runtime_1.createRuntimeError)(`Positional arguments cannot be used after named arguments for ${callType} "${callName}".`, node, template.source);
}
parameters.set(name, {
key,
value
});
}
const callableParameters = acceptedArguments;
const names = [];
let optionalArguments = [];
let arguments_ = [];
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 (0, runtime_1.createRuntimeError)(`Argument "${name}" is defined twice for ${callType} "${callName}".`, node, template.source);
}
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((0, constant_1.createConstantNode)(callableParameter.defaultValue, node.line, node.column));
}
else {
throw (0, runtime_1.createRuntimeError)(`Value for argument "${name}" is required for ${callType} "${callName}".`, node, template.source);
}
}
}
if (isVariadic) {
const resolvedKeys = [];
const arbitraryArguments = [];
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 (0, runtime_1.createRuntimeError)(`Unknown argument${parameters.size > 1 ? 's' : ''} "${[...parameters.keys()].join('", "')}" for ${callType} "${callName}(${names.join(', ')})".`, unknownParameter.key, template.source);
}
return arguments_;
};
const executeCallNode = async (node, executionContext) => {
const { type } = node;
const { template, environment, nodeExecutor: execute } = executionContext;
const { operatorName } = node.attributes;
let callableWrapper;
switch (type) {
case "filter":
callableWrapper = (0, get_filter_1.getFilter)(environment.filters, operatorName);
break;
case "function":
callableWrapper = (0, get_function_1.getFunction)(environment.functions, 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 = (0, get_test_1.getTest)(environment.tests, operatorName);
break;
}
if (callableWrapper === null) {
throw (0, runtime_1.createRuntimeError)(`Unknown ${type} "${operatorName}".`, node, template.source);
}
const { operand, arguments: callArguments } = node.children;
const argumentNodes = getArguments(node, template, callArguments, callableWrapper.acceptedArguments, callableWrapper.isVariadic);
const actualArguments = [];
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 = (0, traceable_method_1.getTraceableMethod)(callableWrapper.callable, node, template.source);
return traceableCallable(executionContext, ...actualArguments).then((value) => {
return value;
});
};
exports.executeCallNode = executeCallNode;
const executeCallNodeSynchronously = (node, executionContext) => {
const { type } = node;
const { template, environment, nodeExecutor: execute } = executionContext;
const { operatorName } = node.attributes;
let callableWrapper;
switch (type) {
case "filter":
callableWrapper = (0, get_filter_1.getFilter)(environment.filters, operatorName);
break;
case "function":
callableWrapper = (0, get_function_1.getFunction)(environment.functions, 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 = (0, get_test_1.getTest)(environment.tests, operatorName);
break;
}
if (callableWrapper === null) {
throw (0, runtime_1.createRuntimeError)(`Unknown ${type} "${operatorName}".`, node, template.source);
}
const { operand, arguments: callArguments } = node.children;
const argumentNodes = getArguments(node, template, callArguments, callableWrapper.acceptedArguments, callableWrapper.isVariadic);
const actualArguments = [];
actualArguments.push(...callableWrapper.nativeArguments);
if (operand) {
actualArguments.push(execute(operand, executionContext));
}
const providedArguments = argumentNodes.map((node) => execute(node, executionContext));
actualArguments.push(...providedArguments);
const traceableCallable = (0, traceable_method_1.getSynchronousTraceableMethod)(callableWrapper.callable, node, template.source);
return traceableCallable(executionContext, ...actualArguments);
};
exports.executeCallNodeSynchronously = executeCallNodeSynchronously;