diff --git a/src/CoverageTools.jl b/src/CoverageTools.jl index bf1923b..ed88abc 100644 --- a/src/CoverageTools.jl +++ b/src/CoverageTools.jl @@ -18,11 +18,24 @@ const CovCount = Union{Nothing,Int} """ has_embedded_errors(expr) -Recursively check if an expression contains any `:error` nodes. +Recursively check if an expression contains any `:error` nodes that represent +actual parse errors. This excludes false positives like `Expr(:error, :symbol)` +which appear when JuliaSyntax parses `break label` where `label` happens to be +named `:error` or other keywords. + +True parse errors either have no arguments `Expr(:error)` or have string messages. """ function has_embedded_errors(expr) expr isa Expr || return false - expr.head === :error && return true + if expr.head === :error + # Expr(:error, :symbol) appears from "break symbol" where symbol is a label name + # This is not a parse error, just unfortunate naming when the label is :error, :done, etc. + # Real parse errors are Expr(:error) with no args or Expr(:error, "message string") + if length(expr.args) == 1 && expr.args[1] isa Symbol + return false # This is a label reference, not a parse error + end + return true # This is a real parse error + end return any(has_embedded_errors, expr.args) end @@ -46,7 +59,7 @@ function find_error_line(expr, last_line=nothing) return Int(line_num), false end end - + if expr.head === :error # Found an error, return the last seen line number return last_line, true diff --git a/test/runtests.jl b/test/runtests.jl index 706455e..4bfdf32 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -49,6 +49,19 @@ end # Test no error ast_no_error = Expr(:block, :(x = 1), :(y = 2)) @test !CoverageTools.has_embedded_errors(ast_no_error) + + # Test Expr(:error, :symbol) from "break label" is NOT treated as an error + # This can occur when JuliaSyntax parses "break error" where "error" is a label name + ast_break_label = Expr(:&&, :(val === nothing), :(break), Expr(:error, :error)) + @test !CoverageTools.has_embedded_errors(ast_break_label) + + # Test with other label names + ast_break_done = Expr(:block, Expr(:error, :done)) + @test !CoverageTools.has_embedded_errors(ast_break_done) + + # But real error messages (strings) should still be detected + ast_real_error = Expr(:block, Expr(:error, "syntax error")) + @test CoverageTools.has_embedded_errors(ast_real_error) end withenv("DISABLE_AMEND_COVERAGE_FROM_SRC" => nothing) do