added parent context to ErrorReporterContext and handling errors in include and extend nodes
This commit is contained in:
@@ -48,7 +48,6 @@ public struct Environment {
|
|||||||
return try template.render(context)
|
return try template.render(context)
|
||||||
} catch {
|
} catch {
|
||||||
try errorReporter.report(error: error)
|
try errorReporter.report(error: error)
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,4 +55,19 @@ public struct Environment {
|
|||||||
return errorReporter.context?.template
|
return errorReporter.context?.template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public func pushTemplate<Result>(_ 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 {
|
||||||
|
try errorReporter.report(error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,26 +35,31 @@ public struct TemplateSyntaxError : Error, Equatable, CustomStringConvertible {
|
|||||||
public class ErrorReporterContext {
|
public class ErrorReporterContext {
|
||||||
public let template: Template
|
public let template: Template
|
||||||
|
|
||||||
public init(template: Template) {
|
public typealias ParentContext = (context: ErrorReporterContext, token: Token)
|
||||||
|
public let parent: ParentContext?
|
||||||
|
|
||||||
|
public init(template: Template, parent: ParentContext? = nil) {
|
||||||
self.template = template
|
self.template = template
|
||||||
|
self.parent = parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ErrorReporter: class {
|
public protocol ErrorReporter: class {
|
||||||
var context: ErrorReporterContext! { get set }
|
var context: ErrorReporterContext! { get set }
|
||||||
func report(error: Error) throws
|
func report(error: Error) throws -> Never
|
||||||
func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error?
|
func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error?
|
||||||
}
|
}
|
||||||
|
|
||||||
open class SimpleErrorReporter: ErrorReporter {
|
open class SimpleErrorReporter: ErrorReporter {
|
||||||
public var context: ErrorReporterContext!
|
public var context: ErrorReporterContext!
|
||||||
|
|
||||||
open func report(error: Error) throws {
|
open func report(error: Error) throws -> Never {
|
||||||
guard let syntaxError = error as? TemplateSyntaxError else { throw error }
|
guard let syntaxError = error as? TemplateSyntaxError else { throw error }
|
||||||
guard let context = context else { throw error }
|
guard let context = context else { throw error }
|
||||||
throw contextAwareError(syntaxError, context: context) ?? error
|
throw contextAwareError(syntaxError, context: context) ?? error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add stack trace using parent context
|
||||||
open func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error? {
|
open func contextAwareError(_ error: TemplateSyntaxError, context: ErrorReporterContext) -> Error? {
|
||||||
guard let lexeme = error.lexeme, lexeme.range != .unknown else { return nil }
|
guard let lexeme = error.lexeme, lexeme.range != .unknown else { return nil }
|
||||||
let templateName = context.template.name.map({ "\($0):" }) ?? ""
|
let templateName = context.template.name.map({ "\($0):" }) ?? ""
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PathKit
|
|||||||
|
|
||||||
class IncludeNode : NodeType {
|
class IncludeNode : NodeType {
|
||||||
let templateName: Variable
|
let templateName: Variable
|
||||||
|
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()
|
||||||
@@ -11,11 +12,12 @@ class IncludeNode : NodeType {
|
|||||||
throw TemplateSyntaxError("'include' tag takes one argument, the template file to be included")
|
throw TemplateSyntaxError("'include' tag takes one argument, the template file to be included")
|
||||||
}
|
}
|
||||||
|
|
||||||
return IncludeNode(templateName: Variable(bits[1]))
|
return IncludeNode(templateName: Variable(bits[1]), token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(templateName: Variable) {
|
init(templateName: Variable, token: Token) {
|
||||||
self.templateName = templateName
|
self.templateName = templateName
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
@@ -25,9 +27,11 @@ class IncludeNode : NodeType {
|
|||||||
|
|
||||||
let template = try context.environment.loadTemplate(name: templateName)
|
let template = try context.environment.loadTemplate(name: templateName)
|
||||||
|
|
||||||
return try context.push {
|
return try context.environment.pushTemplate(template, token: token) {
|
||||||
|
try context.push {
|
||||||
return try template.render(context)
|
return try template.render(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +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
|
||||||
|
|
||||||
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()
|
||||||
@@ -72,12 +73,13 @@ class ExtendsNode : NodeType {
|
|||||||
return dict
|
return dict
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExtendsNode(templateName: Variable(bits[1]), blocks: nodes)
|
return ExtendsNode(templateName: Variable(bits[1]), blocks: nodes, token: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(templateName: Variable, blocks: [String: BlockNode]) {
|
init(templateName: Variable, blocks: [String: BlockNode], token: Token) {
|
||||||
self.templateName = templateName
|
self.templateName = templateName
|
||||||
self.blocks = blocks
|
self.blocks = blocks
|
||||||
|
self.token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
@@ -98,10 +100,12 @@ class ExtendsNode : NodeType {
|
|||||||
blockContext = BlockContext(blocks: blocks)
|
blockContext = BlockContext(blocks: blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
|
return try context.environment.pushTemplate(template, token: token) {
|
||||||
|
try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
|
||||||
return try template.render(context)
|
return try template.render(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Spectre
|
import Spectre
|
||||||
|
import PathKit
|
||||||
@testable import Stencil
|
@testable import Stencil
|
||||||
|
|
||||||
|
|
||||||
@@ -124,6 +125,34 @@ func testEnvironment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.context("given related templates") {
|
||||||
|
let path = Path(#file) + ".." + "fixtures"
|
||||||
|
let loader = FileSystemLoader(paths: [path])
|
||||||
|
let environment = Environment(loader: loader)
|
||||||
|
|
||||||
|
$0.it("reports syntax error in included template") {
|
||||||
|
let template: Template = "{% include \"invalid-include.html\"%}"
|
||||||
|
environment.errorReporter.context = ErrorReporterContext(template: template)
|
||||||
|
|
||||||
|
let context = Context(dictionary: ["target": "World"], environment: environment)
|
||||||
|
|
||||||
|
let includedTemplate = try environment.loadTemplate(name: "invalid-include.html")
|
||||||
|
let error = expectedSyntaxError(token: "target|unknown", template: includedTemplate, description: "Unknown filter 'unknown'")
|
||||||
|
|
||||||
|
try expect(try template.render(context)).toThrow(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("reports syntax error in extended template") {
|
||||||
|
let template = try environment.loadTemplate(name: "invalid-child-super.html")
|
||||||
|
let context = Context(dictionary: ["target": "World"], environment: environment)
|
||||||
|
|
||||||
|
let baseTemplate = try environment.loadTemplate(name: "invalid-base.html")
|
||||||
|
let error = expectedSyntaxError(token: "target|unknown", template: baseTemplate, description: "Unknown filter 'unknown'")
|
||||||
|
|
||||||
|
try expect(try template.render(context)).toThrow(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func testInclude() {
|
|||||||
|
|
||||||
$0.describe("rendering") {
|
$0.describe("rendering") {
|
||||||
$0.it("throws an error when rendering without a loader") {
|
$0.it("throws an error when rendering without a loader") {
|
||||||
let node = IncludeNode(templateName: Variable("\"test.html\""))
|
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try node.render(Context())
|
_ = try node.render(Context())
|
||||||
@@ -41,7 +41,7 @@ func testInclude() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.it("throws an error when it cannot find the included template") {
|
$0.it("throws an error when it cannot find the included template") {
|
||||||
let node = IncludeNode(templateName: Variable("\"unknown.html\""))
|
let node = IncludeNode(templateName: Variable("\"unknown.html\""), token: .block(value: "", at: .unknown))
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try node.render(Context(environment: environment))
|
_ = try node.render(Context(environment: environment))
|
||||||
@@ -51,7 +51,7 @@ func testInclude() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$0.it("successfully renders a found included template") {
|
$0.it("successfully renders a found included template") {
|
||||||
let node = IncludeNode(templateName: Variable("\"test.html\""))
|
let node = IncludeNode(templateName: Variable("\"test.html\""), token: .block(value: "", at: .unknown))
|
||||||
let context = Context(dictionary: ["target": "World"], environment: environment)
|
let context = Context(dictionary: ["target": "World"], environment: environment)
|
||||||
let value = try node.render(context)
|
let value = try node.render(context)
|
||||||
try expect(value) == "Hello World!"
|
try expect(value) == "Hello World!"
|
||||||
|
|||||||
2
Tests/StencilTests/fixtures/invalid-base.html
Normal file
2
Tests/StencilTests/fixtures/invalid-base.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{% block header %}Header{% endblock %}
|
||||||
|
{% block body %}Body {{ target|unknown }} {% endblock %}
|
||||||
3
Tests/StencilTests/fixtures/invalid-child-super.html
Normal file
3
Tests/StencilTests/fixtures/invalid-child-super.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{% extends "invalid-base.html" %}
|
||||||
|
{% block body %}Child {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
1
Tests/StencilTests/fixtures/invalid-include.html
Normal file
1
Tests/StencilTests/fixtures/invalid-include.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello {{ target|unknown }}!
|
||||||
Reference in New Issue
Block a user