Skip to content

Commit

Permalink
Correct file and line info in generated code (#15)
Browse files Browse the repository at this point in the history
Switch out macro-generated file and line info in template functions with the correct locations based on the parsed template code. Makes stacktraces to errors in template functions point to the correct lines in the right files instead of internal package code.
  • Loading branch information
MichaelHatherly authored Nov 26, 2023
1 parent f398edb commit 2f8c17b
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 35 deletions.
5 changes: 5 additions & 0 deletions src/HypertextTemplates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ end

export @template_str, @custom_element, @create_template_str, render, TemplateFileLookup

# Constants.

# Used for replacing package-specific file/line information in macro-generated code.
const SRC_DIR = @__DIR__

# Includes.

include("utilities.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/custom_element.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ macro custom_element(name)
attributes...,
)
end
end
end |> lln_replacer(file, line)
end

function custom_element(io::IO, name::String, slots::NamedTuple; attributes...)
Expand Down
4 changes: 2 additions & 2 deletions src/nodes/abstract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ function transform(n::EzXML.Node)
return Element(n)
end
else
return Text(cdata(n))
return Text(cdata(n), 0)
end
end
end

function nodeline(node::EzXML.Node)
node_ptr = node.ptr
@assert node_ptr != C_NULL
@assert unsafe_load(node_ptr).typ == EzXML.ELEMENT_NODE
@assert unsafe_load(node_ptr).typ in (EzXML.ELEMENT_NODE, EzXML.TEXT_NODE)
return unsafe_load(convert(Ptr{EzXML._Element}, node_ptr)).line
end

Expand Down
8 changes: 4 additions & 4 deletions src/nodes/element.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function expression(c::BuilderContext, e::Element)
$(attrs...),
)
print($(c.io), "/>")
end
end |> lln_replacer(c.file, e.line)
else
name = Symbol(e.name)
body = expression(c, e.body)
Expand All @@ -122,7 +122,7 @@ function expression(c::BuilderContext, e::Element)
print($(c.io), ">")
$(body)
print($(c.io), "</", $(e.name), ">")
end
end |> lln_replacer(c.file, e.line)
end
else
name = Symbol(e.name)
Expand All @@ -132,7 +132,7 @@ function expression(c::BuilderContext, e::Element)
$(body)
return nothing
end
end)
end) |> lln_replacer(c.file, e.line)
end
slots = Expr(
:tuple,
Expand All @@ -141,7 +141,7 @@ function expression(c::BuilderContext, e::Element)
(builder(Symbol(k), expression(c, v)) for (k, v) in e.slots)...,
),
)
:($(name)($(c.io), $(slots); $(attrs...)))
:($(name)($(c.io), $(slots); $(attrs...))) |> lln_replacer(c.file, e.line)
end
end

Expand Down
8 changes: 5 additions & 3 deletions src/nodes/for.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ struct For <: AbstractNode
item::String
index::Union{String,Nothing}
body::Vector{AbstractNode}
line::Int

function For(iter, item, index, body)
function For(iter, item, index, body, line)
return new(
_restore_special_symbols(iter),
_restore_special_symbols(item),
_restore_special_symbols(index),
body,
line,
)
end
end
Expand All @@ -25,7 +27,7 @@ function For(n::EzXML.Node)
iter = key_default(attrs, "iter")
item = key_default(attrs, "item")
index = key_default(attrs, "index")
return For(iter, item, index, transform(EzXML.nodes(n)))
return For(iter, item, index, transform(EzXML.nodes(n)), nodeline(n))
else
error("expected a '<for>' tag, found: $tag")
end
Expand All @@ -46,5 +48,5 @@ function expression(c::BuilderContext, f::For)
for $(item) in $(iter)
$(body)
end
end
end |> lln_replacer(c.file, f.line)
end
9 changes: 6 additions & 3 deletions src/nodes/function.jl
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ struct TemplateFunction
body::Vector{AbstractNode}
file::String
mod::Module
line::Int

function TemplateFunction(name, html, props, body, file, mod)
function TemplateFunction(name, html, props, body, file, mod, line)
if name in VALID_HTML_ELEMENTS
error(
"cannot name a template function the same as a valid HTML element name: $name",
Expand All @@ -253,7 +254,7 @@ struct TemplateFunction
"cannot name a template function the same as a reserved element name: $name",
)
end
return new(_restore_special_symbols(name), html, props, body, file, mod)
return new(_restore_special_symbols(name), html, props, body, file, mod, line)
end
end

Expand All @@ -273,6 +274,7 @@ function TemplateFunction(n::EzXML.Node, file::String, mod::Module)
transform(EzXML.nodes(n)),
file,
mod,
nodeline(n),
)
else
error(
Expand All @@ -291,6 +293,7 @@ function TemplateFunction(n::EzXML.Node, file::String, mod::Module)
transform(EzXML.nodes(n)),
file,
mod,
nodeline(n),
)
else
error("expected a '<function>' or '<html>' tag, found: $tag")
Expand Down Expand Up @@ -396,7 +399,7 @@ function expression(c::TemplateFunction)::Expr
end
end
end
return Base.remove_linenums!(expr)
return expr |> lln_replacer(c.file, c.line)
end

# Decide whether to recompile the template or not.
Expand Down
9 changes: 5 additions & 4 deletions src/nodes/julia.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ const JULIA_TAG = "julia"

struct Julia <: AbstractNode
value::String
line::Int

function Julia(value)
return new(_restore_special_symbols(value))
function Julia(value, line)
return new(_restore_special_symbols(value), line)
end
end

