From f6a08a164f19a64262d1d9828403ce4a0b60d970 Mon Sep 17 00:00:00 2001 From: Ben <162194776+b-nassler@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:12:36 +0100 Subject: [PATCH] Fix crash with missing parameter (#130) --- Sources/LeafKit/LeafParser/LeafParser.swift | 32 ++++++++++++++------- Tests/LeafKitTests/LeafErrorTests.swift | 17 +++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Sources/LeafKit/LeafParser/LeafParser.swift b/Sources/LeafKit/LeafParser/LeafParser.swift index 45a906e9..722559f3 100644 --- a/Sources/LeafKit/LeafParser/LeafParser.swift +++ b/Sources/LeafKit/LeafParser/LeafParser.swift @@ -183,13 +183,20 @@ internal struct LeafParser { var group = [ParameterDeclaration]() var paramsList = [ParameterDeclaration]() - - func dump() { - defer { group = [] } - if group.isEmpty { return } - group.evaluate() - if group.count > 1 { paramsList.append(.expression(group)) } - else { paramsList.append(group.first!) } + + func dump() throws { + defer { group = [] } + if group.isEmpty { return } + group.evaluate() + if group.count > 1 { paramsList.append(.expression(group)) } + else { + guard let first = group.first else { + // It's better to handle this case as well, even though logically it might never happen + // since you're checking if group.isEmpty before. + throw LeafError(.unknownError("Found nil while iterating through params"), file: #file, function: #function, line: #line, column: #column) + } + paramsList.append(first) + } } outer: while let next = peek() { @@ -200,7 +207,12 @@ internal struct LeafParser { let params = try readParameters() // parameter tags not permitted to have bodies if params.count > 1 { group.append(.expression(params)) } - else { group.append(params.first!) } + else { + guard let firstParam = params.first else { + throw LeafError(.unknownError("Found nil while iterating through params")) + } + group.append(firstParam) + } case .parameter(let p): pop() switch p { @@ -214,11 +226,11 @@ internal struct LeafParser { } case .parametersEnd: pop() - dump() + try dump() break outer case .parameterDelimiter: pop() - dump() + try dump() case .whitespace: pop() continue diff --git a/Tests/LeafKitTests/LeafErrorTests.swift b/Tests/LeafKitTests/LeafErrorTests.swift index 257c31d3..4da3a302 100644 --- a/Tests/LeafKitTests/LeafErrorTests.swift +++ b/Tests/LeafKitTests/LeafErrorTests.swift @@ -45,4 +45,21 @@ final class LeafErrorTests: XCTestCase { XCTFail("Wrong error: \(error.localizedDescription)") } } + + /// Verify that rendering a template with a missing required parameter will throw `LeafError.missingParameter` + func testMissingParameterError() { + var test = TestFiles() + // Assuming "/missingParam.leaf" is a template that requires a parameter we intentionally don't provide + test.files["/missingParam.leaf"] = """ + #(foo.bar.trim()) + """ + XCTAssertThrowsError(try TestRenderer(sources: .singleSource(test)) + .render(path: "missingParam", context: [:]) + .wait() + ) { + guard case .unknownError("Found nil while iterating through params") = ($0 as? LeafError)?.reason else { + return XCTFail("Expected LeafError.unknownError(\"Found nil while iterating through params\"), got \(String(reflecting: $0))") + } + } + } }