From 6f6a983a938b0d37e3fe00a723043c56026116fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20Cecilia=20Luque?= Date: Thu, 26 Dec 2019 02:18:27 +0100 Subject: [PATCH] Added the option to throw when a variable used in the template is not found in the context --- CHANGELOG.md | 3 +++ Sources/Environment.swift | 3 +++ Sources/Variable.swift | 8 +++++++- Tests/StencilTests/EnvironmentSpec.swift | 18 +++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 752d7c95..d3c0dc7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ _None_ , i.e. `myfilter = "uppercase"` and then use it to invoke this filter with `{{ string|filter:myfilter }}`. [Ilya Puchka](https://github.com/ilyapuchka) [#203](https://github.com/stencilproject/Stencil/pull/203) +- Added option to throw when a variable used in a template could not be resolved. + [Andres Cecilia Luque](https://github.com/acecilia) + [#289](https://github.com/stencilproject/Stencil/pull/289) ### Deprecations diff --git a/Sources/Environment.swift b/Sources/Environment.swift index 0c2c72e7..ba636ce2 100644 --- a/Sources/Environment.swift +++ b/Sources/Environment.swift @@ -1,16 +1,19 @@ public struct Environment { public let templateClass: Template.Type public var extensions: [Extension] + public let throwOnUnresolvedVariable: Bool public var loader: Loader? public init(loader: Loader? = nil, extensions: [Extension] = [], + throwOnUnresolvedVariable: Bool = false, templateClass: Template.Type = Template.self) { self.templateClass = templateClass self.loader = loader self.extensions = extensions + [DefaultExtension()] + self.throwOnUnresolvedVariable = throwOnUnresolvedVariable } public func loadTemplate(name: String) throws -> Template { diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 44569dff..39f3fff8 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -30,10 +30,16 @@ class FilterExpression: Resolvable { func resolve(_ context: Context) throws -> Any? { let result = try variable.resolve(context) - return try filters.reduce(result) { value, filter in + let filteredResult = try filters.reduce(result) { value, filter in let arguments = try filter.1.map { try $0.resolve(context) } return try filter.0.invoke(value: value, arguments: arguments, context: context) } + + if filteredResult == nil, context.environment.throwOnUnresolvedVariable { + throw TemplateSyntaxError("Variable '\(variable.variable)' could not be resolved") + } + + return filteredResult } } diff --git a/Tests/StencilTests/EnvironmentSpec.swift b/Tests/StencilTests/EnvironmentSpec.swift index f5b829ff..06bc2a35 100644 --- a/Tests/StencilTests/EnvironmentSpec.swift +++ b/Tests/StencilTests/EnvironmentSpec.swift @@ -182,7 +182,23 @@ final class EnvironmentTests: XCTestCase { } } + func testUnresolvedVariable() { + self.template = Template(templateString: "Hello {{ name }}") + + it("can render a variable not defined in the context and throwing is disabled") { + self.environment = Environment() + let result = try self.environment.render(template: self.template, context: [:]) + try expect(result) == "Hello " + } + + it("throws when a variable is not defined in the context and throwing is enabled") { + self.environment = Environment(throwOnUnresolvedVariable: true) + try self.expectError(context: [:], reason: "Variable 'name' could not be resolved", token: "name") + } + } + private func expectError( + context: [String: Any] = ["names": ["Bob", "Alice"], "name": "Bob"], reason: String, token: String, file: String = #file, @@ -192,7 +208,7 @@ final class EnvironmentTests: XCTestCase { let expectedError = expectedSyntaxError(token: token, template: template, description: reason) let error = try expect( - self.environment.render(template: self.template, context: ["names": ["Bob", "Alice"], "name": "Bob"]), + self.environment.render(template: self.template, context: context), file: file, line: line, function: function