diff --git a/package.json b/package.json index 4d77704df..1e67ce9b4 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "eslint": "^6.5.1", "eslint-plugin-import": "^2.18.2", "fs-finder": "^1.8.1", - "nyc": "^13.3.0", + "nyc": "^15.0.1", "replace-in-file": "^3.4.2", "rimraf": "^2.6.2", "sinon": "^6.3.4", diff --git a/src/lib/callable-wrapper.ts b/src/lib/callable-wrapper.ts index 90e4166c1..c8faf935e 100644 --- a/src/lib/callable-wrapper.ts +++ b/src/lib/callable-wrapper.ts @@ -16,7 +16,7 @@ export type TwingCallableWrapperOptions = { is_variadic?: boolean; is_safe?: Array; is_safe_callback?: Function; - deprecated?: string; + deprecated?: boolean | string; alternative?: string; expression_factory?: Function; } @@ -76,12 +76,10 @@ export abstract class TwingCallableWrapper { let callable = this.callable; return function () { - return (callable.apply(null, arguments) as Promise).catch((e) => { - if (e instanceof TwingError) { - if (e.getTemplateLine() === -1) { - e.setTemplateLine(lineno); - e.setSourceContext(source); - } + return (callable.apply(null, arguments) as Promise).catch((e: TwingError) => { + if (e.getTemplateLine() === -1) { + e.setTemplateLine(lineno); + e.setSourceContext(source); } throw e; diff --git a/src/lib/extension/core.ts b/src/lib/extension/core.ts index 225dbf9ee..23487509e 100644 --- a/src/lib/extension/core.ts +++ b/src/lib/extension/core.ts @@ -110,7 +110,6 @@ import {TwingTokenParserSandbox} from "../token-parser/sandbox"; import {TwingBaseNodeVisitor} from "../base-node-visitor"; import {TwingNodeVisitorEscaper} from "../node-visitor/escaper"; import {TwingNodeVisitorSandbox} from "../node-visitor/sandbox"; -import {TwingNodeVisitorOptimizer} from "../node-visitor/optimizer"; import {range} from "./core/functions/range"; import {constant} from "./core/functions/constant"; import {cycle} from "./core/functions/cycle"; @@ -318,7 +317,6 @@ export class TwingExtensionCore extends TwingExtension { return [ new TwingNodeVisitorEscaper(), new TwingNodeVisitorMacroAutoImport(), - new TwingNodeVisitorOptimizer(), new TwingNodeVisitorSandbox() ]; } diff --git a/src/lib/extension/core/filters/escape.ts b/src/lib/extension/core/filters/escape.ts index 73829b2fb..9ff1727b9 100644 --- a/src/lib/extension/core/filters/escape.ts +++ b/src/lib/extension/core/filters/escape.ts @@ -44,23 +44,12 @@ export function escape(env: TwingEnvironment, string: any, strategy: string = 'h charset = env.getCharset(); } - let regExp: RegExp = /^[.\s\S]/u; - switch (strategy) { case 'html': return htmlspecialchars(string); case 'js': // escape all non-alphanumeric characters // into their \x or \uHHHH representations - // if (charset !== 'UTF-8') { - // string = iconv(charset, 'UTF-8', string).toString(); - // } - - // unit: no test case for this - // if ((strlen(string) === 0) ? false : !regExp.test(string)) { - // throw new TwingErrorRuntime('The string to escape is not a valid UTF-8 string.'); - // } - string = string.replace(/[^a-zA-Z0-9,._]/ug, function (matches: string) { let char = matches; @@ -93,21 +82,8 @@ export function escape(env: TwingEnvironment, string: any, strategy: string = 'h return sprintf('\\u%04s\\u%04s', char.substr(0, 4), char.substr(4, 4)); }); - // if (charset !== 'UTF-8') { - // string = iconv('UTF-8', charset, string).toString(); - // } - return string; case 'css': - // if (charset !== 'UTF-8') { - // string = iconv(charset, 'UTF-8', string).toString(); - // } - - // unit: no test case for this - // if ((strlen(string) === 0) ? false : !regExp.test(string)) { - // throw new TwingErrorRuntime('The string to escape is not a valid UTF-8 string.'); - // } - string = string.replace(/[^a-zA-Z0-9]/ug, function (matches: string) { let char = matches; @@ -126,21 +102,8 @@ export function escape(env: TwingEnvironment, string: any, strategy: string = 'h return '\\' + ltrim(bin2hex(char).toUpperCase(), '0') + ' '; }); - // if (charset !== 'UTF-8') { - // string = iconv('UTF-8', charset, string).toString(); - // } - return string; case 'html_attr': - // if (charset !== 'UTF-8') { - // string = iconv(charset, 'UTF-8', string).toString(); - // } - - // unit: no test case for this - // if ((strlen(string) === 0) ? false : !regExp.test(string)) { - // throw new TwingErrorRuntime('The string to escape is not a valid UTF-8 string.'); - // } - string = string.replace(/[^a-zA-Z0-9,.\-_]/ug, function (matches: string) { /** * This function is adapted from code coming from Zend Framework. @@ -195,10 +158,6 @@ export function escape(env: TwingEnvironment, string: any, strategy: string = 'h return `&#x${hex};`; }); - // if (charset !== 'UTF-8') { - // string = iconv('UTF-8', charset, string).toString(); - // } - return string; case 'url': return rawurlencode(string); diff --git a/src/lib/loader/filesystem.ts b/src/lib/loader/filesystem.ts index 7092a1282..4b8d65ee4 100644 --- a/src/lib/loader/filesystem.ts +++ b/src/lib/loader/filesystem.ts @@ -35,10 +35,7 @@ export class TwingLoaderFilesystem implements TwingLoaderInterface { rootPath = (rootPath === null ? process.cwd() : rootPath); this.rootPath = resolvePath(rootPath); - - if (paths) { - this.setPaths(paths); - } + this.setPaths(paths); } /** diff --git a/src/lib/node-visitor/macro-auto-import.ts b/src/lib/node-visitor/macro-auto-import.ts index a683fcfa1..a199c4d67 100644 --- a/src/lib/node-visitor/macro-auto-import.ts +++ b/src/lib/node-visitor/macro-auto-import.ts @@ -8,12 +8,10 @@ import {TwingNodeExpressionMethodCall} from "../node/expression/method-call"; import {TwingNodeExpressionArray} from "../node/expression/array"; export class TwingNodeVisitorMacroAutoImport extends TwingBaseNodeVisitor { - private inAModule: boolean = false; private hasMacroCalls: boolean = false; public doEnterNode(node: TwingNode, env: TwingEnvironment) { if (node.getType() == TwingNodeType.MODULE) { - this.inAModule = true; this.hasMacroCalls = false; } @@ -22,12 +20,10 @@ export class TwingNodeVisitorMacroAutoImport extends TwingBaseNodeVisitor { public doLeaveNode(node: TwingNode, env: TwingEnvironment) { if (node.getType() == TwingNodeType.MODULE) { - this.inAModule = false; - if (this.hasMacroCalls) { node.getNode('constructor_end').setNode('_auto_macro_import', new TwingNodeImport(new TwingNodeExpressionName('_self', 0, 0), new TwingNodeExpressionAssignName('_self', 0, 0), 0, 0, 'import', true)); } - } else if (this.inAModule) { + } else { if ((node.getType() == TwingNodeType.EXPRESSION_GET_ATTR) && (node.getNode('node').getType() === TwingNodeType.EXPRESSION_NAME) && (node.getNode('node').getAttribute('name') === '_self') && (node.getNode('attribute').getType() === TwingNodeType.EXPRESSION_CONSTANT)) { this.hasMacroCalls = true; diff --git a/src/lib/node-visitor/optimizer.ts b/src/lib/node-visitor/optimizer.ts index 3490fbac1..e1afbe4e2 100644 --- a/src/lib/node-visitor/optimizer.ts +++ b/src/lib/node-visitor/optimizer.ts @@ -1,18 +1,10 @@ import {TwingBaseNodeVisitor} from "../base-node-visitor"; -import {TwingNode, TwingNodeType} from "../node"; +import {TwingNode} from "../node"; import {TwingEnvironment} from "../environment"; -const isInteger = require('is-integer'); - /** - * TwingNodeVisitorOptimizer tries to optimizes the AST. - * - * This visitor is always the last registered one. - * - * You can configure which optimizations you want to activate via the - * optimizer mode. - * * @author Eric MORAND + * @deprecated */ export class TwingNodeVisitorOptimizer extends TwingBaseNodeVisitor { static readonly OPTIMIZE_ALL = -1; @@ -22,157 +14,14 @@ export class TwingNodeVisitorOptimizer extends TwingBaseNodeVisitor { // obsolete, does not do anything static readonly OPTIMIZE_VAR_ACCESS = 8; - private loops: Array = []; - private loopsTargets: Array = []; - private optimizers: number; - - /** - * @param {number} optimizers The optimizer mode - */ - constructor(optimizers: number = -1) { - super(); - - this.TwingNodeVisitorInterfaceImpl = this; - - if (!isInteger(optimizers) || optimizers > (TwingNodeVisitorOptimizer.OPTIMIZE_FOR | TwingNodeVisitorOptimizer.OPTIMIZE_RAW_FILTER | TwingNodeVisitorOptimizer.OPTIMIZE_VAR_ACCESS)) { - throw new Error(`Optimizer mode "${optimizers}" is not valid.`); - } - - this.optimizers = optimizers; - } - protected doEnterNode(node: TwingNode, env: TwingEnvironment) { - if (TwingNodeVisitorOptimizer.OPTIMIZE_FOR === (TwingNodeVisitorOptimizer.OPTIMIZE_FOR & this.optimizers)) { - this.enterOptimizeFor(node, env); - } - return node; } protected doLeaveNode(node: TwingNode, env: TwingEnvironment) { - if (TwingNodeVisitorOptimizer.OPTIMIZE_FOR === (TwingNodeVisitorOptimizer.OPTIMIZE_FOR & this.optimizers)) { - this.leaveOptimizeFor(node, env); - } - - if (TwingNodeVisitorOptimizer.OPTIMIZE_RAW_FILTER === (TwingNodeVisitorOptimizer.OPTIMIZE_RAW_FILTER & this.optimizers)) { - node = this.optimizeRawFilter(node, env); - } - - node = this.optimizePrintNode(node, env); - return node; } - /** - * Optimizes print nodes. - * - * It replaces: - * - * * "echo this.render(Parent)Block()" with "this.display(Parent)Block()" - * - * @returns {TwingNode} - */ - private optimizePrintNode(node: TwingNode, env: TwingEnvironment) { - if (node.getType() !== TwingNodeType.PRINT) { - return node; - } - - let exprNode = node.getNode('expr'); - - if (exprNode.getType() === TwingNodeType.EXPRESSION_BLOCK_REFERENCE || - exprNode.getType() === TwingNodeType.EXPRESSION_PARENT) { - exprNode.setAttribute('output', true); - - return exprNode; - } - - return node; - } - - /** - * Removes "raw" filters. - * - * @returns {TwingNode} - */ - private optimizeRawFilter(node: TwingNode, env: TwingEnvironment) { - if (node.getType() === TwingNodeType.EXPRESSION_FILTER && node.getNode('filter').getAttribute('value') === 'raw') { - return node.getNode('node'); - } - - return node; - } - - /** - * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - */ - private enterOptimizeFor(node: TwingNode, env: TwingEnvironment) { - if (node.getType() === TwingNodeType.FOR) { - // disable the loop variable by default - node.setAttribute('with_loop', false); - this.loops.unshift(node); - this.loopsTargets.unshift(node.getNode('value_target').getAttribute('name')); - this.loopsTargets.unshift(node.getNode('key_target').getAttribute('name')); - } - else if (this.loops.length < 1) { - // we are outside a loop - return; - } - - // when do we need to add the loop variable back? - - // the loop variable is referenced for the active loop - else if (node.getType() === TwingNodeType.EXPRESSION_NAME && node.getAttribute('name') === 'loop') { - node.setAttribute('always_defined', true); - this.addLoopToCurrent(); - } - - // optimize access to loop targets - else if (node.getType() === TwingNodeType.EXPRESSION_NAME && this.loopsTargets.includes(node.getAttribute('name'))) { - node.setAttribute('always_defined', true); - } - - // block reference - else if (node.getType() === TwingNodeType.BLOCK_REFERENCE || node.getType() === TwingNodeType.EXPRESSION_BLOCK_REFERENCE) { - this.addLoopToCurrent(); - } - - // include without the only attribute - else if (node.getType() === TwingNodeType.INCLUDE && !node.getAttribute('only')) { - this.addLoopToAll(); - } - - // include function without the with_context=false parameter - else if (node.getType() === TwingNodeType.EXPRESSION_FUNCTION && node.getAttribute('name') === 'include' && (!node.getNode('arguments').hasNode('with_context') || node.getNode('arguments').getNode('with_context').getAttribute('value') !== false)) { - this.addLoopToAll(); - } - - // the loop variable is referenced via an attribute - else if (node.getType() === TwingNodeType.EXPRESSION_GET_ATTR && (node.getNode('attribute').getType() !== TwingNodeType.EXPRESSION_CONSTANT || node.getNode('attribute').getAttribute('value') === 'parent') && (this.loops[0].getAttribute('with_loop') === true || (node.getNode('node').getType() === TwingNodeType.EXPRESSION_NAME && node.getNode('node').getAttribute('name') === 'loop'))) { - this.addLoopToAll(); - } - } - - /** - * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - */ - private leaveOptimizeFor(node: TwingNode, env: TwingEnvironment) { - if (node.getType() === TwingNodeType.FOR) { - this.loops.shift(); - this.loopsTargets.shift(); - this.loopsTargets.shift(); - } - } - - private addLoopToCurrent() { - this.loops[0].setAttribute('with_loop', true); - } - - private addLoopToAll() { - for (let loop of this.loops) { - loop.setAttribute('with_loop', true); - } - } - getPriority() { return 255; } diff --git a/src/lib/node-visitor/sandbox.ts b/src/lib/node-visitor/sandbox.ts index 34becce3d..a95921dc2 100644 --- a/src/lib/node-visitor/sandbox.ts +++ b/src/lib/node-visitor/sandbox.ts @@ -5,7 +5,6 @@ import {TwingNodeCheckSecurity} from "../node/check-security"; import {TwingNodeCheckToString} from "../node/check-to-string"; export class TwingNodeVisitorSandbox extends TwingBaseNodeVisitor { - private inAModule: boolean = false; private tags: Map; private filters: Map; private functions: Map; @@ -23,13 +22,12 @@ export class TwingNodeVisitorSandbox extends TwingBaseNodeVisitor { } if (node.getType() === TwingNodeType.MODULE) { - this.inAModule = true; this.tags = new Map(); this.filters = new Map(); this.functions = new Map(); return node; - } else if (this.inAModule) { + } else { // look for tags if (node.getNodeTag() && !(this.tags.has(node.getNodeTag()))) { this.tags.set(node.getNodeTag(), node); @@ -87,8 +85,6 @@ export class TwingNodeVisitorSandbox extends TwingBaseNodeVisitor { } if (node.getType() === TwingNodeType.MODULE) { - this.inAModule = false; - let nodes = new Map(); let i: number = 0; @@ -96,7 +92,7 @@ export class TwingNodeVisitorSandbox extends TwingBaseNodeVisitor { nodes.set(i++, node.getNode('display_start')); node.getNode('constructor_end').setNode('_security_check', new TwingNode(nodes)); - } else if (this.inAModule) { + } else { if (node.getType() === TwingNodeType.PRINT || node.getType() === TwingNodeType.SET) { this.needsToStringWrap = false; } diff --git a/src/lib/node/block-reference.ts b/src/lib/node/block-reference.ts index 95d4d3c41..d7d3424ed 100644 --- a/src/lib/node/block-reference.ts +++ b/src/lib/node/block-reference.ts @@ -21,7 +21,7 @@ export class TwingNodeBlockReference extends TwingNode implements TwingNodeOutpu compile(compiler: TwingCompiler) { compiler - .write(`await this.traceableDisplayBlock(${this.getTemplateLine()}, this.getSourceContext())('${this.getAttribute('name')}', context.clone(), blocks);\n`) + .write(`this.echo(await this.traceableRenderBlock(${this.getTemplateLine()}, this.getSourceContext())('${this.getAttribute('name')}', context.clone(), blocks));\n`) ; } } diff --git a/src/lib/node/expression/block-reference.ts b/src/lib/node/expression/block-reference.ts index 2c7a591cb..5ffd70d39 100644 --- a/src/lib/node/expression/block-reference.ts +++ b/src/lib/node/expression/block-reference.ts @@ -25,16 +25,8 @@ export class TwingNodeExpressionBlockReference extends TwingNodeExpression { compile(compiler: TwingCompiler) { if (this.getAttribute('is_defined_test')) { this.compileTemplateCall(compiler, 'traceableHasBlock'); - } - else { - if (this.getAttribute('output')) { - this - .compileTemplateCall(compiler, `traceableDisplayBlock`) - .raw(";\n"); - } - else { - this.compileTemplateCall(compiler, 'traceableRenderBlock'); - } + } else { + this.compileTemplateCall(compiler, 'traceableRenderBlock'); } } @@ -43,8 +35,7 @@ export class TwingNodeExpressionBlockReference extends TwingNodeExpression { if (!this.hasNode('template')) { compiler.raw('this'); - } - else { + } else { compiler .raw('(await this.loadTemplate(') .subcompile(this.getNode('template')) diff --git a/src/lib/node/expression/parent.ts b/src/lib/node/expression/parent.ts index 137f9dc0d..3761a90f1 100644 --- a/src/lib/node/expression/parent.ts +++ b/src/lib/node/expression/parent.ts @@ -18,19 +18,10 @@ export class TwingNodeExpressionParent extends TwingNodeExpression { compile(compiler: TwingCompiler) { let name = this.getAttribute('name'); - if (this.getAttribute('output')) { - compiler - .write(`await this.traceableDisplayParentBlock(${this.getTemplateLine()}, this.getSourceContext())(`) - .string(name) - .raw(", context, blocks);\n") - ; - } - else { - compiler - .raw(`this.traceableRenderParentBlock(${this.getTemplateLine()}, this.getSourceContext())(`) - .string(name) - .raw(', context, blocks)') - ; - } + compiler + .raw(`await this.traceableRenderParentBlock(${this.getTemplateLine()}, this.getSourceContext())(`) + .string(name) + .raw(', context, blocks)') + ; } } diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 7a09f859f..0f596e40a 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -377,15 +377,13 @@ export class TwingParser { return null; }; - let length = this.importedSymbols.length; + result = testImportedSymbol(this.importedSymbols[0]); - if (length > 0) { - result = testImportedSymbol(this.importedSymbols[0]); + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + let length = this.importedSymbols.length; - // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) - if (!result && (length > 1)) { - result = testImportedSymbol(this.importedSymbols[length - 1]); - } + if (!result && (length > 1)) { + result = testImportedSymbol(this.importedSymbols[length - 1]); } return result; @@ -1231,7 +1229,7 @@ export class TwingParser { if (function_.isDeprecated()) { let message = `Twing Function "${function_.getName()}" is deprecated`; - if (typeof function_.getDeprecatedVersion() !== 'boolean') { + if (function_.getDeprecatedVersion() !== true) { message += ` since version ${function_.getDeprecatedVersion()}`; } @@ -1263,7 +1261,7 @@ export class TwingParser { if (filter.isDeprecated()) { let message = `Twing Filter "${filter.getName()}" is deprecated`; - if (typeof filter.getDeprecatedVersion() !== 'boolean') { + if (filter.getDeprecatedVersion() !== true) { message += ` since version ${filter.getDeprecatedVersion()}`; } diff --git a/src/lib/template.ts b/src/lib/template.ts index c60c7f7cb..7e5b45394 100644 --- a/src/lib/template.ts +++ b/src/lib/template.ts @@ -68,6 +68,10 @@ export abstract class TwingTemplate { this.macroHandlers = new Map(); } + get environment(): TwingEnvironment { + return this.env; + } + /** * Returns the template name. * @@ -151,7 +155,7 @@ export abstract class TwingTemplate { * * @returns {Promise} */ - protected displayBlock(name: string, context: any, blocks: TwingTemplateBlocksMap = new Map(), useBlocks = true): Promise { + protected displayBlock(name: string, context: any, blocks: TwingTemplateBlocksMap, useBlocks: boolean): Promise { return this.getBlocks().then((ownBlocks) => { let blockHandler: TwingTemplateBlockHandler; @@ -292,15 +296,13 @@ export abstract class TwingTemplate { promise = this.env.resolveTemplate([...templates.values()], this.getSourceContext()); } - return promise.catch((e) => { - if (e instanceof TwingError) { - if (e.getTemplateLine() !== -1) { - throw e; - } + return promise.catch((e: TwingError) => { + if (e.getTemplateLine() !== -1) { + throw e; + } - if (line) { - e.setTemplateLine(line); - } + if (line) { + e.setTemplateLine(line); } throw e; @@ -428,10 +430,20 @@ export abstract class TwingTemplate { } } + /** + * @param lineno + * @param source + * @deprecated + */ public traceableDisplayBlock(lineno: number, source: TwingSource): TwingTemplateTraceableMethod { return this.traceableMethod(this.displayBlock.bind(this), lineno, source); } + /** + * @param lineno + * @param source + * @deprecated + */ public traceableDisplayParentBlock(lineno: number, source: TwingSource): TwingTemplateTraceableMethod { return this.traceableMethod(this.displayParentBlock.bind(this), lineno, source); } @@ -510,11 +522,9 @@ export abstract class TwingTemplate { protected get include(): (context: any, from: TwingSource, templates: string | Map | TwingTemplate, variables: any, withContext: boolean, ignoreMissing: boolean, line: number) => Promise { return (context: any, from: TwingSource, templates: string | Map | TwingTemplate, variables: any, withContext: boolean, ignoreMissing: boolean, line: number): Promise => { - return include(this.env, context, from, templates, variables, withContext, ignoreMissing).catch((e) => { - if (e instanceof TwingError) { - if (e.getTemplateLine() === -1) { - e.setTemplateLine(line); - } + return include(this.env, context, from, templates, variables, withContext, ignoreMissing).catch((e: TwingError) => { + if (e.getTemplateLine() === -1) { + e.setTemplateLine(line); } throw e; diff --git a/test/tests/integration/fixtures/filters/escape_with_variable_as_strategy.ts b/test/tests/integration/fixtures/filters/escape_with_variable_as_strategy.ts new file mode 100644 index 000000000..86b01698b --- /dev/null +++ b/test/tests/integration/fixtures/filters/escape_with_variable_as_strategy.ts @@ -0,0 +1,25 @@ +import TestBase from "../../TestBase"; + +export default class extends TestBase { + getDescription() { + return '"escape" filter with variable as strategy'; + } + + getTemplates() { + return { + 'index.twig': ` +{% set strategy = "html" %} +{{ "foo
"|escape(strategy) }}` + }; + } + + /** + * Double-escape as expected + * @see https://twig.symfony.com/doc/3.x/filters/escape.html + */ + getExpected() { + return ` +foo &lt;br /&gt; +`; + } +} diff --git a/test/tests/unit/lib/environment/test.ts b/test/tests/unit/lib/environment/test.ts index 950385a5c..a7ca17d51 100644 --- a/test/tests/unit/lib/environment/test.ts +++ b/test/tests/unit/lib/environment/test.ts @@ -29,6 +29,7 @@ import {TwingTemplate} from "../../../../../src/lib/template"; import {MappingItem, SourceMapConsumer} from "source-map"; import {TwingLoaderFilesystem} from "../../../../../src/lib/loader/filesystem"; import {TwingNodeText} from "../../../../../src/lib/node/text"; +import {TwingSandboxSecurityPolicy} from "../../../../../src/lib/sandbox/security-policy"; const tmp = require('tmp'); @@ -875,8 +876,6 @@ tape('environment', (test) => { let mappings: MappingItem[] = []; consumer.eachMapping((mapping: MappingItem) => { - console.warn(mapping); - mappings.push({ source: mapping.source, generatedLine: mapping.generatedLine, @@ -1341,5 +1340,28 @@ BAROOFoof`); test.end(); }); + test.test('checkSecurity', (test) => { + test.test('doesnt\'t trigger the security policy when sandboxing is disabled', (test) => { + const fooPolicy = new TwingSandboxSecurityPolicy(); + + let env = new TwingEnvironmentNode(new TwingLoaderArray({ + index: '{{foo}}' + }), { + sandboxed: false, + sandbox_policy: fooPolicy + }); + + let checkSecuritySpy = sinon.spy(fooPolicy, 'checkSecurity'); + + env.checkSecurity([], [], []); + + test.true(checkSecuritySpy.notCalled); + + test.end(); + }); + + test.end(); + }); + test.end(); }); diff --git a/test/tests/unit/lib/extension-set/test.ts b/test/tests/unit/lib/extension-set/test.ts index 619a5d565..191e5c723 100644 --- a/test/tests/unit/lib/extension-set/test.ts +++ b/test/tests/unit/lib/extension-set/test.ts @@ -12,6 +12,7 @@ import {Token} from "twig-lexer"; import {TwingNode} from "../../../../../src/lib/node"; import {TwingBaseNodeVisitor} from "../../../../../src/lib/base-node-visitor"; import {TwingEnvironment} from "../../../../../src/lib/environment"; +import {spy} from "sinon"; class TwingTestExtensionSetExtension extends TwingExtension { getOperators() { @@ -403,6 +404,20 @@ tape('extension-set', (test) => { test.true(extensionSet.isInitialized()); + test.test('on subsequent calls, don\'t initialize extensions', (test) => { + let fooExtension = new TwingExtension(); + let getFiltersSpy = spy(fooExtension, 'getFilters'); + + extensionSet = new TwingExtensionSet(); + extensionSet.addExtension(fooExtension, 'foo'); + extensionSet.getSourceMapNodeFactories(); + extensionSet.getSourceMapNodeFactories(); + + test.same(getFiltersSpy.callCount, 1); + + test.end(); + }); + test.end(); }); diff --git a/test/tests/unit/lib/extension/core/filters/escape/test.ts b/test/tests/unit/lib/extension/core/filters/escape/test.ts index d99569551..e9e1a5282 100644 --- a/test/tests/unit/lib/extension/core/filters/escape/test.ts +++ b/test/tests/unit/lib/extension/core/filters/escape/test.ts @@ -330,10 +330,10 @@ tape('escaping', (test) => { }); test.test('customEscaper', async (test) => { - let customEscaperCases: [string, string | number, string][] = [ - ['fooUTF-8', 'foo', 'foo'], - ['UTF-8', null, 'foo'], - ['42UTF-8', 42, 'foo'], + let customEscaperCases: [string, string | number, string, string][] = [ + ['fooUTF-8', 'foo', 'foo', 'UTF-8'], + ['UTF-8', null, 'foo', 'UTF-8'], + ['42UTF-8', 42, 'foo', undefined], ]; for (let customEscaperCase of customEscaperCases) { @@ -341,7 +341,7 @@ tape('escaping', (test) => { twing.getCoreExtension().setEscaper('foo', foo_escaper_for_test); - test.same(await escape(twing, customEscaperCase[1], customEscaperCase[2]), customEscaperCase[0]); + test.same(await escape(twing, customEscaperCase[1], customEscaperCase[2], customEscaperCase[3]), customEscaperCase[0]); } test.end(); diff --git a/test/tests/unit/lib/extension/core/functions/dump/test.ts b/test/tests/unit/lib/extension/core/functions/dump/test.ts index aabe0a43a..8cef92af2 100644 --- a/test/tests/unit/lib/extension/core/functions/dump/test.ts +++ b/test/tests/unit/lib/extension/core/functions/dump/test.ts @@ -1,5 +1,8 @@ import * as tape from 'tape'; import {dump} from "../../../../../../../../src/lib/extension/core/functions/dump"; +import {TwingTemplate} from "../../../../../../../../src/lib/template"; +import {TwingEnvironmentNode} from "../../../../../../../../src/lib/environment/node"; +import {TwingLoaderArray} from "../../../../../../../../src/lib/loader/array"; tape('dump', async (test) => { test.same(await dump({}, null), `NULL @@ -50,6 +53,16 @@ string(3) "bar" test.same(await dump({}, new Date(0, 0, 0, 0, 0, 0, 0)), `array(0) { } +`); + + class FooTemplate extends TwingTemplate { + protected doDisplay(context: any, blocks: Map): Promise { + return undefined; + } + } + + test.same(await dump({foo: new FooTemplate(new TwingEnvironmentNode(new TwingLoaderArray({})))}), `array(0) { +} `); test.end(); diff --git a/test/tests/unit/lib/helpers/is-in/test.ts b/test/tests/unit/lib/helpers/is-in/test.ts index 8f08ab393..3c8fa9fa1 100644 --- a/test/tests/unit/lib/helpers/is-in/test.ts +++ b/test/tests/unit/lib/helpers/is-in/test.ts @@ -3,6 +3,9 @@ import {isIn} from "../../../../../../src/lib/helpers/is-in"; tape('is-in', (test) => { test.true(isIn(1, [1, 2])); + test.true(isIn(2, [1, 2])); + test.true(isIn(1, {foo: 1, bar: 2})); + test.true(isIn(2, {foo: 1, bar: 2})); test.end(); }); diff --git a/test/tests/unit/lib/node-visitor/optimizer/test.ts b/test/tests/unit/lib/node-visitor/optimizer/test.ts index 40984b419..ffa7cd351 100644 --- a/test/tests/unit/lib/node-visitor/optimizer/test.ts +++ b/test/tests/unit/lib/node-visitor/optimizer/test.ts @@ -1,118 +1,33 @@ import * as tape from 'tape'; import {TwingNodeVisitorOptimizer} from "../../../../../../src/lib/node-visitor/optimizer"; -import {TwingEnvironmentNode} from "../../../../../../src/lib/environment/node"; -import {TwingLoaderArray} from "../../../../../../src/lib/loader/array"; -import {TwingNodeFor} from "../../../../../../src/lib/node/for"; -import {TwingNodeExpressionAssignName} from "../../../../../../src/lib/node/expression/assign-name"; -import {TwingNodeExpressionConstant} from "../../../../../../src/lib/node/expression/constant"; -import {TwingNodeInclude} from "../../../../../../src/lib/node/include"; -import {TwingNodeExpressionFunction} from "../../../../../../src/lib/node/expression/function"; import {TwingNode} from "../../../../../../src/lib/node"; -const sinon = require('sinon'); - tape('node-visitor/optimizer', (test) => { - test.test('constructor', function(test) { + test.test('enterNode', (test) => { let visitor = new TwingNodeVisitorOptimizer(); + let node = new TwingNode(); - test.equals(Reflect.get(visitor, 'optimizers'), -1); - - try { - new TwingNodeVisitorOptimizer('foo' as any); - - test.fail(); - } - catch (e) { - test.same(e.message, 'Optimizer mode "foo" is not valid.'); - } - - try { - new TwingNodeVisitorOptimizer(15); - - test.fail(); - } - catch (e) { - test.same(e.message, 'Optimizer mode "15" is not valid.'); - } + test.same(visitor.enterNode(node, null), node); test.end(); }); - test.test('enterOptimizeFor', (test) => { - test.test('add the loop back', function(test) { - let visitor = new TwingNodeVisitorOptimizer(); - let enterOptimizeFor = Reflect.get(visitor, 'enterOptimizeFor').bind(visitor); - let env = new TwingEnvironmentNode(new TwingLoaderArray({})); - let forNode = new TwingNodeFor(new TwingNodeExpressionAssignName('foo', 1, 1), new TwingNodeExpressionAssignName('foo', 1, 1), new TwingNodeExpressionConstant('foo', 1, 1), null, null, null, 1, 1); - - enterOptimizeFor(forNode, env); - - test.test('when visiting an "include" node', function(test) { - let includeNode = new TwingNodeInclude(new TwingNodeExpressionConstant('foo', 1, 1), null, false, false, 1, 1); - - let addLoopToAllStub = sinon.stub(visitor, 'addLoopToAll'); - - enterOptimizeFor(includeNode, env); - - test.true(addLoopToAllStub.calledOnce); - - addLoopToAllStub.restore(); - - test.end(); - }); - - test.test('when visiting an "include function" node', function(test) { - test.test('with "with_context" set to true', function(test) { - let functionNode = new TwingNodeExpressionFunction('include', new TwingNode(), 1, 1); - let addLoopToAllStub = sinon.stub(visitor, 'addLoopToAll'); - - enterOptimizeFor(functionNode, env); - - test.true(addLoopToAllStub.calledOnce); - - addLoopToAllStub.restore(); - - test.end(); - }); - - test.end(); - }); - - test.end(); - }); - - test.test('doesn\'t add the loop back', function(test) { - let visitor = new TwingNodeVisitorOptimizer(); - let enterOptimizeFor = Reflect.get(visitor, 'enterOptimizeFor').bind(visitor); - let env = new TwingEnvironmentNode(new TwingLoaderArray({})); - let forNode = new TwingNodeFor(new TwingNodeExpressionAssignName('foo', 1, 1), new TwingNodeExpressionAssignName('foo', 1, 1), new TwingNodeExpressionConstant('foo', 1, 1), null, null, null, 1, 1); - - enterOptimizeFor(forNode, env); - - test.test('when visiting an "include function" node', function(test) { - test.test('with "with_context" set to false', function(test) { - let functionNode = new TwingNodeExpressionFunction('include', new TwingNode(new Map([ - ['with_context', new TwingNodeExpressionConstant(false, 1, 1)] - ])), 1, 1); - let addLoopToAllStub = sinon.stub(visitor, 'addLoopToAll'); - - enterOptimizeFor(functionNode, env); - - test.true(addLoopToAllStub.notCalled); + test.test('leaveNode', (test) => { + let visitor = new TwingNodeVisitorOptimizer(); + let node = new TwingNode(); - addLoopToAllStub.restore(); + test.same(visitor.leaveNode(node, null), node); - test.end(); - }); + test.end(); + }); - test.end(); - }); + test.test('getPriority', (test) => { + let visitor = new TwingNodeVisitorOptimizer(); - test.end(); - }); + test.same(visitor.getPriority(), 255); test.end(); }); test.end(); -}); \ No newline at end of file +}); diff --git a/test/tests/unit/lib/node/block-reference/test.ts b/test/tests/unit/lib/node/block-reference/test.ts index d0b7df596..bbf139914 100644 --- a/test/tests/unit/lib/node/block-reference/test.ts +++ b/test/tests/unit/lib/node/block-reference/test.ts @@ -17,7 +17,7 @@ tape('node/block-reference', (test) => { let node = new TwingNodeBlockReference('foo', 1, 1); let compiler = new MockCompiler(); - test.same(compiler.compile(node).getSource(), `await this.traceableDisplayBlock(1, this.getSourceContext())(\'foo\', context.clone(), blocks); + test.same(compiler.compile(node).getSource(), `this.echo(await this.traceableRenderBlock(1, this.getSourceContext())(\'foo\', context.clone(), blocks)); `); test.end(); diff --git a/test/tests/unit/lib/node/expression/parent/test.ts b/test/tests/unit/lib/node/expression/parent/test.ts index fceb76485..cc21d1ffc 100644 --- a/test/tests/unit/lib/node/expression/parent/test.ts +++ b/test/tests/unit/lib/node/expression/parent/test.ts @@ -18,12 +18,12 @@ tape('node/expression/parent', (test) => { let node = new TwingNodeExpressionParent('foo', 1); - test.same(compiler.compile(node).getSource(), 'this.traceableRenderParentBlock(1, this.getSourceContext())(\`foo\`, context, blocks)'); + test.same(compiler.compile(node).getSource(), 'await this.traceableRenderParentBlock(1, this.getSourceContext())(\`foo\`, context, blocks)'); test.test('with special character', (test) => { let node = new TwingNodeExpressionParent('£', 1); - test.same(compiler.compile(node).getSource(), 'this.traceableRenderParentBlock(1, this.getSourceContext())(\`£\`, context, blocks)'); + test.same(compiler.compile(node).getSource(), 'await this.traceableRenderParentBlock(1, this.getSourceContext())(\`£\`, context, blocks)'); test.end(); }); diff --git a/test/tests/unit/lib/parser/test.ts b/test/tests/unit/lib/parser/test.ts index a6945873f..4a0769c78 100644 --- a/test/tests/unit/lib/parser/test.ts +++ b/test/tests/unit/lib/parser/test.ts @@ -55,7 +55,7 @@ class TwingTestExpressionParserExtension extends TwingExtension { getFunctions() { return [ new TwingFunction('deprecated', () => Promise.resolve(), [], { - deprecated: '1' + deprecated: true }), new TwingFunction('deprecated_with_version', () => Promise.resolve(), [], { deprecated: '1' @@ -63,7 +63,7 @@ class TwingTestExpressionParserExtension extends TwingExtension { new TwingFunction('deprecated_with_alternative', () => Promise.resolve(), [], { deprecated: '1', alternative: 'alternative' - }) + }), ]; } @@ -76,7 +76,7 @@ class TwingTestExpressionParserExtension extends TwingExtension { getFilters() { return [ new TwingFilter('deprecated', () => Promise.resolve(), [], { - deprecated: '1' + deprecated: true }), new TwingFilter('deprecated_with_version', () => Promise.resolve(), [], { deprecated: '1' @@ -974,10 +974,10 @@ tape('parser', (test) => { env.addExtension(new TwingTestExpressionParserExtension(), 'foo'); let testCases: [string, boolean, string][] = [ - ['deprecated', false, 'Twing Function "deprecated" is deprecated since version 1 in "index" at line 1.'], + ['deprecated', false, 'Twing Function "deprecated" is deprecated in "index" at line 1.'], ['deprecated_with_version', false, 'Twing Function "deprecated_with_version" is deprecated since version 1 in "index" at line 1.'], ['deprecated_with_alternative', false, 'Twing Function "deprecated_with_alternative" is deprecated since version 1. Use "alternative" instead in "index" at line 1.'], - ['deprecated', true, 'Twing Function "deprecated" is deprecated since version 1 in "index.html.twig" at line 1.'] + ['deprecated', true, 'Twing Function "deprecated" is deprecated in "index.html.twig" at line 1.'] ]; let parser = new TwingParser(env); @@ -998,7 +998,7 @@ tape('parser', (test) => { process.stdout.write = (chunk: string | Buffer): boolean => { process.stdout.write = originalWrite; - test.same(chunk, testCase[2]); + test.same(chunk, testCase[2], testCase[0]); return true; }; @@ -1161,10 +1161,10 @@ tape('parser', (test) => { env.addExtension(new TwingTestExpressionParserExtension(), 'foo'); let testCases: [string, boolean, string][] = [ - ['deprecated', false, 'Twing Filter "deprecated" is deprecated since version 1 in "index" at line 1.'], + ['deprecated', false, 'Twing Filter "deprecated" is deprecated in "index" at line 1.'], ['deprecated_with_version', false, 'Twing Filter "deprecated_with_version" is deprecated since version 1 in "index" at line 1.'], ['deprecated_with_alternative', false, 'Twing Filter "deprecated_with_alternative" is deprecated since version 1. Use "alternative" instead in "index" at line 1.'], - ['deprecated', true, 'Twing Filter "deprecated" is deprecated since version 1 in "index.html.twig" at line 1.'] + ['deprecated', true, 'Twing Filter "deprecated" is deprecated in "index.html.twig" at line 1.'] ]; let parser = new TwingParser(env); @@ -1182,7 +1182,7 @@ tape('parser', (test) => { process.stdout.write = (chunk: Buffer | string): boolean => { process.stdout.write = originalWrite; - test.same(chunk, testCase[2]); + test.same(chunk, testCase[2], testCase[0]); return true; }; diff --git a/test/tests/unit/lib/template/test.ts b/test/tests/unit/lib/template/test.ts index 1fbd6e370..62020659f 100644 --- a/test/tests/unit/lib/template/test.ts +++ b/test/tests/unit/lib/template/test.ts @@ -20,6 +20,10 @@ class TwingTestTemplateTemplate extends TwingTemplate { this.sourceContext = new TwingSource('', 'foo'); } + getEnv(): TwingEnvironment { + return this.env; + } + setEnv(env: TwingEnvironment) { this.env = env; } @@ -78,6 +82,14 @@ class TwingTestTemplateTemplateWithInvalidLoadTemplate extends TwingTemplate { } tape('template', function (test) { + test.test('environment accessor', function (test) { + let template = new TwingTestTemplateTemplate(); + + test.same(template.environment, template.getEnv()); + + test.end(); + }); + test.test('getSourceContext', function (test) { let template = new TwingTestTemplateTemplate();