Expand All @@ -13,7 +14,7 @@ function Julia(n::EzXML.Node)
if length(attrs) == 1
(name, value), = attrs
if name == "value"
return Julia(isempty(value) ? name : value)
return Julia(isempty(value) ? name : value, nodeline(n))
else
error("expected a 'value' attribute for a julia node.")
end
Expand All @@ -26,7 +27,7 @@ function expression(c::BuilderContext, j::Julia)
expr = Meta.parse(j.value)
quote
$(escape_html)($(c.io), $(expr))
end
end |> lln_replacer(c.file, j.line)
end

function escape_html(io::IO, value)
Expand Down
12 changes: 7 additions & 5 deletions src/nodes/match.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ const CASE_TAG = "case"
struct Case
when::String
body::Vector{AbstractNode}
line::Int

function Case(when, body)
return new(_restore_special_symbols(when), body)
function Case(when, body, line)
return new(_restore_special_symbols(when), body, line)
end
end

Expand All @@ -17,6 +18,7 @@ const MATCH_TAG = "match"
struct Match <: AbstractNode
value::String
cases::Vector{Case}
line::Int

function Match(n::EzXML.Node)
tag = EzXML.nodename(n)
Expand All @@ -31,7 +33,7 @@ struct Match <: AbstractNode
if length(attrs) == 1
when = key_default(attrs, "when")
body = transform(EzXML.nodes(each))
push!(cases, Case(when, body))
push!(cases, Case(when, body, nodeline(each)))
else
error("'match' nodes require a single attribute.")
end
Expand All @@ -42,7 +44,7 @@ struct Match <: AbstractNode
# Silently drops text nodes found in the match block.
end
end
return new(value, cases)
return new(value, cases, nodeline(n))
else
error("expected a '<match>' tag, found: $tag")
end
Expand All @@ -64,5 +66,5 @@ function expression(c::BuilderContext, s::Match)
$(Deps.Match).@match $(value) begin
$(cases...)
end
end
end |> lln_replacer(c.file, s.line)
end
9 changes: 5 additions & 4 deletions src/nodes/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ struct Show <: AbstractNode
when::String
body::Vector{AbstractNode}
fallback::Vector{AbstractNode}
line::Int

function Show(when, body, fallback)
return new(_restore_special_symbols(when), body, fallback)
function Show(when, body, fallback, line)
return new(_restore_special_symbols(when), body, fallback, line)
end
end

Expand All @@ -17,7 +18,7 @@ function Show(n::EzXML.Node)
haskey(attrs, "when") || error("expected a 'when' attribute for a 'show' node.")
when = key_default(attrs, "when")
nodes, fallback = split_fallback(n)
return Show(when, transform(nodes), transform(EzXML.nodes(fallback)))
return Show(when, transform(nodes), transform(EzXML.nodes(fallback)), nodeline(n))
else
error("expected a '<show>' tag, found: $tag")
end
Expand All @@ -36,5 +37,5 @@ function expression(c::BuilderContext, s::Show)
else
$(fallback)
end
end
end |> lln_replacer(c.file, s.line)
end
11 changes: 6 additions & 5 deletions src/nodes/slot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ const UNNAMED_SLOT = "#unnamed_slot#"

struct Slot <: AbstractNode
name::Union{String,Nothing}
line::Int

function Slot(name)
return new(_restore_special_symbols(name))
function Slot(name, line)
return new(_restore_special_symbols(name), line)
end
end

function Slot(n::EzXML.Node)
attrs = attributes(n)
if isempty(attrs)
return Slot(nothing)
return Slot(nothing, nodeline(n))
else
if length(attrs) == 1
(name, value), attrs... = attrs
if isempty(value)
return Slot(name)
return Slot(name, nodeline(n))
else
error("slot name attributes should be valueless, got: $value.")
end
Expand All @@ -33,5 +34,5 @@ function expression(c::BuilderContext, s::Slot)
else
name = Symbol(s.name)
:($(c.slots).$(name)($(c.io)))
end
end |> lln_replacer(c.file, s.line)
end
9 changes: 5 additions & 4 deletions src/nodes/text.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
struct Text <: AbstractNode
content::String
line::Int

function Text(content::String)
return new(_restore_special_symbols(content))
function Text(content::String, line)
return new(_restore_special_symbols(content), line)
end
end

Expand All @@ -16,7 +17,7 @@ function Text(n::EzXML.Node)
right = rstrip(content)
content = right == content ? content : "$right "

return Text(all(isspace, content) ? "" : content)
return Text(all(isspace, content) ? "" : content, nodeline(n))
else
error("expected a text node, found: $(EzXML.nodename(n))")
end
Expand All @@ -26,6 +27,6 @@ function expression(c::BuilderContext, t::Text)
if isempty(t.content)
return nothing
else
:(print($(c.io), $(t.content)))
:(print($(c.io), $(t.content))) |> lln_replacer(c.file, t.line)
end
end
21 changes: 21 additions & 0 deletions src/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,24 @@ function split_fallback(n::EzXML.Node)
end
return nodes, fallback
end

function lln_replacer(file::Union{String,Symbol}, line::Integer)
file = Symbol(file)
function (ex::Expr)
if line > 0
MacroTools.postwalk(ex) do each
if isa(each, LineNumberNode)
if startswith(string(each.file), SRC_DIR)
return LineNumberNode(line, file)
else
return each
end
else
return each
end
end
else
return ex
end
end
end
Loading

0 comments on commit 2f8c17b

Please sign in to comment.