improved template syntax errors with file, line number and failed token highlighted in error message

This commit is contained in:
Ilya Puchka
2017-10-03 22:47:28 +02:00
parent 2e80f70f67
commit 6300dbc7bf
17 changed files with 220 additions and 190 deletions

View File

@@ -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()
}

View File

@@ -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"
}
}
}

View File

@@ -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())

View File

@@ -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()

View File

@@ -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") {

View File

@@ -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

View File

@@ -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'"))

View File

@@ -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)
}
}
}

View File

@@ -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