reporting node rendering errors using reference to node’s token
This commit is contained in:
@@ -55,8 +55,7 @@ public struct Environment {
|
|||||||
return errorReporter.context?.template
|
return errorReporter.context?.template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func pushTemplate<Result>(_ template: Template, token: Token?, closure: (() throws -> Result)) rethrows -> Result {
|
||||||
public func pushTemplate<Result>(_ template: Template, token: Token, closure: (() throws -> Result)) rethrows -> Result {
|
|
||||||
let errorReporterContext = errorReporter.context
|
let errorReporterContext = errorReporter.context
|
||||||
defer { errorReporter.context = errorReporterContext }
|
defer { errorReporter.context = errorReporterContext }
|
||||||
errorReporter.context = ErrorReporterContext(
|
errorReporter.context = ErrorReporterContext(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
|
|||||||
public class ErrorReporterContext {
|
public class ErrorReporterContext {
|
||||||
public let template: Template
|
public let template: Template
|
||||||
|
|
||||||
public typealias ParentContext = (context: ErrorReporterContext, token: Token)
|
public typealias ParentContext = (context: ErrorReporterContext, token: Token?)
|
||||||
public let parent: ParentContext?
|
public let parent: ParentContext?
|
||||||
|
|
||||||
public init(template: Template, parent: ParentContext? = nil) {
|
public init(template: Template, parent: ParentContext? = nil) {
|
||||||
@@ -47,30 +47,29 @@ public class ErrorReporterContext {
|
|||||||
public protocol ErrorReporter: class {
|
public protocol ErrorReporter: class {
|
||||||
var context: ErrorReporterContext! { get set }
|
var context: ErrorReporterContext! { get set }
|
||||||
func reportError(_ error: Error) -> Error
|
func reportError(_ error: Error) -> Error
|
||||||
func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error?
|
func contextAwareError(_ error: Error, at range: Range<String.Index>?, context: ErrorReporterContext) -> Error?
|
||||||
}
|
}
|
||||||
|
|
||||||
open class SimpleErrorReporter: ErrorReporter {
|
open class SimpleErrorReporter: ErrorReporter {
|
||||||
public var context: ErrorReporterContext!
|
public var context: ErrorReporterContext!
|
||||||
|
|
||||||
open func reportError(_ error: Error) -> Error {
|
open func reportError(_ error: Error) -> Error {
|
||||||
guard let syntaxError = error as? TemplateSyntaxError else { return error }
|
|
||||||
guard let context = context else { return error }
|
guard let context = context else { return error }
|
||||||
return contextAwareError(syntaxError, context: context) ?? error
|
return contextAwareError(error, at: (error as? TemplateSyntaxError)?.lexeme?.range, context: context) ?? error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add stack trace using parent context
|
// TODO: add stack trace using parent context
|
||||||
open func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error? {
|
open func contextAwareError(_ error: Error, at range: Range<String.Index>?, context: ErrorReporterContext) -> Error? {
|
||||||
guard let lexeme = error.lexeme, lexeme.range != .unknown else { return nil }
|
guard let range = range, range != .unknown else { return nil }
|
||||||
let templateName = context.template.name.map({ "\($0):" }) ?? ""
|
let templateName = context.template.name.map({ "\($0):" }) ?? ""
|
||||||
let tokenContent = context.template.templateString.substring(with: lexeme.range)
|
let tokenContent = context.template.templateString.substring(with: range)
|
||||||
let lexer = Lexer(templateString: context.template.templateString)
|
let line = context.template.templateString.rangeLine(range)
|
||||||
let line = lexer.lexemeLine(lexeme)
|
|
||||||
let highlight = "\(String(Array(repeating: " ", count: line.offset)))^\(String(Array(repeating: "~", count: max(tokenContent.length - 1, 0))))"
|
let highlight = "\(String(Array(repeating: " ", count: line.offset)))^\(String(Array(repeating: "~", count: max(tokenContent.length - 1, 0))))"
|
||||||
let description = "\(templateName)\(line.number):\(line.offset): error: \(error.description)\n\(line.content)\n\(highlight)"
|
let description = "\(templateName)\(line.number):\(line.offset): error: \(error)\n\(line.content)\n\(highlight)"
|
||||||
let error = TemplateSyntaxError(description)
|
let error = TemplateSyntaxError(description)
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Range where Bound == String.Index {
|
extension Range where Bound == String.Index {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ open class Extension {
|
|||||||
/// Registers a simple template tag with a name and a handler
|
/// Registers a simple template tag with a name and a handler
|
||||||
public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
|
public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
|
||||||
registerTag(name, parser: { parser, token in
|
registerTag(name, parser: { parser, token in
|
||||||
return SimpleNode(handler: handler)
|
return SimpleNode(token: token, handler: handler)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
class FilterNode : NodeType {
|
class FilterNode : NodeType {
|
||||||
let resolvable: Resolvable
|
let resolvable: Resolvable
|
||||||
let nodes: [NodeType]
|
let nodes: [NodeType]
|
||||||
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
let bits = token.components()
|
let bits = token.components()
|
||||||
@@ -16,19 +17,20 @@ class FilterNode : NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resolvable = try parser.compileFilter("filter_value|\(bits[1])", containedIn: token)
|
let resolvable = try parser.compileFilter("filter_value|\(bits[1])", containedIn: token)
|
||||||
return FilterNode(nodes: blocks, resolvable: resolvable)
|
return FilterNode(nodes: blocks, resolvable: resolvable, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(nodes: [NodeType], resolvable: Resolvable) {
|
init(nodes: [NodeType], resolvable: Resolvable, token: Token) {
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
self.resolvable = resolvable
|
self.resolvable = resolvable
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
let value = try renderNodes(nodes, context)
|
let value = try renderNodes(nodes, context)
|
||||||
|
|
||||||
return try context.push(dictionary: ["filter_value": value]) {
|
return try context.push(dictionary: ["filter_value": value]) {
|
||||||
return try VariableNode(variable: resolvable).render(context)
|
return try VariableNode(variable: resolvable, token: token).render(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class ForNode : NodeType {
|
|||||||
let nodes:[NodeType]
|
let nodes:[NodeType]
|
||||||
let emptyNodes: [NodeType]
|
let emptyNodes: [NodeType]
|
||||||
let `where`: Expression?
|
let `where`: Expression?
|
||||||
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
||||||
let components = token.components()
|
let components = token.components()
|
||||||
@@ -42,15 +43,16 @@ class ForNode : NodeType {
|
|||||||
} else {
|
} else {
|
||||||
`where` = nil
|
`where` = nil
|
||||||
}
|
}
|
||||||
return ForNode(resolvable: filter, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`)
|
return ForNode(resolvable: filter, loopVariables: loopVariables, nodes: forNodes, emptyNodes:emptyNodes, where: `where`, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(resolvable: Resolvable, loopVariables: [String], nodes:[NodeType], emptyNodes:[NodeType], where: Expression? = nil) {
|
init(resolvable: Resolvable, loopVariables: [String], nodes:[NodeType], emptyNodes:[NodeType], where: Expression? = nil, token: Token? = nil) {
|
||||||
self.resolvable = resolvable
|
self.resolvable = resolvable
|
||||||
self.loopVariables = loopVariables
|
self.loopVariables = loopVariables
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
self.emptyNodes = emptyNodes
|
self.emptyNodes = emptyNodes
|
||||||
self.where = `where`
|
self.where = `where`
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func push<Result>(value: Any, context: Context, closure: () throws -> (Result)) rethrows -> Result {
|
func push<Result>(value: Any, context: Context, closure: () throws -> (Result)) rethrows -> Result {
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ final class IfCondition {
|
|||||||
|
|
||||||
class IfNode : NodeType {
|
class IfNode : NodeType {
|
||||||
let conditions: [IfCondition]
|
let conditions: [IfCondition]
|
||||||
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
var components = token.components()
|
var components = token.components()
|
||||||
@@ -193,27 +194,27 @@ class IfNode : NodeType {
|
|||||||
IfCondition(expression: expression, nodes: nodes)
|
IfCondition(expression: expression, nodes: nodes)
|
||||||
]
|
]
|
||||||
|
|
||||||
var token = parser.nextToken()
|
var nextToken = parser.nextToken()
|
||||||
while let current = token, current.contents.hasPrefix("elif") {
|
while let current = nextToken, current.contents.hasPrefix("elif") {
|
||||||
var components = current.components()
|
var components = current.components()
|
||||||
components.removeFirst()
|
components.removeFirst()
|
||||||
let expression = try parseExpression(components: components, tokenParser: parser, token: current)
|
let expression = try parseExpression(components: components, tokenParser: parser, token: current)
|
||||||
|
|
||||||
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
let nodes = try parser.parse(until(["endif", "elif", "else"]))
|
||||||
token = parser.nextToken()
|
nextToken = parser.nextToken()
|
||||||
conditions.append(IfCondition(expression: expression, nodes: nodes))
|
conditions.append(IfCondition(expression: expression, nodes: nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let current = token, current.contents == "else" {
|
if let current = nextToken, current.contents == "else" {
|
||||||
conditions.append(IfCondition(expression: nil, nodes: try parser.parse(until(["endif"]))))
|
conditions.append(IfCondition(expression: nil, nodes: try parser.parse(until(["endif"]))))
|
||||||
token = parser.nextToken()
|
nextToken = parser.nextToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let current = token, current.contents == "endif" else {
|
guard let current = nextToken, current.contents == "endif" else {
|
||||||
throw TemplateSyntaxError("`endif` was not found.")
|
throw TemplateSyntaxError("`endif` was not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return IfNode(conditions: conditions)
|
return IfNode(conditions: conditions, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
@@ -240,11 +241,12 @@ class IfNode : NodeType {
|
|||||||
return IfNode(conditions: [
|
return IfNode(conditions: [
|
||||||
IfCondition(expression: expression, nodes: trueNodes),
|
IfCondition(expression: expression, nodes: trueNodes),
|
||||||
IfCondition(expression: nil, nodes: falseNodes),
|
IfCondition(expression: nil, nodes: falseNodes),
|
||||||
])
|
], token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(conditions: [IfCondition]) {
|
init(conditions: [IfCondition], token: Token? = nil) {
|
||||||
self.conditions = conditions
|
self.conditions = conditions
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PathKit
|
|||||||
|
|
||||||
class IncludeNode : NodeType {
|
class IncludeNode : NodeType {
|
||||||
let templateName: Variable
|
let templateName: Variable
|
||||||
let token: Token
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
let bits = token.components()
|
let bits = token.components()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ extension Collection {
|
|||||||
class ExtendsNode : NodeType {
|
class ExtendsNode : NodeType {
|
||||||
let templateName: Variable
|
let templateName: Variable
|
||||||
let blocks: [String:BlockNode]
|
let blocks: [String:BlockNode]
|
||||||
let token: Token
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
let bits = token.components()
|
let bits = token.components()
|
||||||
@@ -112,6 +112,7 @@ class ExtendsNode : NodeType {
|
|||||||
class BlockNode : NodeType {
|
class BlockNode : NodeType {
|
||||||
let name: String
|
let name: String
|
||||||
let nodes: [NodeType]
|
let nodes: [NodeType]
|
||||||
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
|
||||||
let bits = token.components()
|
let bits = token.components()
|
||||||
@@ -123,12 +124,13 @@ class BlockNode : NodeType {
|
|||||||
let blockName = bits[1]
|
let blockName = bits[1]
|
||||||
let nodes = try parser.parse(until(["endblock"]))
|
let nodes = try parser.parse(until(["endblock"]))
|
||||||
_ = parser.nextToken()
|
_ = parser.nextToken()
|
||||||
return BlockNode(name:blockName, nodes:nodes)
|
return BlockNode(name:blockName, nodes:nodes, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(name: String, nodes: [NodeType]) {
|
init(name: String, nodes: [NodeType], token: Token) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
|
|||||||
@@ -187,4 +187,21 @@ extension String {
|
|||||||
return String(self[first..<last])
|
return String(self[first..<last])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func rangeLine(_ range: Range<String.Index>) -> (content: String, number: Int, offset: String.IndexDistance) {
|
||||||
|
var lineNumber: Int = 0
|
||||||
|
var offset = 0
|
||||||
|
var lineContent = ""
|
||||||
|
|
||||||
|
for line in components(separatedBy: CharacterSet.newlines) {
|
||||||
|
lineNumber += 1
|
||||||
|
lineContent = line
|
||||||
|
if let rangeOfLine = self.range(of: line), rangeOfLine.contains(range.lowerBound) {
|
||||||
|
offset = distance(from: rangeOfLine.lowerBound, to:
|
||||||
|
range.lowerBound)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (lineContent, lineNumber, offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,38 @@ import Foundation
|
|||||||
public protocol NodeType {
|
public protocol NodeType {
|
||||||
/// Render the node in the given context
|
/// Render the node in the given context
|
||||||
func render(_ context:Context) throws -> String
|
func render(_ context:Context) throws -> String
|
||||||
|
|
||||||
|
/// Reference to this node's token
|
||||||
|
var token: Token? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Render the collection of nodes in the given context
|
/// Render the collection of nodes in the given context
|
||||||
public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String {
|
public func renderNodes(_ nodes:[NodeType], _ context:Context) throws -> String {
|
||||||
return try nodes.map { try $0.render(context) }.joined(separator: "")
|
return try nodes.map({
|
||||||
|
do {
|
||||||
|
return try $0.render(context)
|
||||||
|
} catch {
|
||||||
|
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil, let token = $0.token {
|
||||||
|
if let contentsRange = context.environment.template?.templateString.range(of: token.contents, range: token.range) {
|
||||||
|
syntaxError.lexeme = Token.block(value: token.contents, at: contentsRange)
|
||||||
|
} else {
|
||||||
|
syntaxError.lexeme = token
|
||||||
|
}
|
||||||
|
throw syntaxError
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).joined(separator: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SimpleNode : NodeType {
|
public class SimpleNode : NodeType {
|
||||||
public let handler:(Context) throws -> String
|
public let handler:(Context) throws -> String
|
||||||
|
public let token: Token?
|
||||||
|
|
||||||
public init(handler: @escaping (Context) throws -> String) {
|
public init(token: Token, handler: @escaping (Context) throws -> String) {
|
||||||
|
self.token = token
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,9 +46,11 @@ public class SimpleNode : NodeType {
|
|||||||
|
|
||||||
public class TextNode : NodeType {
|
public class TextNode : NodeType {
|
||||||
public let text:String
|
public let text:String
|
||||||
|
public let token: Token?
|
||||||
|
|
||||||
public init(text:String) {
|
public init(text:String) {
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.token = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func render(_ context:Context) throws -> String {
|
public func render(_ context:Context) throws -> String {
|
||||||
@@ -44,13 +66,16 @@ public protocol Resolvable {
|
|||||||
|
|
||||||
public class VariableNode : NodeType {
|
public class VariableNode : NodeType {
|
||||||
public let variable: Resolvable
|
public let variable: Resolvable
|
||||||
|
public var token: Token?
|
||||||
|
|
||||||
public init(variable: Resolvable) {
|
public init(variable: Resolvable, token: Token? = nil) {
|
||||||
self.variable = variable
|
self.variable = variable
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(variable: String) {
|
public init(variable: String, token: Token? = nil) {
|
||||||
self.variable = Variable(variable)
|
self.variable = Variable(variable)
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
public func render(_ context: Context) throws -> String {
|
public func render(_ context: Context) throws -> String {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Foundation
|
|||||||
|
|
||||||
class NowNode : NodeType {
|
class NowNode : NodeType {
|
||||||
let format:Variable
|
let format:Variable
|
||||||
|
let token: Token?
|
||||||
|
|
||||||
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
||||||
var format:Variable?
|
var format:Variable?
|
||||||
@@ -16,11 +17,12 @@ class NowNode : NodeType {
|
|||||||
format = Variable(components[1])
|
format = Variable(components[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
return NowNode(format:format)
|
return NowNode(format:format, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(format:Variable?) {
|
init(format:Variable?, token: Token? = nil) {
|
||||||
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
|
self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ public class TokenParser {
|
|||||||
case .text(let text, _):
|
case .text(let text, _):
|
||||||
nodes.append(TextNode(text: text))
|
nodes.append(TextNode(text: text))
|
||||||
case .variable:
|
case .variable:
|
||||||
nodes.append(VariableNode(variable: try compileFilter(token.contents, containedIn: token)))
|
let filter = try compileFilter(token.contents, containedIn: token)
|
||||||
|
nodes.append(VariableNode(variable: filter, token: token))
|
||||||
case .block:
|
case .block:
|
||||||
if let parse_until = parse_until , parse_until(self, token) {
|
if let parse_until = parse_until , parse_until(self, token) {
|
||||||
prependToken(token)
|
prependToken(token)
|
||||||
@@ -54,8 +55,8 @@ public class TokenParser {
|
|||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
} catch {
|
} catch {
|
||||||
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil {
|
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil {
|
||||||
syntaxError.lexeme = token
|
syntaxError.lexeme = token
|
||||||
throw syntaxError
|
throw syntaxError
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -105,10 +106,12 @@ public class TokenParser {
|
|||||||
do {
|
do {
|
||||||
return try FilterExpression(token: filterToken, parser: self)
|
return try FilterExpression(token: filterToken, parser: self)
|
||||||
} catch {
|
} catch {
|
||||||
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil,
|
if var syntaxError = error as? TemplateSyntaxError, syntaxError.lexeme == nil {
|
||||||
let filterTokenRange = environment.template?.templateString.range(of: filterToken, range: containingToken.range) {
|
if let filterTokenRange = environment.template?.templateString.range(of: filterToken, range: containingToken.range) {
|
||||||
|
syntaxError.lexeme = Token.block(value: filterToken, at: filterTokenRange)
|
||||||
syntaxError.lexeme = Token.block(value: filterToken, at: filterTokenRange)
|
} else {
|
||||||
|
syntaxError.lexeme = containingToken
|
||||||
|
}
|
||||||
throw syntaxError
|
throw syntaxError
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func testEnvironment() {
|
|||||||
var error = TemplateSyntaxError(description)
|
var error = TemplateSyntaxError(description)
|
||||||
error.lexeme = Token.block(value: token, at: template.templateString.range(of: token)!)
|
error.lexeme = Token.block(value: token, at: template.templateString.range(of: token)!)
|
||||||
let context = ErrorReporterContext(template: template)
|
let context = ErrorReporterContext(template: template)
|
||||||
error = environment.errorReporter.contextAwareError(error, context: context) as! TemplateSyntaxError
|
error = environment.errorReporter.contextAwareError(error, at: error.lexeme?.range, context: context) as! TemplateSyntaxError
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +122,86 @@ func testEnvironment() {
|
|||||||
let error = expectedFilterError(token: "name|unknown", template: template)
|
let error = expectedFilterError(token: "name|unknown", template: template)
|
||||||
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.context("given rendering error") {
|
||||||
|
$0.it("reports rendering error in variable filter") {
|
||||||
|
let template: Template = "{{ name|throw }}"
|
||||||
|
|
||||||
|
var environment = environment
|
||||||
|
let filterExtension = Extension()
|
||||||
|
filterExtension.registerFilter("throw") { (value: Any?) in
|
||||||
|
throw TemplateSyntaxError("Filter rendering error")
|
||||||
|
}
|
||||||
|
environment.extensions += [filterExtension]
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "name|throw", template: template, description: "Filter rendering error")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "Bob"])).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reports rendering error in filter tag") {
|
||||||
|
let template: Template = "{% filter throw %}Test{% endfilter %}"
|
||||||
|
|
||||||
|
var environment = environment
|
||||||
|
let filterExtension = Extension()
|
||||||
|
filterExtension.registerFilter("throw") { (value: Any?) in
|
||||||
|
throw TemplateSyntaxError("Filter rendering error")
|
||||||
|
}
|
||||||
|
environment.extensions += [filterExtension]
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "filter throw", template: template, description: "Filter rendering error")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: [:])).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reports rendering error in simple tag") {
|
||||||
|
let template: Template = "{% simpletag %}"
|
||||||
|
|
||||||
|
var environment = environment
|
||||||
|
let tagExtension = Extension()
|
||||||
|
tagExtension.registerSimpleTag("simpletag") { context in
|
||||||
|
throw TemplateSyntaxError("simpletag error")
|
||||||
|
}
|
||||||
|
environment.extensions += [tagExtension]
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "simpletag", template: template, description: "simpletag error")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: [:])).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reporsts passing argument to simple filter") {
|
||||||
|
let template: Template = "{{ name|uppercase:5 }}"
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "name|uppercase:5", template: template, description: "cannot invoke filter with an argument")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: ["name": "kyle"])).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reports rendering error in custom tag") {
|
||||||
|
let template: Template = "{% customtag %}"
|
||||||
|
|
||||||
|
var environment = environment
|
||||||
|
let tagExtension = Extension()
|
||||||
|
tagExtension.registerTag("customtag") { parser, token in
|
||||||
|
return ErrorNode(token: token)
|
||||||
|
}
|
||||||
|
environment.extensions += [tagExtension]
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "customtag", template: template, description: "Custom Error")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: [:])).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reports rendering error in for body") {
|
||||||
|
let template: Template = "{% for item in array %}{% customtag %}{% endfor %}"
|
||||||
|
|
||||||
|
var environment = environment
|
||||||
|
let tagExtension = Extension()
|
||||||
|
tagExtension.registerTag("customtag") { parser, token in
|
||||||
|
return ErrorNode(token: token)
|
||||||
|
}
|
||||||
|
environment.extensions += [tagExtension]
|
||||||
|
|
||||||
|
let error = expectedSyntaxError(token: "customtag", template: template, description: "Custom Error")
|
||||||
|
try expect(try environment.renderTemplate(string: template.templateString, context: ["array": ["a"]])).toThrow(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$0.context("given related templates") {
|
$0.context("given related templates") {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import Spectre
|
|||||||
|
|
||||||
|
|
||||||
class ErrorNode : NodeType {
|
class ErrorNode : NodeType {
|
||||||
|
let token: Token?
|
||||||
|
init(token: Token? = nil) {
|
||||||
|
self.token = token
|
||||||
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
throw TemplateSyntaxError("Custom Error")
|
throw TemplateSyntaxError("Custom Error")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import Spectre
|
|||||||
import Stencil
|
import Stencil
|
||||||
|
|
||||||
|
|
||||||
fileprivate class CustomNode : NodeType {
|
fileprivate struct CustomNode : NodeType {
|
||||||
|
let token: Token?
|
||||||
func render(_ context:Context) throws -> String {
|
func render(_ context:Context) throws -> String {
|
||||||
return "Hello World"
|
return "Hello World"
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ func testStencil() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exampleExtension.registerTag("customtag") { parser, token in
|
exampleExtension.registerTag("customtag") { parser, token in
|
||||||
return CustomNode()
|
return CustomNode(token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
let environment = Environment(extensions: [exampleExtension])
|
let environment = Environment(extensions: [exampleExtension])
|
||||||
|
|||||||
Reference in New Issue
Block a user