From ac2fd56e8e8e52ec47eccce56bf8638f6ff2e31c Mon Sep 17 00:00:00 2001 From: Ilya Puchka Date: Wed, 27 Dec 2017 02:31:47 +0100 Subject: [PATCH] storing full sourcemap in token, refactored error reporting --- Sources/Environment.swift | 25 +------- Sources/Errors.swift | 73 ++++++------------------ Sources/Include.swift | 10 ++-- Sources/Inheritence.swift | 56 +++++++++--------- Sources/Lexer.swift | 32 +++++++++-- Sources/Parser.swift | 8 ++- Sources/Template.swift | 2 +- Sources/Tokenizer.swift | 54 ++++++++++++------ Tests/StencilTests/EnvironmentSpec.swift | 26 ++++----- Tests/StencilTests/FilterSpec.swift | 4 +- Tests/StencilTests/ForNodeSpec.swift | 6 +- Tests/StencilTests/IfNodeSpec.swift | 12 ++-- Tests/StencilTests/IncludeSpec.swift | 2 +- Tests/StencilTests/LexerSpec.swift | 20 +++---- Tests/StencilTests/ParserSpec.swift | 7 +-- 15 files changed, 149 insertions(+), 188 deletions(-) diff --git a/Sources/Environment.swift b/Sources/Environment.swift index ce2d2c5..5fb9e19 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -45,30 +45,7 @@ public struct Environment { func render(template: Template, context: [String: Any]?) throws -> String { // update temaplte environment as it cen be created from string literal with default environment template.environment = self - errorReporter.context = ErrorReporterContext(template: template) - do { - return try template.render(context) - } catch { - throw errorReporter.reportError(error) - } - } - - var template: Template? { - return errorReporter.context?.template - } - - public func pushTemplate(_ template: Template, token: Token?, closure: (() throws -> Result)) rethrows -> Result { - let errorReporterContext = errorReporter.context - defer { errorReporter.context = errorReporterContext } - errorReporter.context = ErrorReporterContext( - template: template, - parent: errorReporterContext != nil ? (errorReporterContext!, token) : nil - ) - do { - return try closure() - } catch { - throw errorReporter.reportError(error) - } + return try template.render(context) } } diff --git a/Sources/Errors.swift b/Sources/Errors.swift index d13a0ec..4b7cafe 100644 --- a/Sources/Errors.swift +++ b/Sources/Errors.swift @@ -22,13 +22,15 @@ public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible { public let reason: String public var description: String { return reason } public internal(set) var token: Token? - public internal(set) var template: Template? - public internal(set) var parentError: Error? + public internal(set) var stackTrace: [Token] + public var templateName: String? { return token?.sourceMap.filename } + var allTokens: [Token] { + return stackTrace + (token.map({ [$0] }) ?? []) + } - public init(reason: String, token: Token? = nil, template: Template? = nil, parentError: Error? = nil) { + public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) { self.reason = reason - self.parentError = parentError - self.template = template + self.stackTrace = stackTrace self.token = token } @@ -37,77 +39,34 @@ public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible { } public static func ==(lhs:TemplateSyntaxError, rhs:TemplateSyntaxError) -> Bool { - guard lhs.description == rhs.description else { return false } - - switch (lhs.parentError, rhs.parentError) { - case let (lhsParent? as TemplateSyntaxError?, rhsParent? as TemplateSyntaxError?): - return lhsParent == rhsParent - case let (lhsParent?, rhsParent?): - return String(describing: lhsParent) == String(describing: rhsParent) - default: - return lhs.parentError == nil && rhs.parentError == nil - } + return lhs.description == rhs.description && lhs.token == rhs.token && lhs.stackTrace == rhs.stackTrace } } -public class ErrorReporterContext { - public let template: Template - - public typealias ParentContext = (context: ErrorReporterContext, token: Token?) - public let parent: ParentContext? - - public init(template: Template, parent: ParentContext? = nil) { - self.template = template - self.parent = parent - } -} - public protocol ErrorReporter: class { - var context: ErrorReporterContext! { get set } - func reportError(_ error: Error) -> Error func renderError(_ error: Error) -> String } open class SimpleErrorReporter: ErrorReporter { - public var context: ErrorReporterContext! - - open func reportError(_ error: Error) -> Error { - guard let context = context else { return error } - - return TemplateSyntaxError(reason: (error as? TemplateSyntaxError)?.reason ?? "\(error)", - token: (error as? TemplateSyntaxError)?.token, - template: (error as? TemplateSyntaxError)?.template ?? context.template, - parentError: (error as? TemplateSyntaxError)?.parentError - ) - } open func renderError(_ error: Error) -> String { guard let templateError = error as? TemplateSyntaxError else { return error.localizedDescription } - let description: String - if let template = templateError.template, let token = templateError.token { - let templateName = template.name.map({ "\($0):" }) ?? "" - let range = template.templateString.range(of: token.contents, range: token.range) ?? token.range - let line = template.templateString.rangeLine(range) + func describe(token: Token) -> String { + let templateName = token.sourceMap.filename ?? "" + let line = token.sourceMap.line let highlight = "\(String(Array(repeating: " ", count: line.offset)))^\(String(Array(repeating: "~", count: max(token.contents.characters.count - 1, 0))))" - description = "\(templateName)\(line.number):\(line.offset): error: \(templateError.reason)\n" + return "\(templateName)\(line.number):\(line.offset): error: \(templateError.reason)\n" + "\(line.content)\n" + "\(highlight)\n" - } else { - description = templateError.reason } - var descriptions = [description] - - var currentError: TemplateSyntaxError? = templateError - while let parentError = currentError?.parentError { - descriptions.append(renderError(parentError)) - currentError = parentError as? TemplateSyntaxError - } - - return descriptions.reversed().joined(separator: "\n") + var descriptions = templateError.stackTrace.reduce([]) { $0 + [describe(token: $1)] } + let description = templateError.token.map(describe(token:)) ?? templateError.reason + descriptions.append(description) + return descriptions.joined(separator: "\n") } } diff --git a/Sources/Include.swift b/Sources/Include.swift index 58bde91..8ebec9f 100644 --- a/Sources/Include.swift +++ b/Sources/Include.swift @@ -28,14 +28,12 @@ class IncludeNode : NodeType { let template = try context.environment.loadTemplate(name: templateName) do { - return try context.environment.pushTemplate(template, token: token) { - try context.push { - return try template.render(context) - } + return try context.push { + return try template.render(context) } } catch { - if let parentError = error as? TemplateSyntaxError { - throw TemplateSyntaxError(reason: parentError.reason, parentError: parentError) + if let error = error as? TemplateSyntaxError { + throw TemplateSyntaxError(reason: error.reason, stackTrace: error.allTokens) } else { throw error } diff --git a/Sources/Inheritence.swift b/Sources/Inheritence.swift index dba09cf..8cb7db2 100644 --- a/Sources/Inheritence.swift +++ b/Sources/Inheritence.swift @@ -2,22 +2,22 @@ class BlockContext { class var contextKey: String { return "block_context" } // contains mapping of block names to their nodes and templates where they are defined - var blocks: [String: [(BlockNode, Template?)]] + var blocks: [String: [BlockNode]] - init(blocks: [String: [(BlockNode, Template?)]]) { + init(blocks: [String: [BlockNode]]) { self.blocks = blocks } - func pushBlock(_ block: BlockNode, named blockName: String, definedIn template: Template?) { + func pushBlock(_ block: BlockNode, named blockName: String) { if var blocks = blocks[blockName] { - blocks.append((block, template)) + blocks.append(block) self.blocks[blockName] = blocks } else { - self.blocks[blockName] = [(block, template)] + self.blocks[blockName] = [block] } } - func popBlock(named blockName: String) -> (node: BlockNode, template: Template?)? { + func popBlock(named blockName: String) -> BlockNode? { if var blocks = blocks[blockName] { let block = blocks.removeFirst() if blocks.isEmpty { @@ -86,34 +86,31 @@ class ExtendsNode : NodeType { } let baseTemplate = try context.environment.loadTemplate(name: templateName) - let template = context.environment.template let blockContext: BlockContext if let _blockContext = context[BlockContext.contextKey] as? BlockContext { blockContext = _blockContext for (name, block) in blocks { - blockContext.pushBlock(block, named: name, definedIn: template) + blockContext.pushBlock(block, named: name) } } else { - var blocks = [String: [(BlockNode, Template?)]]() - self.blocks.forEach { blocks[$0.key] = [($0.value, template)] } + var blocks = [String: [BlockNode]]() + self.blocks.forEach { blocks[$0.key] = [$0.value] } blockContext = BlockContext(blocks: blocks) } do { // pushes base template and renders it's content // block_context contains all blocks from child templates - return try context.environment.pushTemplate(baseTemplate, token: token) { - try context.push(dictionary: [BlockContext.contextKey: blockContext]) { - return try baseTemplate.render(context) - } + return try context.push(dictionary: [BlockContext.contextKey: blockContext]) { + return try baseTemplate.render(context) } } catch { // if error template is already set (see catch in BlockNode) // and it happend in the same template as current template // there is no need to wrap it in another error - if let error = error as? TemplateSyntaxError, error.template !== context.environment.template { - throw TemplateSyntaxError(reason: error.reason, parentError: error) + if let error = error as? TemplateSyntaxError, error.templateName != token?.sourceMap.filename { + throw TemplateSyntaxError(reason: error.reason, stackTrace: error.allTokens) } else { throw error } @@ -152,7 +149,7 @@ class BlockNode : NodeType { var newContext: [String: Any] = [BlockContext.contextKey: blockContext] - if let blockSuperNode = child.node.nodes.first(where: { + if let blockSuperNode = child.nodes.first(where: { if case .variable(let variable, _)? = $0.token, variable == "block.super" { return true } else { return false} }) { @@ -160,27 +157,28 @@ class BlockNode : NodeType { // render current (base) node so that its content can be used as part of node that extends it newContext["block"] = ["super": try self.render(context)] } catch { - let baseError = context.errorReporter.reportError(error) - throw TemplateSyntaxError( - reason: (baseError as? TemplateSyntaxError)?.reason ?? "\(baseError)", - token: blockSuperNode.token, - template: child.template, - parentError: baseError) + if let error = error as? TemplateSyntaxError { + throw TemplateSyntaxError( + reason: error.reason, + token: blockSuperNode.token, + stackTrace: error.allTokens) + } else { + throw TemplateSyntaxError( + reason: "\(error)", + token: blockSuperNode.token, + stackTrace: []) + } } } // render extension node do { return try context.push(dictionary: newContext) { - return try child.node.render(context) + return try child.render(context) } } catch { - // child node belongs to child template, which is currently not on top of stack - // so we need to use node's template to report errors, not current template - // unless it's already set if var error = error as? TemplateSyntaxError { - error.template = error.template ?? child.template - error.token = error.token ?? child.node.token + error.token = error.token ?? child.token throw error } else { throw error diff --git a/Sources/Lexer.swift b/Sources/Lexer.swift index c6cc15c..caebac4 100644 --- a/Sources/Lexer.swift +++ b/Sources/Lexer.swift @@ -1,9 +1,11 @@ import Foundation struct Lexer { + let templateName: String? let templateString: String - init(templateString: String) { + init(templateName: String? = nil, templateString: String) { + self.templateName = templateName self.templateString = templateString } @@ -16,14 +18,28 @@ struct Lexer { } if string.hasPrefix("{{") { - return .variable(value: strip(), at: range) + let value = strip() + let range = templateString.range(of: value, range: range) ?? range + let line = templateString.rangeLine(range) + let sourceMap = SourceMap(filename: templateName, line: line) + return .variable(value: value, at: sourceMap) } else if string.hasPrefix("{%") { - return .block(value: strip(), at: range) + let value = strip() + let range = templateString.range(of: value, range: range) ?? range + let line = templateString.rangeLine(range) + let sourceMap = SourceMap(filename: templateName, line: line) + return .block(value: value, at: sourceMap) } else if string.hasPrefix("{#") { - return .comment(value: strip(), at: range) + let value = strip() + let range = templateString.range(of: value, range: range) ?? range + let line = templateString.rangeLine(range) + let sourceMap = SourceMap(filename: templateName, line: line) + return .comment(value: value, at: sourceMap) } - return .text(value: string, at: range) + let line = templateString.rangeLine(range) + let sourceMap = SourceMap(filename: templateName, line: line) + return .text(value: string, at: sourceMap) } /// Returns an array of tokens from a given template string. @@ -41,6 +57,7 @@ struct Lexer { while !scanner.isEmpty { if let text = scanner.scan(until: ["{{", "{%", "{#"]) { if !text.1.isEmpty { + let line = templateString.rangeLine(scanner.range) tokens.append(createToken(string: text.1, at: scanner.range)) } @@ -48,6 +65,7 @@ struct Lexer { let result = scanner.scan(until: end, returnUntil: true) tokens.append(createToken(string: result, at: scanner.range)) } else { + let line = templateString.rangeLine(scanner.range) tokens.append(createToken(string: scanner.content, at: scanner.range)) scanner.content = "" } @@ -165,7 +183,7 @@ extension String { return String(self[first..) -> (content: String, number: UInt, offset: String.IndexDistance) { + public func rangeLine(_ range: Range) -> RangeLine { var lineNumber: UInt = 0 var offset: Int = 0 var lineContent = "" @@ -183,3 +201,5 @@ extension String { return (lineContent, lineNumber, offset) } } + +public typealias RangeLine = (content: String, number: UInt, offset: String.IndexDistance) diff --git a/Sources/Parser.swift b/Sources/Parser.swift index e2a583c..40bdab8 100644 --- a/Sources/Parser.swift +++ b/Sources/Parser.swift @@ -107,9 +107,11 @@ public class TokenParser { return try FilterExpression(token: filterToken, parser: self) } catch { if var error = error as? TemplateSyntaxError, error.token == nil { - // find range of filter in the containing token so that only filter is highligted, not the whole token - if let filterTokenRange = environment.template?.templateString.range(of: filterToken, range: containingToken.range) { - error.token = Token.variable(value: filterToken, at: filterTokenRange) + // find offset of filter in the containing token so that only filter is highligted, not the whole token + if let filterTokenRange = containingToken.contents.range(of: filterToken) { + var rangeLine = containingToken.sourceMap.line + rangeLine.offset += containingToken.contents.distance(from: containingToken.contents.startIndex, to: filterTokenRange.lowerBound) + error.token = .variable(value: filterToken, at: SourceMap(filename: containingToken.sourceMap.filename, line: rangeLine)) } else { error.token = containingToken } diff --git a/Sources/Template.swift b/Sources/Template.swift index 03176b2..0bf1c78 100644 --- a/Sources/Template.swift +++ b/Sources/Template.swift @@ -20,7 +20,7 @@ open class Template: ExpressibleByStringLiteral { self.name = name self.templateString = templateString - let lexer = Lexer(templateString: templateString) + let lexer = Lexer(templateName: name, templateString: templateString) tokens = lexer.tokenize() } diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 55482ed..7f23f0e 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -40,18 +40,34 @@ extension String { } } +public struct SourceMap: Equatable { + public let filename: String? + public let line: RangeLine + + init(filename: String? = nil, line: RangeLine = ("", 0, 0)) { + self.filename = filename + self.line = line + } + + static let unknown = SourceMap() + + public static func ==(lhs: SourceMap, rhs: SourceMap) -> Bool { + return lhs.filename == rhs.filename && lhs.line == rhs.line + } +} + public enum Token : Equatable { /// A token representing a piece of text. - case text(value: String, at: Range) + case text(value: String, at: SourceMap) /// A token representing a variable. - case variable(value: String, at: Range) + case variable(value: String, at: SourceMap) /// A token representing a comment. - case comment(value: String, at: Range) + case comment(value: String, at: SourceMap) /// A token representing a template block. - case block(value: String, at: Range) + case block(value: String, at: SourceMap) /// Returns the underlying value as an array seperated by spaces public func components() -> [String] { @@ -74,29 +90,29 @@ public enum Token : Equatable { } } - public var range: Range { + public var sourceMap: SourceMap { switch self { - case .block(_, let range), - .variable(_, let range), - .text(_, let range), - .comment(_, let range): - return range + case .block(_, let sourceMap), + .variable(_, let sourceMap), + .text(_, let sourceMap), + .comment(_, let sourceMap): + return sourceMap } } - + } public func == (lhs: Token, rhs: Token) -> Bool { switch (lhs, rhs) { - case (.text(let lhsValue), .text(let rhsValue)): - return lhsValue == rhsValue - case (.variable(let lhsValue), .variable(let rhsValue)): - return lhsValue == rhsValue - case (.block(let lhsValue), .block(let rhsValue)): - return lhsValue == rhsValue - case (.comment(let lhsValue), .comment(let rhsValue)): - return lhsValue == rhsValue + case let (.text(lhsValue, lhsAt), .text(rhsValue, rhsAt)): + return lhsValue == rhsValue && lhsAt == rhsAt + case let (.variable(lhsValue, lhsAt), .variable(rhsValue, rhsAt)): + return lhsValue == rhsValue && lhsAt == rhsAt + case let (.block(lhsValue, lhsAt), .block(rhsValue, rhsAt)): + return lhsValue == rhsValue && lhsAt == rhsAt + case let (.comment(lhsValue, lhsAt), .comment(rhsValue, rhsAt)): + return lhsValue == rhsValue && lhsAt == rhsAt default: return false } diff --git a/Tests/StencilTests/EnvironmentSpec.swift b/Tests/StencilTests/EnvironmentSpec.swift index 24f2136..ca097e1 100644 --- a/Tests/StencilTests/EnvironmentSpec.swift +++ b/Tests/StencilTests/EnvironmentSpec.swift @@ -41,15 +41,17 @@ func testEnvironment() { } func expectedSyntaxError(token: String, template: Template, description: String) -> TemplateSyntaxError { - let token = Token.block(value: token, at: template.templateString.range(of: token)!) - return TemplateSyntaxError(reason: description, token: token, template: template, parentError: nil) + let range = template.templateString.range(of: token)! + let rangeLine = template.templateString.rangeLine(range) + let sourceMap = SourceMap(filename: template.name, line: rangeLine) + let token = Token.block(value: token, at: sourceMap) + return TemplateSyntaxError(reason: description, token: token, stackTrace: []) } func expectError(reason: String, token: String) throws { let expectedError = expectedSyntaxError(token: token, template: template, description: reason) - let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"])) - .toThrow(expectedError) + let error = try expect(environment.render(template: template, context: ["names": ["Bob", "Alice"], "name": "Bob"])).toThrow() as TemplateSyntaxError try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError) } @@ -200,10 +202,10 @@ func testEnvironment() { func expectError(reason: String, token: String, includedToken: String) throws { var expectedError = expectedSyntaxError(token: token, template: template, description: reason) - expectedError.parentError = expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason) + expectedError.stackTrace = [expectedSyntaxError(token: includedToken, template: includedTemplate, description: reason).token!] let error = try expect(environment.render(template: template, context: ["target": "World"])) - .toThrow(expectedError) + .toThrow() as TemplateSyntaxError try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError) } @@ -249,11 +251,10 @@ func testEnvironment() { func expectError(reason: String, childToken: String, baseToken: String?) throws { var expectedError = expectedSyntaxError(token: childToken, template: childTemplate, description: reason) if let baseToken = baseToken { - expectedError.parentError = expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason) + expectedError.stackTrace = [expectedSyntaxError(token: baseToken, template: baseTemplate, description: reason).token!] } - let error = try expect(environment.render(template: childTemplate, context: ["target": "World"])) - .toThrow(expectedError) + .toThrow() as TemplateSyntaxError try expect(environment.errorReporter.renderError(error)) == environment.errorReporter.renderError(expectedError) } @@ -312,7 +313,7 @@ func testEnvironment() { private extension Expectation { @discardableResult - func toThrow(_ error: T) throws -> T { + func toThrow() throws -> T { var thrownError: Error? = nil do { @@ -323,12 +324,9 @@ private extension Expectation { if let thrownError = thrownError { if let thrownError = thrownError as? T { - if error != thrownError { - throw failure("\(thrownError) is not \(error)") - } return thrownError } else { - throw failure("\(thrownError) is not \(error)") + throw failure("\(thrownError) is not \(T.self)") } } else { throw failure("expression did not throw an error") diff --git a/Tests/StencilTests/FilterSpec.swift b/Tests/StencilTests/FilterSpec.swift index 2501610..7898cc0 100644 --- a/Tests/StencilTests/FilterSpec.swift +++ b/Tests/StencilTests/FilterSpec.swift @@ -62,7 +62,7 @@ func testFilter() { } let context = Context(dictionary: context, environment: Environment(extensions: [repeatExtension])) - try expect(try template.render(context)).toThrow(TemplateSyntaxError("No Repeat")) + try expect(try template.render(context)).toThrow(TemplateSyntaxError(reason: "No Repeat", token: template.tokens.first)) } $0.it("allows you to override a default filter") { @@ -91,7 +91,7 @@ func testFilter() { describe("capitalize filter") { - let template = Template(templateString: "{{ name|capitalize }}") + let template = Template(templateString: "{{ name|capitalize }}") $0.it("capitalizes a string") { let result = try template.render(Context(dictionary: ["name": "kyle"])) diff --git a/Tests/StencilTests/ForNodeSpec.swift b/Tests/StencilTests/ForNodeSpec.swift index 8d41c1d..1177b35 100644 --- a/Tests/StencilTests/ForNodeSpec.swift +++ b/Tests/StencilTests/ForNodeSpec.swift @@ -169,11 +169,9 @@ func testForNode() { } $0.it("handles invalid input") { - let tokens: [Token] = [ - .block(value: "for i", at: .unknown), - ] + let tokens: [Token] = [.block(value: "for i", at: .unknown)] let parser = TokenParser(tokens: tokens, environment: Environment()) - let error = TemplateSyntaxError("'for' statements should use the following syntax 'for x in y where condition'.") + let error = TemplateSyntaxError(reason: "'for' statements should use the following syntax 'for x in y where condition'.", token: tokens.first) try expect(try parser.parse()).toThrow(error) } diff --git a/Tests/StencilTests/IfNodeSpec.swift b/Tests/StencilTests/IfNodeSpec.swift index 25119eb..9fada9f 100644 --- a/Tests/StencilTests/IfNodeSpec.swift +++ b/Tests/StencilTests/IfNodeSpec.swift @@ -179,22 +179,18 @@ func testIfNode() { } $0.it("throws an error when parsing an if block without an endif") { - let tokens: [Token] = [ - .block(value: "if value", at: .unknown), - ] + let tokens: [Token] = [.block(value: "if value", at: .unknown)] let parser = TokenParser(tokens: tokens, environment: Environment()) - let error = TemplateSyntaxError("`endif` was not found.") + let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) try expect(try parser.parse()).toThrow(error) } $0.it("throws an error when parsing an ifnot without an endif") { - let tokens: [Token] = [ - .block(value: "ifnot value", at: .unknown), - ] + let tokens: [Token] = [.block(value: "ifnot value", at: .unknown)] let parser = TokenParser(tokens: tokens, environment: Environment()) - let error = TemplateSyntaxError("`endif` was not found.") + let error = TemplateSyntaxError(reason: "`endif` was not found.", token: tokens.first) try expect(try parser.parse()).toThrow(error) } } diff --git a/Tests/StencilTests/IncludeSpec.swift b/Tests/StencilTests/IncludeSpec.swift index bf95c18..153d7f6 100644 --- a/Tests/StencilTests/IncludeSpec.swift +++ b/Tests/StencilTests/IncludeSpec.swift @@ -14,7 +14,7 @@ func testInclude() { 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") + let error = TemplateSyntaxError(reason: "'include' tag takes one argument, the template file to be included", token: tokens.first) try expect(try parser.parse()).toThrow(error) } diff --git a/Tests/StencilTests/LexerSpec.swift b/Tests/StencilTests/LexerSpec.swift index b74df68..270cb4b 100644 --- a/Tests/StencilTests/LexerSpec.swift +++ b/Tests/StencilTests/LexerSpec.swift @@ -9,7 +9,7 @@ func testLexer() { let tokens = lexer.tokenize() try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "Hello World", at: "Hello World".range) + try expect(tokens.first) == .text(value: "Hello World", at: SourceMap(line: ("Hello World", 1, 0))) } $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", at: "{# Comment #}".range) + try expect(tokens.first) == .comment(value: "Comment", at: SourceMap(line: ("{# Comment #}", 1, 3))) } $0.it("can tokenize a variable") { @@ -25,7 +25,7 @@ func testLexer() { let tokens = lexer.tokenize() try expect(tokens.count) == 1 - try expect(tokens.first) == .variable(value: "Variable", at: "{{ Variable }}".range) + try expect(tokens.first) == .variable(value: "Variable", at: SourceMap(line: ("{{ Variable }}", 1, 3))) } $0.it("can tokenize unclosed tag by ignoring it") { @@ -34,18 +34,18 @@ func testLexer() { let tokens = lexer.tokenize() try expect(tokens.count) == 1 - try expect(tokens.first) == .text(value: "", at: "".range) + try expect(tokens.first) == .text(value: "", at: SourceMap(line: ("{{ thing", 1, 0))) } $0.it("can tokenize a mixture of content") { - let templateString = "My name is {{ name }}." + let templateString = "My name is {{ myname }}." let lexer = Lexer(templateString: templateString) let tokens = lexer.tokenize() try expect(tokens.count) == 3 - 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: ".")!) + try expect(tokens[0]) == Token.text(value: "My name is ", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "My name is ")!))) + try expect(tokens[1]) == Token.variable(value: "myname", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "myname")!))) + try expect(tokens[2]) == Token.text(value: ".", at: SourceMap(line: templateString.rangeLine(templateString.range(of: ".")!))) } $0.it("can tokenize two variables without being greedy") { @@ -54,8 +54,8 @@ func testLexer() { let tokens = lexer.tokenize() try expect(tokens.count) == 2 - 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 }}")!) + try expect(tokens[0]) == Token.variable(value: "thing", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "thing")!))) + try expect(tokens[1]) == Token.variable(value: "name", at: SourceMap(line: templateString.rangeLine(templateString.range(of: "name")!))) } $0.it("can tokenize an unclosed block") { diff --git a/Tests/StencilTests/ParserSpec.swift b/Tests/StencilTests/ParserSpec.swift index facd07a..25c485f 100644 --- a/Tests/StencilTests/ParserSpec.swift +++ b/Tests/StencilTests/ParserSpec.swift @@ -52,11 +52,10 @@ func testTokenParser() { } $0.it("errors when parsing an unknown tag") { - let parser = TokenParser(tokens: [ - .block(value: "unknown", at: .unknown), - ], environment: Environment()) + let tokens: [Token] = [.block(value: "unknown", at: .unknown)] + let parser = TokenParser(tokens: tokens, environment: Environment()) - try expect(try parser.parse()).toThrow(TemplateSyntaxError("Unknown template tag 'unknown'")) + try expect(try parser.parse()).toThrow(TemplateSyntaxError(reason: "Unknown template tag 'unknown'", token: tokens.first)) } } }