Skip to content

DebugVisitor ​

Annotates the rendered output with where it came from, so a rendered element can be traced back to the tag that produced it.

ruby
require "herb/engine/visitors/debug_visitor"

Herb::Engine.new(source, visitors: [Herb::Engine::DebugVisitor.new])

The first top-level element of a template carries which template it is, and each ERB output tag is wrapped in a <span style="display: contents"> carrying where in that template it was written:

AttributeOnSays
data-herb-debug-file-relative-pathelementwhich template this is
data-herb-debug-file-nameelementits basename
data-herb-debug-file-full-pathelementits full path
data-herb-debug-outline-typebothwhether it is a view, a partial, or an ERB output
data-herb-debug-attach-to-parentelementthat the template has more than one root
data-herb-debug-insertedspanthat this span is Herb's and not the author's
data-herb-debug-erbspanthe tag as it was written
data-herb-debug-line, -columnspanwhere that tag is
data-herb-debug-nodebothwhich render this was, with node: true

Tracing rendered output back to a tag ​

<%= link_to "Abc", "" %> produces an <a> that says nothing about where it came from. Wrapping it says so:

html
<span
  data-herb-debug-inserted="true"
  data-herb-debug-line="2"
  data-herb-debug-column="7"
  data-herb-debug-erb="&lt;%= link_to &quot;Abc&quot;, &quot;&quot; %&gt;"
  style="display: contents;"
>
  <a href="">Abc</a>
Attribute `href` must not be empty. Either provide a meaningful value or remove the attribute entirely. (html-no-empty-attributes)
</span>

Anything looking at the rendered page, such as a linter running over the response, walks up from the element it has a finding about and takes the first marker it meets:

Nearest markerWhat it can say
[data-herb-debug-inserted]the tag, and its line and column
[data-herb-debug-file-relative-path]only the template
neithernothing

Not every element ends up under a marker. An element written as plain HTML has no tag to name, an ERB tag inside an attribute value cannot be wrapped in a span, a template with more than one root only marks the first, and helpers that take a block are skipped. Treat a missing marker as unattributed rather than assuming coverage.

node: true adds the render as well, which needs InstrumentationVisitor in the same stack to have anything to report:

ruby
Herb::Engine.new(source, visitors: [
  Herb::Engine::DebugVisitor.new(node: true),
  Herb::Engine::InstrumentationVisitor.new
])

Without it the markers say only where in a file something was written, so a partial rendered three times puts three identical ones in the page. With it each carries the render it belongs to, which is what tells them apart.

The wrapper is a real cost. A <span> is not valid everywhere an ERB tag can appear, <ul> being the obvious case, so a strict linter reading the rendered page will have findings about Herb's own instrumentation.

Released under the MIT License.