handling unknown filter errors
This commit is contained in:
@@ -51,4 +51,9 @@ public struct Environment {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var template: Template? {
|
||||
return errorReporter.context?.template
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class FilterNode : NodeType {
|
||||
throw TemplateSyntaxError("`endfilter` was not found.")
|
||||
}
|
||||
|
||||
let resolvable = try parser.compileFilter("filter_value|\(bits[1])")
|
||||
let resolvable = try parser.compileFilter("filter_value|\(bits[1])", containedIn: token)
|
||||
return FilterNode(nodes: blocks, resolvable: resolvable)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,24 +21,24 @@ class ForNode : NodeType {
|
||||
.map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }
|
||||
|
||||
let variable = components[3]
|
||||
let filter = try parser.compileFilter(variable, containedIn: token)
|
||||
|
||||
var emptyNodes = [NodeType]()
|
||||
|
||||
let forNodes = try parser.parse(until(["endfor", "empty"]))
|
||||
|
||||
guard let token = parser.nextToken() else {
|
||||
if let token = parser.nextToken() {
|
||||
if token.contents == "empty" {
|
||||
emptyNodes = try parser.parse(until(["endfor"]))
|
||||
_ = parser.nextToken()
|
||||
}
|
||||
} else {
|
||||
throw TemplateSyntaxError("`endfor` was not found.")
|
||||
}
|
||||
|
||||
if token.contents == "empty" {
|
||||
emptyNodes = try parser.parse(until(["endfor"]))
|
||||
_ = parser.nextToken()
|
||||
}
|
||||
|
||||
let filter = try parser.compileFilter(variable)
|
||||
let `where`: Expression?
|
||||
if components.count >= 6 {
|
||||
`where` = try parseExpression(components: Array(components.suffix(from: 5)), tokenParser: parser)
|
||||
`where` = try parseExpression(components: Array(components.suffix(from: 5)), tokenParser: parser, token: token)
|
||||
} else {
|
||||
`where` = nil
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ final class IfExpressionParser {
|
||||
let tokens: [IfToken]
|
||||
var position: Int = 0
|
||||
|
||||
init(components: [String], tokenParser: TokenParser) throws {
|
||||
init(components: [String], tokenParser: TokenParser, token: Token) throws {
|
||||
self.tokens = try components.map { component in
|
||||
if let op = findOperator(name: component) {
|
||||
switch op {
|
||||
@@ -111,7 +111,7 @@ final class IfExpressionParser {
|
||||
}
|
||||
}
|
||||
|
||||
return .variable(try tokenParser.compileFilter(component))
|
||||
return .variable(try tokenParser.compileFilter(component, containedIn: token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ final class IfExpressionParser {
|
||||
}
|
||||
|
||||
|
||||
func parseExpression(components: [String], tokenParser: TokenParser) throws -> Expression {
|
||||
let parser = try IfExpressionParser(components: components, tokenParser: tokenParser)
|
||||
func parseExpression(components: [String], tokenParser: TokenParser, token: Token) throws -> Expression {
|
||||
let parser = try IfExpressionParser(components: components, tokenParser: tokenParser, token: token)
|
||||
return try parser.parse()
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ class IfNode : NodeType {
|
||||
var components = token.components()
|
||||
components.removeFirst()
|
||||
|
||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
||||
let expression = try parseExpression(components: components, tokenParser: parser, token: token)
|
||||
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
||||
var conditions: [IfCondition] = [
|
||||
IfCondition(expression: expression, nodes: nodes)
|
||||
@@ -197,7 +197,7 @@ class IfNode : NodeType {
|
||||
while let current = token, current.contents.hasPrefix("elif") {
|
||||
var components = current.components()
|
||||
components.removeFirst()
|
||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
||||
let expression = try parseExpression(components: components, tokenParser: parser, token: current)
|
||||
|
||||
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
||||
token = parser.nextToken()
|
||||
@@ -227,16 +227,16 @@ class IfNode : NodeType {
|
||||
|
||||
falseNodes = try parser.parse(until(["endif", "else"]))
|
||||
|
||||
guard let token = parser.nextToken() else {
|
||||
if let token = parser.nextToken() {
|
||||
if token.contents == "else" {
|
||||
trueNodes = try parser.parse(until(["endif"]))
|
||||
_ = parser.nextToken()
|
||||
}
|
||||
} else {
|
||||
throw TemplateSyntaxError("`endif` was not found.")
|
||||
}
|
||||
|
||||
if token.contents == "else" {
|
||||
trueNodes = try parser.parse(until(["endif"]))
|
||||
_ = parser.nextToken()
|
||||
}
|
||||
|
||||
let expression = try parseExpression(components: components, tokenParser: parser)
|
||||
let expression = try parseExpression(components: components, tokenParser: parser, token: token)
|
||||
return IfNode(conditions: [
|
||||
IfCondition(expression: expression, nodes: trueNodes),
|
||||
IfCondition(expression: nil, nodes: falseNodes),
|
||||
|
||||
@@ -40,7 +40,7 @@ public class TokenParser {
|
||||
case .text(let text, _):
|
||||
nodes.append(TextNode(text: text))
|
||||
case .variable:
|
||||
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
|
||||
nodes.append(VariableNode(variable: try compileFilter(token.contents, containedIn: token)))
|
||||
case .block:
|
||||
if let parse_until = parse_until , parse_until(self, token) {
|
||||
prependToken(token)
|
||||
@@ -100,7 +100,23 @@ public class TokenParser {
|
||||
|
||||
throw TemplateSyntaxError("Unknown filter '\(name)'")
|
||||
}
|
||||
|
||||
|
||||
public func compileFilter(_ filterToken: String, containedIn containingToken: Token) throws -> Resolvable {
|
||||
do {
|
||||
return try FilterExpression(token: filterToken, parser: self)
|
||||
} catch {
|
||||
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil,
|
||||
let filterTokenRange = environment.template?.templateString.range(of: filterToken, range: containingToken.range) {
|
||||
|
||||
syntaxError.lexeme = Token.block(value: filterToken, at: filterTokenRange)
|
||||
throw syntaxError
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Use compileFilter(_:containedIn:)")
|
||||
public func compileFilter(_ token: String) throws -> Resolvable {
|
||||
return try FilterExpression(token: token, parser: self)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ class FilterExpression : Resolvable {
|
||||
init(token: String, parser: TokenParser) throws {
|
||||
let bits = token.characters.split(separator: "|").map({ String($0).trim(character: " ") })
|
||||
if bits.isEmpty {
|
||||
filters = []
|
||||
variable = Variable("")
|
||||
throw TemplateSyntaxError("Variable tags must include at least 1 argument")
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func testEnvironment() {
|
||||
return error
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on invalid for tag syntax") {
|
||||
$0.it("reports syntax error on invalid for tag syntax") {
|
||||
let template: Template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
|
||||
let error = expectedSyntaxError(
|
||||
token: "{% for name in %}",
|
||||
@@ -52,7 +52,7 @@ func testEnvironment() {
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context:["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on missing endfor") {
|
||||
$0.it("reports syntax error on missing endfor") {
|
||||
let template: Template = "{% for name in names %}{{ name }}"
|
||||
let error = expectedSyntaxError(
|
||||
token: "{% for name in names %}",
|
||||
@@ -62,7 +62,7 @@ func testEnvironment() {
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on unknown tag") {
|
||||
$0.it("reports syntax error on unknown tag") {
|
||||
let template: Template = "{% for name in names %}{{ name }}{% end %}"
|
||||
let error = expectedSyntaxError(
|
||||
token: "{% end %}",
|
||||
@@ -72,6 +72,58 @@ func testEnvironment() {
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.context("given unknown filter") {
|
||||
func expectedFilterError(token: String, template: Template) -> TemplateSyntaxError {
|
||||
return expectedSyntaxError(
|
||||
token: token,
|
||||
template: template,
|
||||
description: "Unknown filter 'unknown'"
|
||||
)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in for tag") {
|
||||
let template: Template = "{% for name in names|unknown %}{{ name }}{% endfor %}"
|
||||
let error = expectedFilterError(token: "names|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in for-where tag") {
|
||||
let template: Template = "{% for name in names where name|unknown %}{{ name }}{% endfor %}"
|
||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in if tag") {
|
||||
let template: Template = "{% if name|unknown %}{{ name }}{% endif %}"
|
||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in elif tag") {
|
||||
let template: Template = "{% if name %}{{ name }}{% elif name|unknown %}{% endif %}"
|
||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in ifnot tag") {
|
||||
let template: Template = "{% ifnot name|unknown %}{{ name }}{% endif %}"
|
||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in filter tag") {
|
||||
let template: Template = "{% filter unknown %}Text{% endfilter %}"
|
||||
let error = expectedFilterError(token: "{% filter unknown %}", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: [:])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("reports syntax error in variable tag") {
|
||||
let template: Template = "{{ name|unknown }}"
|
||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,19 +105,19 @@ func testExpressions() {
|
||||
|
||||
$0.describe("expression parsing") {
|
||||
$0.it("can parse a variable expression") {
|
||||
let expression = try parseExpression(components: ["value"], tokenParser: parser)
|
||||
let expression = try parseExpression(components: ["value"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
try expect(expression.evaluate(context: Context())).to.beFalse()
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beTrue()
|
||||
}
|
||||
|
||||
$0.it("can parse a not expression") {
|
||||
let expression = try parseExpression(components: ["not", "value"], tokenParser: parser)
|
||||
let expression = try parseExpression(components: ["not", "value"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
try expect(expression.evaluate(context: Context())).to.beTrue()
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["value": true]))).to.beFalse()
|
||||
}
|
||||
|
||||
$0.describe("and expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "and", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "and", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to false with lhs false") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": false, "rhs": true]))).to.beFalse()
|
||||
@@ -137,7 +137,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("or expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "or", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "or", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with lhs true") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": true, "rhs": false]))).to.beTrue()
|
||||
@@ -157,7 +157,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("equality expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "==", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "==", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with equal lhs/rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "a"]))).to.beTrue()
|
||||
@@ -193,7 +193,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("inequality expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "!=", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with inequal lhs/rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": "a", "rhs": "b"]))).to.beTrue()
|
||||
@@ -205,7 +205,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("more than expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", ">", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", ">", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with lhs > rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 4]))).to.beTrue()
|
||||
@@ -217,7 +217,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("more than equal expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", ">=", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with lhs == rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
||||
@@ -229,7 +229,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("less than expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "<", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "<", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with lhs < rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 4, "rhs": 4.5]))).to.beTrue()
|
||||
@@ -241,7 +241,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("less than equal expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "<=", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with lhs == rhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 5.0, "rhs": 5]))).to.beTrue()
|
||||
@@ -253,7 +253,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("multiple expression") {
|
||||
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["one", "or", "two", "and", "not", "three"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true with one") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["one": true]))).to.beTrue()
|
||||
@@ -281,7 +281,7 @@ func testExpressions() {
|
||||
}
|
||||
|
||||
$0.describe("in expression") {
|
||||
let expression = try! parseExpression(components: ["lhs", "in", "rhs"], tokenParser: parser)
|
||||
let expression = try! parseExpression(components: ["lhs", "in", "rhs"], tokenParser: parser, token: .text(value: "", at: .unknown))
|
||||
|
||||
$0.it("evaluates to true when rhs contains lhs") {
|
||||
try expect(expression.evaluate(context: Context(dictionary: ["lhs": 1, "rhs": [1, 2, 3]]))).to.beTrue()
|
||||
|
||||
@@ -91,7 +91,7 @@ func testForNode() {
|
||||
|
||||
$0.it("renders the given nodes while filtering items using where expression") {
|
||||
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
|
||||
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
||||
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
|
||||
let node = ForNode(resolvable: Variable("items"), loopVariables: ["item"], nodes: nodes, emptyNodes: [], where: `where`)
|
||||
try expect(try node.render(context)) == "2132"
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func testForNode() {
|
||||
$0.it("renders the given empty nodes when all items filtered out with where expression") {
|
||||
let nodes: [NodeType] = [VariableNode(variable: "item")]
|
||||
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
|
||||
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
||||
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()), token: .text(value: "", at: .unknown))
|
||||
let node = ForNode(resolvable: Variable("emptyItems"), loopVariables: ["item"], nodes: nodes, emptyNodes: emptyNodes, where: `where`)
|
||||
try expect(try node.render(context)) == "empty"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user