Skip to content

Commit

Permalink
Resolve #541
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Morand committed Oct 4, 2021
1 parent 56e186b commit 788d657
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 87 deletions.
5 changes: 5 additions & 0 deletions .nycrc.json
Expand Up @@ -7,6 +7,11 @@
".ts"
],
"include": "src",
"exclude": [
"src/base.ts",
"src/browser.ts",
"src/main.ts"
],
"reporter": [
"text-summary",
"html"
Expand Down
7 changes: 4 additions & 3 deletions src/lib/environment.ts
Expand Up @@ -5,7 +5,7 @@ import {TwingExtensionCore} from "./extension/core";
import {TwingExtensionInterface} from "./extension-interface";
import {TwingFilter} from "./filter";
import {TwingLexer} from "./lexer";
import {TwingParser} from "./parser";
import {TwingParser, TwingParserOptions} from "./parser";
import {TwingTokenStream} from "./token-stream";
import {TwingSource} from "./source";
import {TwingLoaderInterface} from "./loader-interface";
Expand Down Expand Up @@ -535,13 +535,14 @@ export abstract class TwingEnvironment extends EventEmitter {
* Converts a token list to a template.
*
* @param {TwingTokenStream} stream
* @param {TwingParserOptions} options
* @return {TwingNodeModule}
*
* @throws {TwingErrorSyntax} When the token stream is syntactically or semantically wrong
*/
parse(stream: TwingTokenStream): TwingNodeModule {
parse(stream: TwingTokenStream, options?: TwingParserOptions): TwingNodeModule {
if (!this.parser) {
this.parser = new TwingParser(this);
this.parser = new TwingParser(this, options);
}

return this.parser.parse(stream);
Expand Down
20 changes: 13 additions & 7 deletions src/lib/node-visitor/escaper.ts
Expand Up @@ -129,24 +129,30 @@ export class TwingNodeVisitorEscaper extends TwingBaseNodeVisitor {
return new TwingNodePrint(this.getEscaperFilter(type, expression), node.getTemplateLine(), node.getTemplateColumn());
}

private preEscapeFilterNode(filter: TwingNodeExpressionFilter, env: TwingEnvironment) {
let name = filter.getNode('filter').getAttribute('value');
private preEscapeFilterNode(filterNode: TwingNodeExpressionFilter, env: TwingEnvironment) {
let name = filterNode.getNode('filter').getAttribute('value');

const filter = env.getFilter(name);

if (!filter) {
return filterNode;
}

let type = env.getFilter(name).getPreEscape();

if (type === null) {
return filter;
return filterNode;
}

let node = filter.getNode('node');
let node = filterNode.getNode('node');

if (this.isSafeFor(type, node, env)) {
return filter;
return filterNode;
}

filter.setNode('node', this.getEscaperFilter(type, node));
filterNode.setNode('node', this.getEscaperFilter(type, node));

return filter;
return filterNode;
}

private isSafeFor(type: TwingNode | string | false, expression: TwingNode, env: TwingEnvironment) {
Expand Down
115 changes: 80 additions & 35 deletions src/lib/parser.ts
Expand Up @@ -39,6 +39,8 @@ import {TwingNodeExpressionConditional} from "./node/expression/conditional";
import {TwingOperator, TwingOperatorAssociativity} from "./operator";
import {namePattern, Token, TokenType} from "twig-lexer";
import {typeToEnglish} from "./lexer";
import {TwingNodeExpressionFunction} from "./node/expression/function";
import {TwingNodeExpressionFilter} from "./node/expression/filter";

const sha256 = require('crypto-js/sha256');
const hex = require('crypto-js/enc-hex');
Expand Down Expand Up @@ -74,6 +76,10 @@ type TwingParserImportedSymbolAlias = {
type TwingParserImportedSymbolType = Map<string, TwingParserImportedSymbolAlias>;
type TwingParserImportedSymbol = Map<string, TwingParserImportedSymbolType>;

export type TwingParserOptions = {
strict: boolean;
};

export class TwingParser {
private stack: Array<TwingParserStackEntry> = [];
private stream: TwingTokenStream;
Expand All @@ -92,10 +98,15 @@ export class TwingParser {
private unaryOperators: Map<string, TwingOperator>;
private binaryOperators: Map<string, TwingOperator>;

constructor(env: TwingEnvironment) {
protected readonly options: TwingParserOptions;

constructor(env: TwingEnvironment, options?: TwingParserOptions) {
this.env = env;
this.unaryOperators = env.getUnaryOperators();
this.binaryOperators = env.getBinaryOperators();
this.options = Object.assign({}, {
strict: true
}, options);
}

getVarName(prefix: string = '__internal_'): string {
Expand Down Expand Up @@ -1188,6 +1199,8 @@ export class TwingParser {
}

private getTest(line: number): Array<any> {
const {strict} = this.options;

let stream = this.getStream();
let name = stream.expect(TokenType.NAME).value;

Expand All @@ -1208,6 +1221,20 @@ export class TwingParser {

return [name, test];
}
else {
// non-existing two-words test
if (!strict) {
stream.next();

return [name, new TwingTest(name, null, [])];
}
}
}
else {
// non-existing one-word test
if (!strict) {
return [name, new TwingTest(name, null, [])];
}
}

let e = new TwingErrorSyntax(`Unknown "${name}" test.`, line, stream.getSourceContext());
Expand All @@ -1218,66 +1245,84 @@ export class TwingParser {
}

private getFunctionExpressionFactory(name: string, line: number, column: number) {
const {strict} = this.options;

let function_ = this.env.getFunction(name);

if (!function_) {
let e = new TwingErrorSyntax(`Unknown "${name}" function.`, line, this.getStream().getSourceContext());
if (function_) {
if (function_.isDeprecated()) {
let message = `Twing Function "${function_.getName()}" is deprecated`;

e.addSuggestions(name, Array.from(this.env.getFunctions().keys()));
if (function_.getDeprecatedVersion() !== true) {
message += ` since version ${function_.getDeprecatedVersion()}`;
}

throw e;
}
if (function_.getAlternative()) {
message += `. Use "${function_.getAlternative()}" instead`;
}

if (function_.isDeprecated()) {
let message = `Twing Function "${function_.getName()}" is deprecated`;
let src = this.getStream().getSourceContext();

if (function_.getDeprecatedVersion() !== true) {
message += ` since version ${function_.getDeprecatedVersion()}`;
}
message += ` in "${src.getName()}" at line ${line}.`;

if (function_.getAlternative()) {
message += `. Use "${function_.getAlternative()}" instead`;
process.stdout.write(message);
}

let src = this.getStream().getSourceContext();
return function_.getExpressionFactory();
}
else {
if (strict) {
let e = new TwingErrorSyntax(`Unknown "${name}" function.`, line, this.getStream().getSourceContext());

message += ` in "${src.getName()}" at line ${line}.`;
e.addSuggestions(name, Array.from(this.env.getFunctions().keys()));

process.stdout.write(message);
}
throw e;
}

return function_.getExpressionFactory();
return (name: string, functionArguments: TwingNode, line: number, columnno: number) => {
return new TwingNodeExpressionFunction(name, functionArguments, line, columnno);
};
}
}

private getFilterExpressionFactory(name: string, line: number, column: number) {
const {strict} = this.options;

let filter = this.env.getFilter(name);

if (!filter) {
let e = new TwingErrorSyntax(`Unknown "${name}" filter.`, line, this.getStream().getSourceContext());
if (filter) {
if (filter.isDeprecated()) {
let message = `Twing Filter "${filter.getName()}" is deprecated`;

e.addSuggestions(name, Array.from(this.env.getFilters().keys()));
if (filter.getDeprecatedVersion() !== true) {
message += ` since version ${filter.getDeprecatedVersion()}`;
}

throw e;
}
if (filter.getAlternative()) {
message += `. Use "${filter.getAlternative()}" instead`;
}

if (filter.isDeprecated()) {
let message = `Twing Filter "${filter.getName()}" is deprecated`;
let src = this.getStream().getSourceContext();

if (filter.getDeprecatedVersion() !== true) {
message += ` since version ${filter.getDeprecatedVersion()}`;
}
message += ` in "${src.getName()}" at line ${line}.`;

if (filter.getAlternative()) {
message += `. Use "${filter.getAlternative()}" instead`;
process.stdout.write(message);
}

let src = this.getStream().getSourceContext();
return filter.getExpressionFactory();
}
else {
if (strict) {
let e = new TwingErrorSyntax(`Unknown "${name}" filter.`, line, this.getStream().getSourceContext());

message += ` in "${src.getName()}" at line ${line}.`;
e.addSuggestions(name, Array.from(this.env.getFilters().keys()));

process.stdout.write(message);
}
throw e;
}

return filter.getExpressionFactory();
return (node: TwingNode, filterName: TwingNodeExpressionConstant, filterArguments: TwingNode, line: number, column: number, tag: string = null) => {
return new TwingNodeExpressionFilter(node, filterName, filterArguments, line, column, tag);
}
}
}
}

0 comments on commit 788d657

Please sign in to comment.