improved template syntax errors with file, line number and failed token highlighted in error message
This commit is contained in:
@@ -17,7 +17,7 @@ func testFilterTag() {
|
||||
}
|
||||
|
||||
$0.it("errors without a filter") {
|
||||
let template = Template(templateString: "{% filter %}Test{% endfilter %}")
|
||||
let template = Template(templateString: "Some {% filter %}Test{% endfilter %}")
|
||||
try expect(try template.render()).toThrow()
|
||||
}
|
||||
|
||||
|
||||
@@ -170,88 +170,13 @@ func testForNode() {
|
||||
|
||||
$0.it("handles invalid input") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "for i"),
|
||||
.block(value: "for i", at: .unknown),
|
||||
]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
let error = TemplateSyntaxError("'for' statements should use the following 'for x in y where condition' `for i`.")
|
||||
let error = TemplateSyntaxError("'for' statements should use the following syntax 'for x in y where condition'.")
|
||||
try expect(try parser.parse()).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("can iterate over struct properties") {
|
||||
struct MyStruct {
|
||||
let string: String
|
||||
let number: Int
|
||||
}
|
||||
|
||||
let context = Context(dictionary: [
|
||||
"struct": MyStruct(string: "abc", number: 123)
|
||||
])
|
||||
|
||||
let nodes: [NodeType] = [
|
||||
VariableNode(variable: "property"),
|
||||
TextNode(text: "="),
|
||||
VariableNode(variable: "value"),
|
||||
TextNode(text: "\n"),
|
||||
]
|
||||
let node = ForNode(resolvable: Variable("struct"), loopVariables: ["property", "value"], nodes: nodes, emptyNodes: [])
|
||||
let result = try node.render(context)
|
||||
|
||||
try expect(result) == "string=abc\nnumber=123\n"
|
||||
}
|
||||
|
||||
$0.it("can iterate tuple items") {
|
||||
let context = Context(dictionary: [
|
||||
"tuple": (one: 1, two: "dva"),
|
||||
])
|
||||
|
||||
let nodes: [NodeType] = [
|
||||
VariableNode(variable: "label"),
|
||||
TextNode(text: "="),
|
||||
VariableNode(variable: "value"),
|
||||
TextNode(text: "\n"),
|
||||
]
|
||||
|
||||
let node = ForNode(resolvable: Variable("tuple"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
|
||||
let result = try node.render(context)
|
||||
|
||||
try expect(result) == "one=1\ntwo=dva\n"
|
||||
}
|
||||
|
||||
$0.it("can iterate over class properties") {
|
||||
class MyClass {
|
||||
var baseString: String
|
||||
var baseInt: Int
|
||||
init(_ string: String, _ int: Int) {
|
||||
baseString = string
|
||||
baseInt = int
|
||||
}
|
||||
}
|
||||
|
||||
class MySubclass: MyClass {
|
||||
var childString: String
|
||||
init(_ childString: String, _ string: String, _ int: Int) {
|
||||
self.childString = childString
|
||||
super.init(string, int)
|
||||
}
|
||||
}
|
||||
|
||||
let context = Context(dictionary: [
|
||||
"class": MySubclass("child", "base", 1)
|
||||
])
|
||||
|
||||
let nodes: [NodeType] = [
|
||||
VariableNode(variable: "label"),
|
||||
TextNode(text: "="),
|
||||
VariableNode(variable: "value"),
|
||||
TextNode(text: "\n"),
|
||||
]
|
||||
|
||||
let node = ForNode(resolvable: Variable("class"), loopVariables: ["label", "value"], nodes: nodes, emptyNodes: [])
|
||||
let result = try node.render(context)
|
||||
|
||||
try expect(result) == "childString=child\nbaseString=base\nbaseInt=1\n"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ func testIfNode() {
|
||||
$0.describe("parsing") {
|
||||
$0.it("can parse an if block") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -25,11 +25,11 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an if with else block") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "else"),
|
||||
.text(value: "false"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "else", at: .unknown),
|
||||
.text(value: "false", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -50,13 +50,13 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an if with elif block") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "elif something"),
|
||||
.text(value: "some"),
|
||||
.block(value: "else"),
|
||||
.text(value: "false"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "elif something", at: .unknown),
|
||||
.text(value: "some", at: .unknown),
|
||||
.block(value: "else", at: .unknown),
|
||||
.text(value: "false", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -81,11 +81,11 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an if with elif block without else") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "elif something"),
|
||||
.text(value: "some"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "elif something", at: .unknown),
|
||||
.text(value: "some", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -106,15 +106,15 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an if with multiple elif block") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "elif something1"),
|
||||
.text(value: "some1"),
|
||||
.block(value: "elif something2"),
|
||||
.text(value: "some2"),
|
||||
.block(value: "else"),
|
||||
.text(value: "false"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "elif something1", at: .unknown),
|
||||
.text(value: "some1", at: .unknown),
|
||||
.block(value: "elif something2", at: .unknown),
|
||||
.text(value: "some2", at: .unknown),
|
||||
.block(value: "else", at: .unknown),
|
||||
.text(value: "false", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -144,9 +144,9 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an if with complex expression") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value == \"test\" and not name"),
|
||||
.text(value: "true"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value == \"test\" and not name", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -156,11 +156,11 @@ func testIfNode() {
|
||||
|
||||
$0.it("can parse an ifnot block") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "ifnot value"),
|
||||
.text(value: "false"),
|
||||
.block(value: "else"),
|
||||
.text(value: "true"),
|
||||
.block(value: "endif")
|
||||
.block(value: "ifnot value", at: .unknown),
|
||||
.text(value: "false", at: .unknown),
|
||||
.block(value: "else", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -180,7 +180,7 @@ func testIfNode() {
|
||||
|
||||
$0.it("throws an error when parsing an if block without an endif") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value"),
|
||||
.block(value: "if value", at: .unknown),
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -190,7 +190,7 @@ func testIfNode() {
|
||||
|
||||
$0.it("throws an error when parsing an ifnot without an endif") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "ifnot value"),
|
||||
.block(value: "ifnot value", at: .unknown),
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -242,9 +242,9 @@ func testIfNode() {
|
||||
|
||||
$0.it("supports variable filters in the if expression") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if value|uppercase == \"TEST\""),
|
||||
.text(value: "true"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if value|uppercase == \"TEST\"", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
@@ -256,9 +256,9 @@ func testIfNode() {
|
||||
|
||||
$0.it("evaluates nil properties as false") {
|
||||
let tokens: [Token] = [
|
||||
.block(value: "if instance.value"),
|
||||
.text(value: "true"),
|
||||
.block(value: "endif")
|
||||
.block(value: "if instance.value", at: .unknown),
|
||||
.text(value: "true", at: .unknown),
|
||||
.block(value: "endif", at: .unknown)
|
||||
]
|
||||
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
@@ -11,7 +11,7 @@ func testInclude() {
|
||||
|
||||
$0.describe("parsing") {
|
||||
$0.it("throws an error when no template is given") {
|
||||
let tokens: [Token] = [ .block(value: "include") ]
|
||||
let tokens: [Token] = [ .block(value: "include", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
let error = TemplateSyntaxError("'include' tag takes one argument, the template file to be included")
|
||||
@@ -19,7 +19,7 @@ func testInclude() {
|
||||
}
|
||||
|
||||
$0.it("can parse a valid include block") {
|
||||
let tokens: [Token] = [ .block(value: "include \"test.html\"") ]
|
||||
let tokens: [Token] = [ .block(value: "include \"test.html\"", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
|
||||
@@ -9,7 +9,7 @@ func testLexer() {
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 1
|
||||
try expect(tokens.first) == .text(value: "Hello World")
|
||||
try expect(tokens.first) == .text(value: "Hello World", at: "Hello World".range)
|
||||
}
|
||||
|
||||
$0.it("can tokenize a comment") {
|
||||
@@ -17,7 +17,7 @@ func testLexer() {
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 1
|
||||
try expect(tokens.first) == .comment(value: "Comment")
|
||||
try expect(tokens.first) == .comment(value: "Comment", at: "{# Comment #}".range)
|
||||
}
|
||||
|
||||
$0.it("can tokenize a variable") {
|
||||
@@ -25,34 +25,37 @@ func testLexer() {
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 1
|
||||
try expect(tokens.first) == .variable(value: "Variable")
|
||||
try expect(tokens.first) == .variable(value: "Variable", at: "{{ Variable }}".range)
|
||||
}
|
||||
|
||||
$0.it("can tokenize unclosed tag by ignoring it") {
|
||||
let lexer = Lexer(templateString: "{{ thing")
|
||||
let templateString = "{{ thing"
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 1
|
||||
try expect(tokens.first) == .text(value: "")
|
||||
try expect(tokens.first) == .text(value: "", at: "".range)
|
||||
}
|
||||
|
||||
$0.it("can tokenize a mixture of content") {
|
||||
let lexer = Lexer(templateString: "My name is {{ name }}.")
|
||||
let templateString = "My name is {{ name }}."
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 3
|
||||
try expect(tokens[0]) == Token.text(value: "My name is ")
|
||||
try expect(tokens[1]) == Token.variable(value: "name")
|
||||
try expect(tokens[2]) == Token.text(value: ".")
|
||||
try expect(tokens[0]) == Token.text(value: "My name is ", at: templateString.range(of: "My name is ")!)
|
||||
try expect(tokens[1]) == Token.variable(value: "name", at: templateString.range(of: "{{ name }}")!)
|
||||
try expect(tokens[2]) == Token.text(value: ".", at: templateString.range(of: ".")!)
|
||||
}
|
||||
|
||||
$0.it("can tokenize two variables without being greedy") {
|
||||
let lexer = Lexer(templateString: "{{ thing }}{{ name }}")
|
||||
let templateString = "{{ thing }}{{ name }}"
|
||||
let lexer = Lexer(templateString: templateString)
|
||||
let tokens = lexer.tokenize()
|
||||
|
||||
try expect(tokens.count) == 2
|
||||
try expect(tokens[0]) == Token.variable(value: "thing")
|
||||
try expect(tokens[1]) == Token.variable(value: "name")
|
||||
try expect(tokens[0]) == Token.variable(value: "thing", at: templateString.range(of: "{{ thing }}")!)
|
||||
try expect(tokens[1]) == Token.variable(value: "name", at: templateString.range(of: "{{ name }}")!)
|
||||
}
|
||||
|
||||
$0.it("can tokenize an unclosed block") {
|
||||
|
||||
@@ -8,7 +8,7 @@ func testNowNode() {
|
||||
describe("NowNode") {
|
||||
$0.describe("parsing") {
|
||||
$0.it("parses default format without any now arguments") {
|
||||
let tokens: [Token] = [ .block(value: "now") ]
|
||||
let tokens: [Token] = [ .block(value: "now", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
@@ -18,7 +18,7 @@ func testNowNode() {
|
||||
}
|
||||
|
||||
$0.it("parses now with a format") {
|
||||
let tokens: [Token] = [ .block(value: "now \"HH:mm\"") ]
|
||||
let tokens: [Token] = [ .block(value: "now \"HH:mm\"", at: .unknown) ]
|
||||
let parser = TokenParser(tokens: tokens, environment: Environment())
|
||||
let nodes = try parser.parse()
|
||||
let node = nodes.first as? NowNode
|
||||
|
||||
@@ -6,7 +6,7 @@ func testTokenParser() {
|
||||
describe("TokenParser") {
|
||||
$0.it("can parse a text token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
.text(value: "Hello World")
|
||||
.text(value: "Hello World", at: .unknown)
|
||||
], environment: Environment())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
@@ -18,7 +18,7 @@ func testTokenParser() {
|
||||
|
||||
$0.it("can parse a variable token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
.variable(value: "'name'")
|
||||
.variable(value: "'name'", at: .unknown)
|
||||
], environment: Environment())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
@@ -30,7 +30,7 @@ func testTokenParser() {
|
||||
|
||||
$0.it("can parse a comment token") {
|
||||
let parser = TokenParser(tokens: [
|
||||
.comment(value: "Secret stuff!")
|
||||
.comment(value: "Secret stuff!", at: .unknown)
|
||||
], environment: Environment())
|
||||
|
||||
let nodes = try parser.parse()
|
||||
@@ -44,7 +44,7 @@ func testTokenParser() {
|
||||
}
|
||||
|
||||
let parser = TokenParser(tokens: [
|
||||
.block(value: "known"),
|
||||
.block(value: "known", at: .unknown),
|
||||
], environment: Environment(extensions: [simpleExtension]))
|
||||
|
||||
let nodes = try parser.parse()
|
||||
@@ -53,7 +53,7 @@ func testTokenParser() {
|
||||
|
||||
$0.it("errors when parsing an unknown tag") {
|
||||
let parser = TokenParser(tokens: [
|
||||
.block(value: "unknown"),
|
||||
.block(value: "unknown", at: .unknown),
|
||||
], environment: Environment())
|
||||
|
||||
try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
func testTemplate() {
|
||||
@@ -15,5 +15,30 @@ func testTemplate() {
|
||||
let result = try template.render([ "name": "Kyle" ])
|
||||
try expect(result) == "Hello World"
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on invalid for tag syntax") {
|
||||
let template: Template = "Hello {% for name in %}{{ name }}, {% endfor %}!"
|
||||
var error = TemplateSyntaxError("'for' statements should use the following syntax 'for x in y where condition'.")
|
||||
error.token = Token.block(value: "{% for name in %}", at: template.templateString.range(of: "{% for name in %}")!)
|
||||
error = error.contextAwareError(templateName: nil, templateContent: template.templateString)!
|
||||
try expect(try template.render(["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on missing endfor") {
|
||||
let template: Template = "{% for name in names %}{{ name }}"
|
||||
var error = TemplateSyntaxError("`endfor` was not found.")
|
||||
error.token = Token.block(value: "{% for name in names %}", at: template.templateString.range(of: "{% for name in names %}")!)
|
||||
error = error.contextAwareError(templateName: nil, templateContent: template.templateString)!
|
||||
try expect(try template.render(["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
$0.it("throws syntax error on unknown tag") {
|
||||
let template: Template = "{% for name in names %}{{ name }}{% end %}"
|
||||
var error = TemplateSyntaxError("Unknown template tag 'end'")
|
||||
error.token = Token.block(value: "{% end %}", at: template.templateString.range(of: "{% end %}")!)
|
||||
error = error.contextAwareError(templateName: nil, templateContent: template.templateString)!
|
||||
try expect(try template.render(["names": ["Bob", "Alice"]])).toThrow(error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Spectre
|
||||
import Stencil
|
||||
@testable import Stencil
|
||||
|
||||
|
||||
func testToken() {
|
||||
describe("Token") {
|
||||
$0.it("can split the contents into components") {
|
||||
let token = Token.text(value: "hello world")
|
||||
let token = Token.text(value: "hello world", at: .unknown)
|
||||
let components = token.components()
|
||||
|
||||
try expect(components.count) == 2
|
||||
@@ -14,7 +14,7 @@ func testToken() {
|
||||
}
|
||||
|
||||
$0.it("can split the contents into components with single quoted strings") {
|
||||
let token = Token.text(value: "hello 'kyle fuller'")
|
||||
let token = Token.text(value: "hello 'kyle fuller'", at: .unknown)
|
||||
let components = token.components()
|
||||
|
||||
try expect(components.count) == 2
|
||||
@@ -23,7 +23,7 @@ func testToken() {
|
||||
}
|
||||
|
||||
$0.it("can split the contents into components with double quoted strings") {
|
||||
let token = Token.text(value: "hello \"kyle fuller\"")
|
||||
let token = Token.text(value: "hello \"kyle fuller\"", at: .unknown)
|
||||
let components = token.components()
|
||||
|
||||
try expect(components.count) == 2
|
||||
|
||||
Reference in New Issue
Block a user