Resolve issue #610

This commit is contained in:
Eric MORAND 2024-03-12 16:38:45 +01:00
parent e55bb6a14c
commit 4340596d3d
34 changed files with 265 additions and 285 deletions

View File

@ -44,7 +44,7 @@
"create-hash": "^1.2.0",
"esrever": "^0.2.0",
"htmlspecialchars": "^1.0.5",
"iconv-lite": "^0.4.19",
"iconv-lite": "^0.6.3",
"is-plain-object": "^2.0.4",
"isobject": "^3.0.1",
"levenshtein": "^1.0.5",

View File

@ -293,7 +293,6 @@ export type {TwingSandboxSecurityNotAllowedTagError} from "./lib/sandbox/securit
export type {TwingSource} from "./lib/source";
export type {TwingSourceMapRuntime} from "./lib/source-map-runtime";
export type {
TwingTemplate,
TwingTemplateAliases,
TwingTemplateBlockMap,
TwingTemplateBlockHandler,
@ -302,6 +301,11 @@ export type {
export type {TwingTest} from "./lib/test";
export type {TwingTokenStream} from "./lib/token-stream";
export interface TwingTemplate {
execute: import("./lib/template").TwingTemplate["execute"];
render: import("./lib/template").TwingTemplate["render"];
}
export {createEnvironment} from "./lib/environment";
export {createExtensionSet} from "./lib/extension-set";
export {createFilter} from "./lib/filter";

View File

@ -319,7 +319,7 @@ export const createEnvironment = (
throw createTemplateLoadingError([name]);
}
const template = createTemplate(environment, ast);
const template = createTemplate(ast);
loadedTemplates.set(templateFqn, template);
@ -383,7 +383,7 @@ export const createEnvironment = (
render: (name, context) => {
return environment.loadTemplate(name)
.then((template) => {
return template.render(context, {
return template.render(environment, context, {
sandboxed: isSandboxed
});
});
@ -393,7 +393,7 @@ export const createEnvironment = (
return environment.loadTemplate(name)
.then((template) => {
return template.render(context, {
return template.render(environment, context, {
sandboxed: isSandboxed,
sourceMapRuntime
});

View File

@ -2,22 +2,17 @@ import type {TwingTemplate, TwingTemplateAliases, TwingTemplateBlockMap} from ".
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 {TwingEnvironment} from "./environment";
import type {TwingNodeExecutor} from "./node-executor";
export type TwingExecutionContext = {
aliases: TwingTemplateAliases;
blocks: TwingTemplateBlockMap;
charset: string,
context: TwingContext<any, any>;
dateFormat: string;
dateIntervalFormat: string;
isStrictVariables: boolean;
environment: TwingEnvironment;
nodeExecutor: TwingNodeExecutor;
numberFormat: TwingNumberFormat;
outputBuffer: TwingOutputBuffer;
sandboxed: boolean;
sourceMapRuntime?: TwingSourceMapRuntime;
template: TwingTemplate;
timezone: string;
};

View File

@ -20,7 +20,8 @@ export const dateModify: TwingCallable = (
date: Date | DateTime | string,
modifier: string
): Promise<DateTime> => {
const {timezone: defaultTimezone} = executionContext;
const {environment} = executionContext;
const {timezone: defaultTimezone} = environment;
return createDate(defaultTimezone, date, null)
.then((dateTime) => {

View File

@ -24,7 +24,8 @@ export const date: TwingCallable = (
format: string | null,
timezone: string | null | false
): Promise<string> => {
const {dateFormat, dateIntervalFormat} = executionContext;
const {environment} = executionContext;
const {dateFormat, dateIntervalFormat} = environment;
return createDate(executionContext, date, timezone)
.then((date) => {

View File

@ -1,5 +1,6 @@
import {createMarkup, TwingMarkup} from "../../../markup";
import type {TwingCallable} from "../../../callable-wrapper";
import {escapeValue} from "../../../helpers/escape-value";
export const escape: TwingCallable<[
value: string | TwingMarkup | null,
@ -13,12 +14,12 @@ export const escape: TwingCallable<[
strategy = "html";
}
const {template, charset} = executionContext;
const {template, environment} = executionContext;
return template.escape(value, strategy, charset)
return escapeValue(template, environment, value, strategy, environment.charset)
.then((value) => {
if (typeof value === "string") {
return createMarkup(value, charset);
return createMarkup(value, environment.charset);
}
return value;

View File

@ -23,7 +23,8 @@ export const numberFormat: TwingCallable = (
decimalPoint: string | null,
thousandSeparator: string | null
): Promise<string> => {
const {numberFormat} = executionContext;
const {environment} = executionContext;
const {numberFormat} = environment;
if (numberOfDecimals === null) {
numberOfDecimals = numberFormat.numberOfDecimals;

View File

@ -104,5 +104,5 @@ export const date: TwingCallable = (
return Promise.resolve(date);
}
return createDate(executionContext.timezone, date, timezone);
return createDate(executionContext.environment.timezone, date, timezone);
}

View File

@ -34,7 +34,7 @@ export const include: TwingCallable<[
ignoreMissing,
sandboxed
): Promise<TwingMarkup> => {
const {template, charset, context, nodeExecutor, outputBuffer, sourceMapRuntime} = executionContext;
const {template, environment, context, nodeExecutor, outputBuffer, sourceMapRuntime} = executionContext;
const from = template.name;
if (!isPlainObject(variables) && !isTraversable(variables)) {
@ -54,7 +54,7 @@ export const include: TwingCallable<[
}
const resolveTemplate = (templates: Array<string | TwingTemplate | null>): Promise<TwingTemplate | null> => {
return template.resolveTemplate(templates)
return template.resolveTemplate(environment, templates)
.catch((error) => {
if (!ignoreMissing) {
throw error;
@ -71,6 +71,7 @@ export const include: TwingCallable<[
if (template) {
return template.render(
environment,
createContext(variables),
{
nodeExecutor,
@ -87,6 +88,6 @@ export const include: TwingCallable<[
.then(() => {
const result = outputBuffer.getAndClean();
return createMarkup(result, charset);
return createMarkup(result, environment.charset);
});
}

View File

@ -23,8 +23,9 @@ const array_rand = require('locutus/php/array/array_rand');
* @returns {Promise<any>} A random value from the given sequence
*/
export const random: TwingCallable = (executionContext, values: any | null, max: number | null): any => {
const {charset} = executionContext;
const {environment} = executionContext;
const {charset} = environment;
let _do = (): any => {
if (values === null) {
return max === null ? mt_rand() : mt_rand(0, max);

View File

@ -14,14 +14,17 @@ export const source: TwingCallable<[
name: string,
ignoreMissing: boolean
], string | null> = (executionContext, name, ignoreMissing) => {
const {template} = executionContext;
const {template, environment} = executionContext;
return template.getTemplateSource(name)
.then((source) => {
if (!ignoreMissing && (source === null)) {
return environment.loadTemplate(name, template.name)
.catch(() => {
return null;
})
.then((template) => {
if (!ignoreMissing && (template === null)) {
throw createTemplateLoadingError([name]);
}
return source?.code || null;
return template?.source.code || null;
});
};

View File

@ -1,5 +1,7 @@
import {TwingTemplate} from "../../../template";
import {createTemplate, TwingTemplate} from "../../../template";
import {TwingCallable} from "../../../callable-wrapper";
import * as createHash from "create-hash";
import {createSource} from "../../../source";
/**
* Loads a template from a string.
@ -8,14 +10,26 @@ import {TwingCallable} from "../../../callable-wrapper";
* {{ include(template_from_string("Hello {{ name }}")) }}
* </pre>
*
* @param executionContext A TwingTemplate instance
* @param string A template as a string or object implementing toString()
* @param executionContext
* @param code
* @param name An optional name for the template to be used in error messages
*
* @returns {Promise<TwingTemplate>}
*/
export const templateFromString: TwingCallable = (executionContext, string: string, name: string | null): Promise<TwingTemplate> => {
const {template} = executionContext;
return template.createTemplateFromString(string, name);
export const templateFromString: TwingCallable = (executionContext, code: string, name: string | null): Promise<TwingTemplate> => {
const {environment} = executionContext;
const hash: string = createHash("sha256").update(code).digest("hex").toString();
if (name !== null) {
name = `${name} (string template ${hash})`;
}
else {
name = `__string_template__${hash}`;
}
const ast = environment.parse(environment.tokenize(createSource(name, code)));
const template = createTemplate(ast);
return Promise.resolve(template);
}

View File

@ -0,0 +1,38 @@
import {isAMarkup, TwingMarkup} from "../markup";
import {createRuntimeError} from "../error/runtime";
import {TwingEnvironment} from "../environment";
import {TwingEscapingStrategy} from "../escaping-strategy";
import {TwingTemplate} from "../template";
export const escapeValue = (
template: TwingTemplate,
environment: TwingEnvironment,
value: string | boolean | TwingMarkup | null | undefined,
strategy: TwingEscapingStrategy | string,
charset: string | null
): Promise<string | boolean | TwingMarkup> => {
if (typeof value === "boolean") {
return Promise.resolve(value);
}
if (isAMarkup(value)) {
return Promise.resolve(value);
}
let result: string;
if ((value === null) || (value === undefined)) {
result = '';
}
else {
const strategyHandler = environment.escapingStrategyHandlers[strategy];
if (strategyHandler === undefined) {
return Promise.reject(createRuntimeError(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`));
}
result = strategyHandler(value.toString(), charset || environment.charset, template.name);
}
return Promise.resolve(result);
}

View File

@ -5,37 +5,39 @@ import {isPlainObject} from "./is-plain-object";
import {get} from "./get";
import type {TwingAttributeAccessorCallType} from "../node/expression/attribute-accessor";
import {isBoolean, isFloat} from "./php";
import {TwingTemplate} from "../template";
import type {TwingEnvironment} from "../environment";
const isObject = require('isobject');
/**
* Returns the attribute value for a given array/object.
*
* @param {TwingTemplate} template
* @param environment
* @param {*} object The object or array from where to get the item
* @param {*} attribute The item to get from the array or object
* @param {Map<any, any>} methodArguments A map of arguments to pass if the item is an object method
* @param {string} type The type of attribute (@see Twig_Template constants)
* @param {boolean} shouldTestExistence Whether this is only a defined check
* @param {boolean} shouldIgnoreStrictCheck Whether to ignore the strict attribute check or not
* @param sandboxed
*
* @return {Promise<any>} The attribute value, or a boolean when isDefinedTest is true, or null when the attribute is not set and ignoreStrictCheck is true
*
* @throw {TwingErrorRuntime} if the attribute does not exist and Twing is running in strict mode and isDefinedTest is false
*/
export const getAttribute = (
template: TwingTemplate,
environment: TwingEnvironment,
object: any,
attribute: any,
methodArguments: Map<any, any>,
type: TwingAttributeAccessorCallType,
shouldTestExistence: boolean,
shouldIgnoreStrictCheck: boolean | null,
sandboxed: boolean,
isStrictVariables: boolean
sandboxed: boolean
): Promise<any> => {
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !isStrictVariables : shouldIgnoreStrictCheck;
const {sandboxPolicy} = environment;
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !environment.isStrictVariables : shouldIgnoreStrictCheck;
const _do = (): any => {
let message: string;
@ -65,7 +67,7 @@ export const getAttribute = (
}
if (type !== "array" && sandboxed) {
template.checkPropertyAllowed(object, attribute);
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return get(object, arrayItem);
@ -155,7 +157,7 @@ export const getAttribute = (
}
if (sandboxed) {
template.checkPropertyAllowed(object, attribute);
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return get(object, attribute);
@ -250,7 +252,7 @@ export const getAttribute = (
}
if (sandboxed) {
template.checkMethodAllowed(object, method);
sandboxPolicy.checkMethodAllowed(object, method);
}
return get(object, method).apply(object, [...methodArguments.values()]);

View File

@ -6,6 +6,7 @@ export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNod
const {
template,
context,
environment,
outputBuffer,
blocks,
nodeExecutor: execute,
@ -17,6 +18,7 @@ export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNod
const renderBlock = getTraceableMethod(template.renderBlock, node.line, node.column, template.name);
return renderBlock(
environment,
name,
context.clone(),
outputBuffer,

View File

@ -11,11 +11,11 @@ import {
} from "../sandbox/security-not-allowed-tag-error";
export const executeCheckSecurityNode: TwingNodeExecutor<TwingCheckSecurityNode> = (node, executionContext) => {
const {template, sandboxed} = executionContext;
const {template, environment, sandboxed} = executionContext;
const {usedTags, usedFunctions, usedFilters} = node.attributes;
try {
sandboxed && template.checkSecurity(
sandboxed && environment.sandboxPolicy.checkSecurity(
[...usedTags.keys()],
[...usedFilters.keys()],
[...usedFunctions.keys()]

View File

@ -3,16 +3,17 @@ 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 {template, environment, nodeExecutor: execute, sandboxed} = executionContext;
const {value: valueNode} = node.children;
const {sandboxPolicy} = environment;
return execute(valueNode, executionContext)
.then((value) => {
if (sandboxed) {
const assertToStringAllowed = getTraceableMethod((value: any) => {
if ((value !== null) && (typeof value === 'object')) {
try {
template.checkMethodAllowed(value, 'toString');
sandboxPolicy.checkMethodAllowed(value, 'toString');
} catch (error) {
return Promise.reject(error);
}

View File

@ -4,7 +4,7 @@ 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 {template, sandboxed, environment, nodeExecutor: execute} = executionContext;
const {target, attribute, arguments: methodArguments} = node.children;
const {type, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
@ -16,15 +16,14 @@ export const executeAttributeAccessorNode: TwingNodeExecutor<TwingAttributeAcces
const traceableGetAttribute = getTraceableMethod(getAttribute, node.line, node.column, template.name);
return traceableGetAttribute(
template,
environment,
target,
attribute,
methodArguments,
type,
shouldTestExistence,
shouldIgnoreStrictCheck || null,
sandboxed,
isStrictVariables
sandboxed
)
})
};

View File

@ -4,7 +4,7 @@ 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, context, environment, nodeExecutor: execute, outputBuffer, blocks, sandboxed, sourceMapRuntime} = executionContext;
const {template: templateNode, name: blockNameNode} = node.children;
const blockName = await execute(blockNameNode, executionContext);
@ -21,7 +21,7 @@ export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = a
template.name
);
resolveTemplate = loadTemplate(templateName);
resolveTemplate = loadTemplate(environment, templateName);
} else {
resolveTemplate = Promise.resolve(template)
}
@ -31,14 +31,14 @@ export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = a
if (node.attributes.shouldTestExistence) {
const hasBlock = getTraceableMethod(executionContextOfTheBlock.hasBlock, node.line, node.column, template.name);
return hasBlock(blockName, context.clone(), outputBuffer, blocks, sandboxed, execute);
return hasBlock(environment, 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);
return renderBlock(environment, blockName, context.clone(), outputBuffer, new Map(), false, sandboxed, execute, sourceMapRuntime);
} else {
return renderBlock(blockName, context.clone(), outputBuffer, blocks, true, sandboxed, execute, sourceMapRuntime);
return renderBlock(environment, blockName, context.clone(), outputBuffer, blocks, true, sandboxed, execute, sourceMapRuntime);
}
}
});

View File

@ -8,6 +8,9 @@ import {TwingBaseNode} from "../../node";
import {createConstantNode, TwingConstantNode} from "../../node/expression/constant";
import {TwingBaseExpressionNode} from "../../node/expression";
import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
import {getTest} from "../../helpers/get-test";
import {getFunction} from "../../helpers/get-function";
import {getFilter} from "../../helpers/get-filter";
const array_merge = require('locutus/php/array/array_merge');
const snakeCase = require('snake-case');
@ -124,24 +127,24 @@ const getArguments = (
export const executeCallNode: TwingNodeExecutor<TwingBaseCallNode<any>> = async (node, executionContext) => {
const {type} = node;
const {template, nodeExecutor: execute} = executionContext
const {template, environment, nodeExecutor: execute} = executionContext
const {operatorName} = node.attributes;
let callableWrapper: TwingCallableWrapper | null;
switch (type) {
case "filter":
callableWrapper = template.getFilter(operatorName);
callableWrapper = getFilter(environment.filters, operatorName);
break;
case "function":
callableWrapper = template.getFunction(operatorName);
callableWrapper = 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 = template.getTest(operatorName);
callableWrapper = getTest(environment.tests, operatorName);
break;
}

View File

@ -1,16 +1,17 @@
import {TwingNodeExecutor} from "../../node-executor";
import {TwingEscapeNode} from "../../node/expression/escape";
import {getTraceableMethod} from "../../helpers/traceable-method";
import {escapeValue} from "../../helpers/escape-value";
export const executeEscapeNode: TwingNodeExecutor<TwingEscapeNode> = (node, executionContext) => {
const {template, nodeExecutor: execute} = executionContext;
const {template, environment, 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);
const traceableEscape = getTraceableMethod(escapeValue, node.line, node.column, template.name);
return escape(value, strategy, null, true);
return traceableEscape(template, environment, value, strategy, null);
});
};

View File

@ -5,7 +5,7 @@ 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 {template, context, environment, outputBuffer, aliases, nodeExecutor: execute, sandboxed, sourceMapRuntime} = executionContext;
const {methodName, shouldTestExistence} = node.attributes;
const {operand, arguments: methodArguments} = node.children;
@ -31,7 +31,7 @@ export const executeMethodCall: TwingNodeExecutor<TwingMethodCallNode> = async (
if (macroHandler) {
return Promise.resolve(macroHandler);
} else {
return template.getParent(context, outputBuffer, sandboxed, execute)
return template.getParent(environment, context, outputBuffer, sandboxed, execute)
.then((parent) => {
if (parent) {
return getHandler(parent);
@ -45,7 +45,7 @@ export const executeMethodCall: TwingNodeExecutor<TwingMethodCallNode> = async (
return getHandler(macroTemplate)
.then((handler) => {
if (handler) {
return handler(outputBuffer, sandboxed, sourceMapRuntime, execute, ...macroArguments);
return handler(environment, outputBuffer, sandboxed, sourceMapRuntime, execute, ...macroArguments);
} else {
throw createRuntimeError(`Macro "${methodName}" is not defined in template "${macroTemplate.name}".`, node, template.name);
}

View File

@ -6,8 +6,7 @@ import {getContextValue} from "../../helpers/get-context-value";
export const executeNameNode: TwingNodeExecutor<TwingNameNode> = (node, {
template,
context,
charset,
isStrictVariables
environment
}) => {
const {name, isAlwaysDefined, shouldIgnoreStrictCheck, shouldTestExistence} = node.attributes;
@ -19,9 +18,9 @@ export const executeNameNode: TwingNodeExecutor<TwingNameNode> = (node, {
);
return traceableGetContextValue(
charset,
environment.charset,
template.name,
isStrictVariables,
environment.isStrictVariables,
context,
name,
isAlwaysDefined,

View File

@ -3,9 +3,9 @@ import type {TwingParentFunctionNode} from "../../node/expression/parent-functio
import {getTraceableMethod} from "../../helpers/traceable-method";
export const executeParentFunction: TwingNodeExecutor<TwingParentFunctionNode> = (node, executionContext) => {
const {template, context, nodeExecutor: execute, outputBuffer, sandboxed, sourceMapRuntime,} = executionContext;
const {template, context, environment, 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);
return renderParentBlock(environment, name, context, outputBuffer, sandboxed, execute, sourceMapRuntime);
};

View File

@ -5,7 +5,7 @@ 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 {template, environment, aliases, nodeExecutor: execute,} = executionContext;
const {alias: aliasNode, templateName: templateNameNode} = node.children;
const {global} = node.attributes;
@ -19,7 +19,7 @@ export const executeImportNode: TwingNodeExecutor<TwingImportNode> = async (node
const loadTemplate = getTraceableMethod(template.loadTemplate, node.line, node.column, template.name);
aliasValue = await loadTemplate(templateName);
aliasValue = await loadTemplate(environment, templateName);
}
aliases.set(aliasNode.attributes.name, aliasValue);

View File

@ -7,19 +7,11 @@ import {mergeIterables} from "./helpers/merge-iterables";
import {createRuntimeError} from "./error/runtime";
import {createNode, getChildren, getChildrenCount, TwingBaseNode} from "./node";
import {TwingError} from "./error";
import {createMarkup, isAMarkup, TwingMarkup} from "./markup";
import {createMarkup, TwingMarkup} from "./markup";
import {createTemplateLoadingError, isATemplateLoadingError} from "./error/loader";
import {cloneMap} from "./helpers/clone-map";
import {iteratorToMap} from "./helpers/iterator-to-map";
import {createSource, TwingSource} from "./source";
import {TwingFilter} from "./filter";
import {TwingFunction} from "./function";
import {TwingTest} from "./test";
import {getFilter} from "./helpers/get-filter";
import {getTest} from "./helpers/get-test";
import {getFunction} from "./helpers/get-function";
import * as createHash from "create-hash";
import {TwingEscapingStrategy} from "./escaping-strategy";
import {TwingSource} from "./source";
import {getTraceableMethod} from "./helpers/traceable-method";
import {TwingConstantNode} from "./node/expression/constant";
import {executeNode, type TwingNodeExecutor} from "./node-executor";
@ -27,6 +19,7 @@ import {getKeyValuePairs} from "./helpers/get-key-value-pairs";
export type TwingTemplateBlockMap = Map<string, [TwingTemplate, string]>;
export type TwingTemplateBlockHandler = (
environment: TwingEnvironment,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
blocks: TwingTemplateBlockMap,
@ -35,6 +28,7 @@ export type TwingTemplateBlockHandler = (
sourceMapRuntime?: TwingSourceMapRuntime
) => Promise<void>;
export type TwingTemplateMacroHandler = (
environment: TwingEnvironment,
outputBuffer: TwingOutputBuffer,
sandboxed: boolean,
sourceMapRuntime: TwingSourceMapRuntime | undefined,
@ -52,28 +46,9 @@ export interface TwingTemplate {
readonly source: TwingSource;
readonly macroHandlers: Map<string, TwingTemplateMacroHandler>;
readonly name: string;
/**
* @param candidate
* @param method
*
* @throws {@link TwingSandboxSecurityNotAllowedMethodError} When the method of the passed candidate is not allowed to be executed
*/
checkMethodAllowed(candidate: any | TwingMarkup, method: string): void;
/**
* @param candidate
* @param property
*
* @throws {@link TwingSandboxSecurityNotAllowedPropertyError} When the property of the passed candidate is not allowed to be accessed
*/
checkPropertyAllowed(candidate: any | TwingMarkup, property: string): void;
checkSecurity(tags: Array<string>, filters: Array<string>, functions: Array<string>): void;
createTemplateFromString(content: string, name: string | null): Promise<TwingTemplate>;
displayBlock(
environment: TwingEnvironment,
name: string,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
@ -84,9 +59,8 @@ export interface TwingTemplate {
sourceMapRuntime?: TwingSourceMapRuntime
): Promise<void>;
escape(value: string | boolean | TwingMarkup | null | undefined, strategy: TwingEscapingStrategy | string, charset: string | null, autoEscape?: boolean): Promise<string | boolean | TwingMarkup>;
execute(
environment: TwingEnvironment,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
childBlocks: TwingTemplateBlockMap,
@ -97,13 +71,10 @@ export interface TwingTemplate {
}
): Promise<void>;
getBlocks(): Promise<TwingTemplateBlockMap>;
getFilter(name: string): TwingFilter | null;
getFunction(name: string): TwingFunction | null;
getBlocks(environment: TwingEnvironment): Promise<TwingTemplateBlockMap>;
getParent(
environment: TwingEnvironment,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
sandboxed: boolean,
@ -111,13 +82,10 @@ export interface TwingTemplate {
sourceMapRuntime?: TwingSourceMapRuntime
): Promise<TwingTemplate | null>;
getTemplateSource(name: string): Promise<TwingSource | null>;
getTest(name: string): TwingTest | null;
getTraits(): Promise<TwingTemplateBlockMap>;
getTraits(environment: TwingEnvironment): Promise<TwingTemplateBlockMap>;
hasBlock(
environment: TwingEnvironment,
name: string,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
@ -139,15 +107,15 @@ export interface TwingTemplate {
): Promise<TwingTemplate>;
/**
* @param identifier
*
* @throws {TwingTemplateLoadingError} When no embedded template exists for the passed identifier.
*/
loadTemplate(
identifier: TwingTemplate | string | Array<TwingTemplate | null>
environment: TwingEnvironment,
identifier: TwingTemplate | string | Array<TwingTemplate | null>,
): Promise<TwingTemplate>;
render(
environment: TwingEnvironment,
context: Record<string, any>,
options?: {
nodeExecutor?: TwingNodeExecutor;
@ -158,6 +126,7 @@ export interface TwingTemplate {
): Promise<string>;
renderBlock(
environment: TwingEnvironment,
name: string,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
@ -169,6 +138,7 @@ export interface TwingTemplate {
): Promise<string>;
renderParentBlock(
environment: TwingEnvironment,
name: string,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
@ -182,17 +152,15 @@ export interface TwingTemplate {
*
* Similar to loadTemplate() but it also accepts instances of TwingTemplate and an array of templates where each is tried to be loaded.
*
* @param environment
* @param names A template or an array of templates to try consecutively
*/
resolveTemplate(names: Array<string | TwingTemplate | null>): Promise<TwingTemplate>;
resolveTemplate(environment: TwingEnvironment, names: Array<string | TwingTemplate | null>): Promise<TwingTemplate>;
}
export const createTemplate = (
environment: TwingEnvironment,
ast: TwingTemplateNode
): TwingTemplate => {
const {charset, dateFormat, dateIntervalFormat, isStrictVariables, numberFormat, timezone} = environment
// blocks
const blockHandlers: Map<string, TwingTemplateBlockHandler> = new Map();
@ -201,24 +169,19 @@ export const createTemplate = (
const {blocks: blockNodes} = ast.children;
for (const [name, blockNode] of getChildren(blockNodes)) {
const blockHandler: TwingTemplateBlockHandler = (context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
const blockHandler: TwingTemplateBlockHandler = (environment, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
const aliases = template.aliases.clone();
return nodeExecutor(blockNode.children.body, {
aliases,
blocks,
charset,
context,
dateFormat,
dateIntervalFormat,
isStrictVariables,
environment,
nodeExecutor,
numberFormat,
outputBuffer,
sandboxed,
sourceMapRuntime,
template,
timezone
template
});
};
@ -231,7 +194,7 @@ export const createTemplate = (
const {macros: macrosNode} = ast.children;
for (const [name, macroNode] of Object.entries(macrosNode.children)) {
const macroHandler: TwingTemplateMacroHandler = async (outputBuffer, sandboxed, sourceMapRuntime, nodeExecutor, ...args) => {
const macroHandler: TwingTemplateMacroHandler = async (environment, outputBuffer, sandboxed, sourceMapRuntime, nodeExecutor, ...args) => {
const {body, arguments: macroArguments} = macroNode.children;
const keyValuePairs = getKeyValuePairs(macroArguments);
@ -244,18 +207,13 @@ export const createTemplate = (
const defaultValue = await nodeExecutor(defaultValueNode, {
aliases,
blocks: new Map(),
charset,
context: createContext(),
dateFormat,
dateIntervalFormat,
isStrictVariables,
environment,
nodeExecutor,
numberFormat,
outputBuffer,
sandboxed,
sourceMapRuntime,
template,
timezone
template
});
let value = args.shift();
@ -277,18 +235,13 @@ export const createTemplate = (
return await nodeExecutor(body, {
aliases,
blocks,
charset,
context,
dateFormat,
dateIntervalFormat,
isStrictVariables,
environment,
nodeExecutor,
numberFormat,
outputBuffer,
sandboxed,
sourceMapRuntime,
template,
timezone
template
})
.then(() => {
const content = outputBuffer.getContents();
@ -316,15 +269,25 @@ export const createTemplate = (
// parent
let parent: TwingTemplate | null = null;
const displayParentBlock = (name: string, context: TwingContext<any, any>, outputBuffer: TwingOutputBuffer, blocks: TwingTemplateBlockMap, sandboxed: boolean, nodeExecutor: TwingNodeExecutor, sourceMapRuntime?: TwingSourceMapRuntime): Promise<void> => {
return template.getTraits()
const displayParentBlock = (
environment: TwingEnvironment,
name: string,
context: TwingContext<any, any>,
outputBuffer: TwingOutputBuffer,
blocks: TwingTemplateBlockMap,
sandboxed: boolean,
nodeExecutor: TwingNodeExecutor,
sourceMapRuntime?: TwingSourceMapRuntime
): Promise<void> => {
return template.getTraits(environment)
.then((traits) => {
const trait = traits.get(name);
if (trait) {
const [blockTemplate, blockName] = trait;
return blockTemplate.displayBlock(
environment,
blockName,
context,
outputBuffer,
@ -336,10 +299,10 @@ export const createTemplate = (
);
}
else {
return template.getParent(context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime)
return template.getParent(environment, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime)
.then((parent) => {
if (parent !== null) {
return parent.displayBlock(name, context, outputBuffer, blocks, false, sandboxed, nodeExecutor, sourceMapRuntime);
return parent.displayBlock(environment, name, context, outputBuffer, blocks, false, sandboxed, nodeExecutor, sourceMapRuntime);
}
else {
throw createRuntimeError(`The template has no parent and no traits defining the "${name}" block.`, undefined, template.name);
@ -385,7 +348,7 @@ export const createTemplate = (
return aliases;
},
get ast() {
return ast;
return ast;
},
get blockHandlers() {
return blockHandlers;
@ -402,26 +365,8 @@ export const createTemplate = (
get source() {
return ast.attributes.source;
},
checkMethodAllowed: environment.sandboxPolicy.checkMethodAllowed,
checkPropertyAllowed: environment.sandboxPolicy.checkPropertyAllowed,
checkSecurity: environment.sandboxPolicy.checkSecurity,
createTemplateFromString: (code, name) => {
const hash: string = createHash("sha256").update(code).digest("hex").toString();
if (name !== null) {
name = `${name} (string template ${hash})`;
}
else {
name = `__string_template__${hash}`;
}
const ast = environment.parse(environment.tokenize(createSource(name, code)));
const template = createTemplate(environment, ast);
return Promise.resolve(template);
},
displayBlock: (name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
return template.getBlocks()
displayBlock: (environment, name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
return template.getBlocks(environment)
.then((ownBlocks) => {
let blockHandler: TwingTemplateBlockHandler | undefined;
let block: [TwingTemplate, string] | undefined;
@ -438,12 +383,12 @@ export const createTemplate = (
}
if (blockHandler) {
return blockHandler(context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime);
return blockHandler(environment, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime);
}
else {
return template.getParent(context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime).then((parent) => {
return template.getParent(environment, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime).then((parent) => {
if (parent) {
return parent.displayBlock(name, context, outputBuffer, mergeIterables(ownBlocks, blocks), false, sandboxed, nodeExecutor, sourceMapRuntime);
return parent.displayBlock(environment, name, context, outputBuffer, mergeIterables(ownBlocks, blocks), false, sandboxed, nodeExecutor, sourceMapRuntime);
}
else {
const block = blocks.get(name);
@ -462,62 +407,30 @@ export const createTemplate = (
}
});
},
escape: (value, strategy, charset) => {
if (typeof value === "boolean") {
return Promise.resolve(value);
}
if (isAMarkup(value)) {
return Promise.resolve(value);
}
let result: string;
if ((value === null) || (value === undefined)) {
result = '';
}
else {
const strategyHandler = environment.escapingStrategyHandlers[strategy];
if (strategyHandler === undefined) {
return Promise.reject(createRuntimeError(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`));
}
result = strategyHandler(value.toString(), charset || environment.charset, template.name);
}
return Promise.resolve(result);
},
execute: async (context, outputBuffer, childBlocks, nodeExecutor, options) => {
execute: async (environment, context, outputBuffer, childBlocks, nodeExecutor, options) => {
const aliases = template.aliases.clone();
const sandboxed = options?.sandboxed || false;
const sourceMapRuntime = options?.sourceMapRuntime;
return Promise.all([
template.getParent(context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime),
template.getBlocks()
template.getParent(environment, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime),
template.getBlocks(environment)
]).then(([parent, ownBlocks]) => {
const blocks = mergeIterables(ownBlocks, childBlocks);
return nodeExecutor(ast, {
aliases,
blocks,
charset,
context,
dateFormat,
dateIntervalFormat,
isStrictVariables,
environment,
nodeExecutor,
numberFormat,
outputBuffer,
sandboxed,
sourceMapRuntime,
template,
timezone
template
}).then(() => {
if (parent) {
return parent.execute(context, outputBuffer, blocks, nodeExecutor, {
return parent.execute(environment, context, outputBuffer, blocks, nodeExecutor, {
sandboxed,
sourceMapRuntime
});
@ -535,12 +448,12 @@ export const createTemplate = (
throw error;
});
},
getBlocks: () => {
getBlocks: (environment) => {
if (blocks) {
return Promise.resolve(blocks);
}
else {
return template.getTraits()
return template.getTraits(environment)
.then((traits) => {
blocks = mergeIterables(traits, new Map([...blockHandlers.keys()].map((key) => {
return [key, [template, key]];
@ -550,13 +463,7 @@ export const createTemplate = (
});
}
},
getFilter: (name) => {
return getFilter(environment.filters, name);
},
getFunction: (name) => {
return getFunction(environment.functions, name);
},
getParent: async (context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime) => {
getParent: async (environment, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime) => {
if (parent !== null) {
return Promise.resolve(parent);
}
@ -564,23 +471,18 @@ export const createTemplate = (
const parentNode = ast.children.parent;
if (parentNode) {
return template.getBlocks()
return template.getBlocks(environment)
.then(async (blocks) => {
const parentName = await nodeExecutor(parentNode, {
aliases: createContext(),
blocks,
charset,
context,
dateFormat,
dateIntervalFormat,
isStrictVariables,
environment,
nodeExecutor,
numberFormat,
outputBuffer,
sandboxed,
sourceMapRuntime,
template,
timezone
template
});
const loadTemplate = getTraceableMethod(
@ -590,7 +492,7 @@ export const createTemplate = (
template.name
);
const loadedParent = await loadTemplate(parentName);
const loadedParent = await loadTemplate(environment, parentName);
if (parentNode.type === "constant") {
parent = loadedParent;
@ -603,18 +505,12 @@ export const createTemplate = (
return Promise.resolve(null);
}
},
getTemplateSource: (name) => {
return environment.loader.getSource(name, template.name);
},
getTest: (name) => {
return getTest(environment.tests, name);
},
getTraits: async () => {
getTraits: async (environment) => {
if (traits === null) {
traits = new Map();
const {traits: traitsNode} = ast.children;
for (const [, traitNode] of getChildren(traitsNode)) {
const {template: templateNameNode, targets} = traitNode.children;
const templateName = templateNameNode.attributes.value as string;
@ -626,13 +522,13 @@ export const createTemplate = (
template.name
);
const traitTemplate = await loadTemplate(templateName);
const traitTemplate = await loadTemplate(environment, templateName);
if (!traitTemplate.canBeUsedAsATrait) {
throw createRuntimeError(`Template ${templateName} cannot be used as a trait.`, templateNameNode, template.name);
}
const traitBlocks = cloneMap(await traitTemplate.getBlocks());
const traitBlocks = cloneMap(await traitTemplate.getBlocks(environment));
for (const [key, target] of getChildren(targets)) {
const traitBlock = traitBlocks.get(key);
@ -653,21 +549,21 @@ export const createTemplate = (
return Promise.resolve(traits);
},
hasBlock: (name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime): Promise<boolean> => {
hasBlock: (environment, name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime): Promise<boolean> => {
if (blocks.has(name)) {
return Promise.resolve(true);
}
else {
return template.getBlocks()
return template.getBlocks(environment)
.then((blocks) => {
if (blocks.has(name)) {
return Promise.resolve(true);
}
else {
return template.getParent(context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime)
return template.getParent(environment, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime)
.then((parent) => {
if (parent) {
return parent.hasBlock(name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime);
return parent.hasBlock(environment, name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime);
}
else {
return false;
@ -688,16 +584,16 @@ export const createTemplate = (
return Promise.reject(createTemplateLoadingError([`embedded#${index}`]));
}
return Promise.resolve(createTemplate(environment, ast));
return Promise.resolve(createTemplate(ast));
},
loadTemplate: (identifier) => {
loadTemplate: (environment, identifier) => {
let promise: Promise<TwingTemplate>;
if (typeof identifier === "string") {
promise = environment.loadTemplate(identifier, template.name);
}
else if (Array.isArray(identifier)) {
promise = template.resolveTemplate(identifier);
promise = template.resolveTemplate(environment, identifier);
}
else {
promise = Promise.resolve(identifier);
@ -705,7 +601,7 @@ export const createTemplate = (
return promise;
},
render: (context, options) => {
render: (environment, context, options) => {
const actualOutputBuffer: TwingOutputBuffer = options?.outputBuffer || createOutputBuffer();
actualOutputBuffer.start();
@ -713,6 +609,7 @@ export const createTemplate = (
const nodeExecutor = options?.nodeExecutor || executeNode;
return template.execute(
environment,
createContext(iteratorToMap(context)),
actualOutputBuffer,
new Map(),
@ -725,24 +622,24 @@ export const createTemplate = (
return actualOutputBuffer.getAndFlush();
});
},
renderBlock: (name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
renderBlock: (environment, name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime) => {
outputBuffer.start();
return template.displayBlock(name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime).then(() => {
return template.displayBlock(environment, name, context, outputBuffer, blocks, useBlocks, sandboxed, nodeExecutor, sourceMapRuntime).then(() => {
return outputBuffer.getAndClean();
});
},
renderParentBlock: (name, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime) => {
renderParentBlock: (environment, name, context, outputBuffer, sandboxed, nodeExecutor, sourceMapRuntime) => {
outputBuffer.start();
return template.getBlocks()
return template.getBlocks(environment)
.then((blocks) => {
return displayParentBlock(name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime).then(() => {
return displayParentBlock(environment, name, context, outputBuffer, blocks, sandboxed, nodeExecutor, sourceMapRuntime).then(() => {
return outputBuffer.getAndClean();
})
});
},
resolveTemplate: (names) => {
resolveTemplate: (environment, names) => {
const loadTemplateAtIndex = (index: number): Promise<TwingTemplate> => {
if (index < names.length) {
const name = names[index];
@ -754,7 +651,7 @@ export const createTemplate = (
return Promise.resolve(name);
}
else {
return template.loadTemplate(name)
return template.loadTemplate(environment, name)
.catch(() => {
return loadTemplateAtIndex(index + 1);
});

View File

@ -23,6 +23,7 @@ const createMockCache = (): TwingCache => {
// todo: unit test every property because this is the public API
import "./load-template";
import "./loader";
tape('createEnvironment ', ({test}) => {
test('options', ({test}) => {
@ -85,7 +86,9 @@ tape('createEnvironment ', ({test}) => {
return Promise.resolve();
});
return getEnvironment().loadTemplate('foo')
const environment = getEnvironment();
return environment.loadTemplate('foo')
.then(() => {
return getEnvironment().loadTemplate('foo');
})
@ -93,7 +96,7 @@ tape('createEnvironment ', ({test}) => {
return getEnvironment().loadTemplate('foo');
})
.then((template) => {
return template?.render({});
return template?.render(environment, {});
})
.then((content) => {
same(content, '1');
@ -149,7 +152,9 @@ tape('createEnvironment ', ({test}) => {
return Promise.resolve();
});
return getEnvironment().loadTemplate('foo')
const environment = getEnvironment();
return environment.loadTemplate('foo')
.then(() => {
return getEnvironment().loadTemplate('foo');
})
@ -157,7 +162,7 @@ tape('createEnvironment ', ({test}) => {
return getEnvironment().loadTemplate('foo');
})
.then((template) => {
return template?.render({});
return template?.render(environment, {});
})
.then((content) => {
same(content, '-1');
@ -183,7 +188,9 @@ tape('createEnvironment ', ({test}) => {
return Promise.resolve(false);
});
return getEnvironment().loadTemplate('foo')
const environment = getEnvironment();
return environment.loadTemplate('foo')
.then(() => {
return getEnvironment().loadTemplate('foo');
})
@ -191,7 +198,7 @@ tape('createEnvironment ', ({test}) => {
return getEnvironment().loadTemplate('foo');
})
.then((template) => {
return template?.render({});
return template?.render(environment, {});
})
.then(() => {
same(isFreshStub.callCount, 0);
@ -267,7 +274,7 @@ tape('createEnvironment ', ({test}) => {
return environment.loadTemplate('foo');
})
.then((template) => {
return template?.render({});
return template?.render(environment, {});
})
.then((content) => {
same(content, 'bar');

View File

@ -56,7 +56,7 @@ tape('createEnvironment::loadTemplate', ({test}) => {
return environment.loadTemplate(('index'))
.then((template) => {
return template.render({});
return template.render(environment, {});
})
.then(() => {
same(loadedTemplates, [

View File

@ -0,0 +1,12 @@
import * as tape from "tape";
import {createEnvironment} from "../../../../../src/lib/environment";
import {createArrayLoader} from "../../../../../src/lib/loader/array";
tape('createEnvironment::loader', ({same, end}) => {
const loader = createArrayLoader({});
const environment = createEnvironment(loader);
same(environment.loader, loader);
end();
});

View File

@ -14,9 +14,9 @@ tape('createTemplate => ::ast', ({test}) => {
.then(({ast}) => {
const serializedAST = JSON.stringify(ast);
const deserializedAST: TwingTemplateNode = JSON.parse(serializedAST);
const template = createTemplate(environment, deserializedAST);
const template = createTemplate(deserializedAST);
return template.render({})
return template.render(environment, {})
.then((output) => {
same(output, '10');
})
@ -39,9 +39,9 @@ tape('createTemplate => ::ast', ({test}) => {
.then(({ast}) => {
const serializedAST = JSON.stringify(ast);
const deserializedAST: TwingTemplateNode = JSON.parse(serializedAST);
const template = createTemplate(environment, deserializedAST);
const template = createTemplate(deserializedAST);
return template.render({})
return template.render(environment, {})
.then((output) => {
same(output, 'Foo');
})

View File

@ -32,9 +32,9 @@ tape('createTemplate => ::execute', ({test}) => {
const executeNodeSpy = spy(executeNode);
const template = createTemplate(environment, ast);
const template = createTemplate(ast);
return template.execute(createContext(), createOutputBuffer(), new Map(), executeNodeSpy)
return template.execute(environment, createContext(), createOutputBuffer(), new Map(), executeNodeSpy)
.then(() => {
same(executeNodeSpy.firstCall.args[1].sandboxed, false);
same(executeNodeSpy.firstCall.args[1].sourceMapRuntime, undefined);
@ -59,11 +59,11 @@ tape('createTemplate => ::execute', ({test}) => {
const executeNodeSpy = spy(executeNode);
const template = createTemplate(environment, ast);
const template = createTemplate(ast);
const sourceMapRuntime = createSourceMapRuntime();
return template.execute(createContext(), createOutputBuffer(), new Map(), executeNodeSpy, {
return template.execute(environment, createContext(), createOutputBuffer(), new Map(), executeNodeSpy, {
sandboxed: true,
sourceMapRuntime
}).then(() => {
@ -101,12 +101,12 @@ tape('createTemplate => ::execute', ({test}) => {
}
};
const template = createTemplate(environment, ast);
const template = createTemplate(ast);
const outputBuffer = createOutputBuffer();
outputBuffer.start();
return template.execute(createContext(), outputBuffer, new Map(), nodeExecutor).then(() => {
return template.execute(environment, createContext(), outputBuffer, new Map(), nodeExecutor).then(() => {
same(outputBuffer.getContents(), 'foo5');
}).finally(end);
});

View File

@ -1,6 +1,4 @@
import * as tape from "tape";
import {createEnvironment} from "../../../../../src/lib/environment";
import {createArrayLoader} from "../../../../../src/lib/loader/array";
import {createTemplate} from "../../../../../src/lib/template";
import {createTemplateNode} from "../../../../../src/lib/node/template";
import {createBaseNode} from "../../../../../src/lib/node";
@ -8,8 +6,7 @@ import {createSource} from "../../../../../src/lib/source";
tape('createTemplate => ::loadEmbeddedTemplate', ({test}) => {
test('throws an error on invalid index', ({fail, same, end}) => {
const environment = createEnvironment(createArrayLoader({}));
const template = createTemplate(environment, createTemplateNode(
const template = createTemplate(createTemplateNode(
createBaseNode(null, {}, {
content: createBaseNode(null)
}, 1, 1),

View File

@ -40,12 +40,12 @@ tape('createTemplate => ::render', ({test}) => {
}
};
const template = createTemplate(environment, ast);
const template = createTemplate(ast);
const outputBuffer = createOutputBuffer();
outputBuffer.start();
return template.render(createContext(), {
return template.render(environment, createContext(), {
outputBuffer,
nodeExecutor
}).then(() => {