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

@@ -12,7 +12,7 @@ class ForNode : NodeType {
guard components.count >= 3 && components[2] == "in" &&
(components.count == 4 || (components.count >= 6 && components[4] == "where")) else {
throw TemplateSyntaxError("'for' statements should use the following 'for x in y where condition' `\(token.contents)`.")
throw TemplateSyntaxError("'for' statements should use the following syntax 'for x in y where condition'.")
}
let loopVariables = components[1].characters

View File

@@ -219,7 +219,7 @@ class IfNode : NodeType {
class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType {
var components = token.components()
guard components.count == 2 else {
throw TemplateSyntaxError("'ifnot' statements should use the following 'ifnot condition' `\(token.contents)`.")
throw TemplateSyntaxError("'ifnot' statements should use the following syntax 'ifnot condition'.")
}
components.removeFirst()
var trueNodes = [NodeType]()

View File

@@ -5,7 +5,7 @@ struct Lexer {
self.templateString = templateString
}
func createToken(string: String) -> Token {
func createToken(string: String, at range: Range<String.Index>) -> Token {
func strip() -> String {
guard string.characters.count > 4 else { return "" }
let start = string.index(string.startIndex, offsetBy: 2)
@@ -14,14 +14,14 @@ struct Lexer {
}
if string.hasPrefix("{{") {
return .variable(value: strip())
return .variable(value: strip(), at: range)
} else if string.hasPrefix("{%") {
return .block(value: strip())
return .block(value: strip(), at: range)
} else if string.hasPrefix("{#") {
return .comment(value: strip())
return .comment(value: strip(), at: range)
}
return .text(value: string)
return .text(value: string, at: range)
}
/// Returns an array of tokens from a given template string.
@@ -39,14 +39,14 @@ struct Lexer {
while !scanner.isEmpty {
if let text = scanner.scan(until: ["{{", "{%", "{#"]) {
if !text.1.isEmpty {
tokens.append(createToken(string: text.1))
tokens.append(createToken(string: text.1, at: scanner.range))
}
let end = map[text.0]!
let result = scanner.scan(until: end, returnUntil: true)
tokens.append(createToken(string: result))
tokens.append(createToken(string: result, at: scanner.range))
} else {
tokens.append(createToken(string: scanner.content))
tokens.append(createToken(string: scanner.content, at: scanner.range))
scanner.content = ""
}
}
@@ -57,41 +57,50 @@ struct Lexer {
class Scanner {
let _content: String //stores original content
var content: String
var range: Range<String.Index>
init(_ content: String) {
self._content = content
self.content = content
range = content.startIndex..<content.startIndex
}
var isEmpty: Bool {
return content.isEmpty
}
func scan(until: String, returnUntil: Bool = false) -> String {
var index = content.startIndex
if until.isEmpty {
return ""
}
var index = content.startIndex
range = range.upperBound..<range.upperBound
while index != content.endIndex {
let substring = content.substring(from: index)
if substring.hasPrefix(until) {
let result = content.substring(to: index)
content = substring
if returnUntil {
content = content.substring(from: until.endIndex)
range = range.lowerBound..<_content.index(range.upperBound, offsetBy: until.count)
content = substring.substring(from: until.endIndex)
return result + until
}
content = substring
return result
}
index = content.index(after: index)
range = range.lowerBound..<_content.index(after: range.upperBound)
}
content = ""
range = "".range
return ""
}
@@ -101,6 +110,7 @@ class Scanner {
}
var index = content.startIndex
range = range.upperBound..<range.upperBound
while index != content.endIndex {
let substring = content.substring(from: index)
for string in until {
@@ -112,6 +122,7 @@ class Scanner {
}
index = content.index(after: index)
range = range.lowerBound..<_content.index(after: range.upperBound)
}
return nil
@@ -151,4 +162,21 @@ extension String {
let last = findLastNot(character: character) ?? endIndex
return String(self[first..<last])
}
func lineAndPosition(at range: Range<String.Index>) -> (content: String, number: Int, offset: String.IndexDistance) {
var lineNumber: Int = 0
var offset = 0
var lineContent = ""
enumerateLines { (line, stop) in
lineNumber += 1
lineContent = line
if let rangeOfLine = self.range(of: line), rangeOfLine.contains(range.lowerBound) {
offset = self.distance(from: rangeOfLine.lowerBound, to: range.lowerBound)
stop = true
}
}
return (lineContent, lineNumber, offset)
}
}

View File

@@ -3,10 +3,22 @@ import Foundation
public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
public let description:String
public var token: Token?
public init(_ description:String) {
self.description = description
}
public func contextAwareError(templateName: String?, templateContent: String) -> TemplateSyntaxError? {
guard let token = token, token.range != .unknown else { return nil }
let templateName = templateName.map({ "\($0):" }) ?? ""
let (line, lineNumber, offset) = templateContent.lineAndPosition(at: token.range)
let tokenContent = templateContent.substring(with: token.range)
let highlight = "\(String(Array(repeating: " ", count: offset)))^\(String(Array(repeating: "~", count: max(tokenContent.count - 1, 0))))"
let description = "\(templateName)\(lineNumber):\(offset): error: " + self.description + "\n\(line)\n\(highlight)\n"
return TemplateSyntaxError(description)
}
}

View File

@@ -10,7 +10,7 @@ class NowNode : NodeType {
let components = token.components()
guard components.count <= 2 else {
throw TemplateSyntaxError("'now' tags may only have one argument: the format string `\(token.contents)`.")
throw TemplateSyntaxError("'now' tags may only have one argument: the format string.")
}
if components.count == 2 {
format = Variable(components[1])

View File

@@ -37,7 +37,7 @@ public class TokenParser {
let token = nextToken()!
switch token {
case .text(let text):
case .text(let text, _):
nodes.append(TextNode(text: text))
case .variable:
nodes.append(VariableNode(variable: try compileFilter(token.contents)))
@@ -48,8 +48,18 @@ public class TokenParser {
}
if let tag = token.components().first {
let parser = try findTag(name: tag)
nodes.append(try parser(self, token))
do {
let parser = try findTag(name: tag)
let node = try parser(self, token)
nodes.append(node)
} catch {
if var syntaxError = error as? TemplateSyntaxError, syntaxError.token == nil {
syntaxError.token = token
throw syntaxError
} else {
throw error
}
}
}
case .comment:
continue

View File

@@ -7,6 +7,7 @@ let NSFileNoSuchFileError = 4
/// A class representing a template
open class Template: ExpressibleByStringLiteral {
let templateString: String
let environment: Environment
let tokens: [Token]
@@ -17,6 +18,7 @@ open class Template: ExpressibleByStringLiteral {
public required init(templateString: String, environment: Environment? = nil, name: String? = nil) {
self.environment = environment ?? Environment()
self.name = name
self.templateString = templateString
let lexer = Lexer(templateString: templateString)
tokens = lexer.tokenize()
@@ -66,8 +68,17 @@ open class Template: ExpressibleByStringLiteral {
func render(_ context: Context) throws -> String {
let context = context
let parser = TokenParser(tokens: tokens, environment: context.environment)
let nodes = try parser.parse()
return try renderNodes(nodes, context)
do {
let nodes = try parser.parse()
return try renderNodes(nodes, context)
} catch {
if let syntaxError = error as? TemplateSyntaxError,
let error = syntaxError.contextAwareError(templateName: name, templateContent: templateString) {
throw error
} else {
throw error
}
}
}
/// Render the given template

View File

@@ -40,46 +40,62 @@ extension String {
}
}
extension Range where Bound == String.Index {
internal static var unknown: Range {
return "".range
}
}
extension String {
var range: Range<String.Index> {
return startIndex..<endIndex
}
}
public enum Token : Equatable {
/// A token representing a piece of text.
case text(value: String)
case text(value: String, at: Range<String.Index>)
/// A token representing a variable.
case variable(value: String)
case variable(value: String, at: Range<String.Index>)
/// A token representing a comment.
case comment(value: String)
case comment(value: String, at: Range<String.Index>)
/// A token representing a template block.
case block(value: String)
case block(value: String, at: Range<String.Index>)
/// Returns the underlying value as an array seperated by spaces
public func components() -> [String] {
switch self {
case .block(let value):
return value.smartSplit()
case .variable(let value):
return value.smartSplit()
case .text(let value):
return value.smartSplit()
case .comment(let value):
case .block(let value, _),
.variable(let value, _),
.text(let value, _),
.comment(let value, _):
return value.smartSplit()
}
}
public var contents: String {
switch self {
case .block(let value):
return value
case .variable(let value):
return value
case .text(let value):
return value
case .comment(let value):
case .block(let value, _),
.variable(let value, _),
.text(let value, _),
.comment(let value, _):
return value
}
}
public var range: Range<String.Index> {
switch self {
case .block(_, let range),
.variable(_, let range),
.text(_, let range),
.comment(_, let range):
return range
}
}
}

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