Skip to content

Find violations in ERB templates - #46

Merged
dduugg merged 5 commits into
mainfrom
erb-support
Oct 12, 2026
Merged

dduugg merged 5 commits into
mainfrom
erb-support

Conversation

@dduugg

@dduugg dduugg commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1

Problem

FileCache#get_file replaced every file that isn't .rb or .rake with the placeholder string "x" before parsing. So for a violation in an ERB template, count returned 0 and every sources* method left it out, with no warning. The same happened to the other files packwerk parses as Ruby: .builder, .gemspec, .ru, Gemfile and Rakefile.

Approach

ERB templates go through Herb.extract_ruby, which blanks out everything except the Ruby code in place, so line numbers still match the template. Each tag ends with ;, so two tags on one line stay separate statements, and ERB comments and <%% escapes are dropped. The rest of the pipeline (receiver chains, anonymization, line numbers) works unchanged, and sources_with_contexts still shows lines from the template itself.

Why herb: packwerk 3.x parses ERB with better_html, which is deprecated in favor of herb, and packwerk's move away from it (Shopify/packwerk#447) uses the same Herb.extract_ruby call. Packwerk's current ERB parser couldn't be reused anyway, because it discards source positions.

Other changes:

  • RUBY_FILE now matches every file type packwerk parses as Ruby, from Packwerk::Parsers::Factory.
  • A file that can't be parsed now prints Could not parse <file>:<line> (<syntax error>), so its violations are not included and contributes no references. In an ERB template the line is the template line. Previously that raised a Sorbet TypeError, as did a file with no code at all (e.g. only comments). Files with no code now have no references and no warning.
  • Removed the unused Violation::RUBY_FILE and its "does not play nicely with ERB files" comment.
  • A constant that is a file's only code (e.g. a partial that's just <%= Billing::Invoice %>) is now found. The search skipped the root of the AST.
  • sources_with_contexts shows the right lines. It used the 1-based line number as a 0-based index, so the window ran from 2 lines before to 4 after, and for a reference on line 1 or 2 the start went negative and wrapped to the end of the file. ERB partials often reference constants near the top, so this would come up a lot. Violations#sources_with_contexts also passes its start_offset: and end_offset: through now; it ignored them.
  • Bumps the version to 0.1.26.

New dependency

  • herb >= 0.9: 0.9.0 added the extract_ruby keyword options used here, and the new specs pass on 0.9.0 as well as 0.11.0. The options are passed explicitly rather than relying on herb's pre-1.0 defaults.
  • herb is a native extension. It ships precompiled gems for macOS and Linux (arm and x86, gnu and musl), and the lockfile picks up the same platforms as nokogiri.
  • Adds the tapioca-generated sorbet/rbi/gems/herb@0.11.0.rbi.

Testing

  • spec/query_packwerk/file_cache_spec.rb covers references and their template line numbers in an ERB template (a block helper, two tags on one line, a comment followed by code on the same line, an escaped tag), non-HTML ERB, a constant that is a template's only code, files with no code, missing files, a syntax error (warned about once per file), each Ruby file type, and a file packwerk doesn't parse.
  • spec/query_packwerk/violation_spec.rb covers count and sources_with_contexts for a violation in an ERB template, including the lines shown around each reference and a reference on the first line. violations_spec.rb covers the offsets passed to Violations#sources_with_contexts.
  • 16 of the 21 new examples fail against main. The other 5 pin behavior that already worked (.rake, missing files, a template with no Ruby, Gemfile.lock). Flipping any of the three extract_ruby options, or removing the no-AST fallback, fails at least one.
  • Ran against the ERB violations of a large Rails monolith: every ERB file listed in its package_todo.yml files now resolves to at least one reference (previously none), and every reported line number points at the template line containing the reported source.
  • bundle exec rspec (90 examples), rubocop and srb tc pass. The new specs also pass locally on Ruby 3.3 and 3.4.

ERB templates were replaced with a placeholder before parsing, so
`count` returned 0 for their violations and every `sources*` method
left them out. The same happened to the other files packwerk parses as
Ruby (.builder, .gemspec, .ru, Gemfile, Rakefile).

- Extract the Ruby from ERB templates with herb, blanking the HTML in
  place so line numbers still match the template. Context lines still
  come from the template itself.
- Parse every file type that packwerk parses as Ruby.
- Warn when a file can't be parsed. Files that can't be parsed, or that
  have no code, now have no references instead of raising a TypeError.

Fixes #1
@dduugg
dduugg requested a review from a team as a code owner October 11, 2026 23:06
@github-project-automation github-project-automation Bot moved this to Triage in Modularity Oct 11, 2026
…be parsed

- Search the root of a file's AST too. A template whose only Ruby is a
  bare constant, like `<%= Billing::Invoice %>`, had no references.
- Include the line and message of the first syntax error in the
  "Could not parse" warning. In ERB templates the line is the template
  line, so the problem can be found without seeing the extracted Ruby.
- Cover .rake, non-HTML ERB, missing files, files packwerk doesn't
  parse, and warning only once per file.
The line number was used as a 0-based index, so the window ran from 2
lines before the reference to 4 after, and the label didn't match the
lines shown. For a reference in the first 2 lines of a file, the start
went negative and wrapped to the end of the file, showing unrelated
lines or none. ERB partials often reference constants near the top, so
this would come up a lot with ERB support.

Also pass the offsets through from Violations#sources_with_contexts,
which ignored them.
`all` passes on an empty array, so the example also passed on main,
where ERB templates had no contexts at all.
@dduugg dduugg mentioned this pull request Oct 12, 2026
@dduugg
dduugg merged commit 7fc40c3 into main Oct 12, 2026
11 checks passed
@dduugg
dduugg deleted the erb-support branch October 12, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Violation doesn't handle ERB templates

1 participant