Skip to content

Commit

Permalink
Fix issue #449
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmorand committed Apr 19, 2020
1 parent cb934c3 commit f2e5bb9
Show file tree
Hide file tree
Showing 24 changed files with 174 additions and 386 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 5 additions & 7 deletions src/lib/callable-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type TwingCallableWrapperOptions = {
is_variadic?: boolean;
is_safe?: Array<any>;
is_safe_callback?: Function;
deprecated?: string;
deprecated?: boolean | string;
alternative?: string;
expression_factory?: Function;
}
Expand Down Expand Up @@ -76,12 +76,10 @@ export abstract class TwingCallableWrapper<T> {
let callable = this.callable;

return function () {
return (callable.apply(null, arguments) as Promise<T>).catch((e) => {
if (e instanceof TwingError) {
if (e.getTemplateLine() === -1) {
e.setTemplateLine(lineno);
e.setSourceContext(source);
}
return (callable.apply(null, arguments) as Promise<T>).catch((e: TwingError) => {
if (e.getTemplateLine() === -1) {
e.setTemplateLine(lineno);
e.setSourceContext(source);
}

throw e;
Expand Down
2 changes: 0 additions & 2 deletions src/lib/extension/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -318,7 +317,6 @@ export class TwingExtensionCore extends TwingExtension {
return [
new TwingNodeVisitorEscaper(),
new TwingNodeVisitorMacroAutoImport(),
new TwingNodeVisitorOptimizer(),
new TwingNodeVisitorSandbox()
];
}
Expand Down
41 changes: 0 additions & 41 deletions src/lib/extension/core/filters/escape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 1 addition & 4 deletions src/lib/loader/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
6 changes: 1 addition & 5 deletions src/lib/node-visitor/macro-auto-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;

Expand Down
155 changes: 2 additions & 153 deletions src/lib/node-visitor/optimizer.ts
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
* @deprecated
*/
export class TwingNodeVisitorOptimizer extends TwingBaseNodeVisitor {
static readonly OPTIMIZE_ALL = -1;
Expand All @@ -22,157 +14,14 @@ export class TwingNodeVisitorOptimizer extends TwingBaseNodeVisitor {
// obsolete, does not do anything
static readonly OPTIMIZE_VAR_ACCESS = 8;

private loops: Array<TwingNode> = [];
private loopsTargets: Array<TwingNode> = [];
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;
}
Expand Down
8 changes: 2 additions & 6 deletions src/lib/node-visitor/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, TwingNode>;
private filters: Map<string, TwingNode>;
private functions: Map<string, TwingNode>;
Expand All @@ -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);
Expand Down Expand Up @@ -87,16 +85,14 @@ export class TwingNodeVisitorSandbox extends TwingBaseNodeVisitor {
}

if (node.getType() === TwingNodeType.MODULE) {
this.inAModule = false;

let nodes = new Map();
let i: number = 0;

nodes.set(i++, new TwingNodeCheckSecurity(this.filters, this.tags, this.functions));
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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/node/block-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
;
}
}
Loading

0 comments on commit f2e5bb9

Please sign in to comment.