Skip to content

Ruby: Add RuboCop support for Ruby in ERB templates - #2260

Draft
joelhawksley wants to merge 22 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley-add-erb-rubocop
Draft

joelhawksley wants to merge 22 commits into
marcoroth:mainfrom
joelhawksley:joelhawksley-add-erb-rubocop

Conversation

@joelhawksley

@joelhawksley joelhawksley commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

This adds rubocop-herb, a RuboCop plugin that runs a project’s configured RuboCop cops against Ruby embedded in .erb and .herb templates.

The plugin uses Herb's extract_ruby binding to produce position-preserving Ruby for the entire template and passes it through RuboCop’s native ruby_extractors API. This preserves control flow and variable usage across ERB tags while mapping offenses and autocorrections back to their original template locations. Existing configuration continues to work, including custom cops, inherited configuration, exclusions, target Ruby versions, --only, and safe or unsafe autocorrection.

Cops that depend on physical Ruby file contents or obscured template layout are excluded for template files by default. The integration lives under gems/rubocop-herb and is enabled through the standard RuboCop plugin configuration:

bundle add rubocop-herb
plugins:
  - rubocop-herb

This PR implements responsibility (2) from the review discussion. Running Herb’s own linter rules as part of RuboCop remains separate future work for the herb-linter integration.

@github-actions github-actions Bot added documentation Improvements or additions to documentation ruby Ruby source for the gem and its libraries typescript TypeScript source across the javascript/ packages rubygem The herb RubyGem and its packaging config Herb configuration file handling and config options rbs RBS type signatures in sig/ rust Rust bindings and the Herb Rust crate labels Aug 17, 2026
@marcoroth
marcoroth marked this pull request as draft August 21, 2026 16:47

@marcoroth marcoroth left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this started!

I reserved the rubocop-herb gem name to run the Herb Linter as part of a regular RuboCop run, but I could also see that gem to have tighter RuboCop integration in the future for what you are proposing here.

I think if we'd want to land something like this feature, it probably belongs into rubocop-herb.

@marcoroth marcoroth changed the title Add RuboCop support for Ruby in ERB templates Ruby: Add RuboCop support for Ruby in ERB templates Sep 12, 2026
@joelhawksley

Copy link
Copy Markdown
Contributor Author

I reserved the rubocop-herb gem name to run the Herb Linter as part of a regular RuboCop run, but I could also see that gem to have tighter RuboCop integration in the future for what you are proposing here.

I am suggesting the opposite (I think?): we should run RuboCop as part of Herb, not the other way around. I don't know if the semantics matter in this case though.

I think if we'd want to land something like this feature, it probably belongs into rubocop-herb.

@marcoroth my mental model of the Herb linter has been that it is meant to replace ERB Lint out of the box, and ERB Lint supports RuboCop as one of its linters: https://github.com/Shopify/erb_lint#linters. I think dropping ERB Lint (which doesn't get much attention these days) is a good value proposition for adopting Herb.

I'd like to drop our use of ERB Lint at GitHub, but we use the ERB Lint RuboCop linter, so this is a blocker. Which means I'm motivated to get rubocop-herb working, if you're looking for someone to take the work on.

@marcoroth

Copy link
Copy Markdown
Owner

Yeah, I think that rubocop-herb would have two responsibilities:

  1. to run Herb's own Linter rules as part of a RuboCop run (which is most likely going to be powered by the herb-linter gem)
  2. to run the configured RuboCop rules (minus the ones that don't make sense) on the Ruby inside the ERB templates

This PR is providing the work for 2). So, if you'd want to migrate the mechanics of this pull request into gems/rubocop-herb/* and cover the 2) aspect I'd be more than happy to get that going. Thank you! 🙏🏼

@joelhawksley
joelhawksley marked this pull request as ready for review September 18, 2026 17:23
@joelhawksley

Copy link
Copy Markdown
Contributor Author

Alright @marcoroth, I've gotten this working to the point of catching RuboCop errors in .html.erb files in other projects: discourse/discourse#43828. Let me know what you think ❤️

ZogStriP pushed a commit to discourse/discourse that referenced this pull request Sep 18, 2026
As part of testing marcoroth/herb#2260, I ran
the PR against this repository. `rubocop-herb` found and autofixed
RuboCop rule violations in six .html.erb files, using the existing
RuboCop rule configuration for this project.
@joelhawksley
joelhawksley force-pushed the joelhawksley-add-erb-rubocop branch from d1d11f6 to 5705225 Compare October 1, 2026 19:36
joelhawksley and others added 3 commits October 1, 2026 13:51
Replace the duplicate Ruby source builder with Herb.extract_ruby while retaining range collection for safe autocorrection. Normalize masked multibyte characters to RuboCop's character offsets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Inline the minimal RuboCop extraction hook around Herb.extract_ruby and remove the standalone extractor, range collector, and autocorrection restriction patches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Refresh the path gem lock entry for Herb 0.11.0 and align extraction tests with the current Herb binding behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@joelhawksley
joelhawksley force-pushed the joelhawksley-add-erb-rubocop branch from 4472bc2 to 75e2ebe Compare October 1, 2026 21:06
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
Comment thread gems/rubocop-herb/config/default.yml Resolved
joelhawksley and others added 3 commits October 2, 2026 10:39
Signed-off-by: Joel Hawksley <joel@hawksley.org>
Register every .erb and .herb file for RuboCop extraction, apply template-specific exclusions consistently, and cover non-HTML ERB and Herb files in integration tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Register the Herb extractor directly through RuboCop::Runner.ruby_extractors, matching RuboCop's established template plugins.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep invalid extracted sources in RuboCop's pipeline so Lint/Syntax can report malformed Ruby, while continuing to skip valid template fragments with no AST.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@joelhawksley

Copy link
Copy Markdown
Contributor Author

Moving back to draft while I look at byte vs. character position logic gaps between RuboCop and Herb ❤️

@joelhawksley
joelhawksley marked this pull request as draft October 6, 2026 17:20
joelhawksley and others added 2 commits October 6, 2026 11:39
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
joelhawksley and others added 2 commits October 6, 2026 11:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added c C source for the core parser, lexer, and AST node @herb-tools/node native Node.js addon node-wasm @herb-tools/node-wasm WebAssembly parser for Node.js java Java bindings and the org.herb package labels Oct 21, 2026
joelhawksley and others added 4 commits October 21, 2026 13:18
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 62861432-cba5-4e6b-b0fe-fad60ea33328
Herb.extract_ruby now preserves Unicode character positions, so the
plugin no longer needs its own position-preserving workaround.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 62861432-cba5-4e6b-b0fe-fad60ea33328
- Revert extract_ruby Unicode position change; align positions in the plugin
- Drop offenses anchored in markup and corrections that would edit markup
- Register the extractor once; single source of truth for template extensions
- Remove nonexistent Layout/LeadingEmptyLine, exclude cops with ERB false positives
- Rewrite tests (default config, markup protection, cop existence) and run them with rake test

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e554b8ed-a44a-4452-9ccf-06e6e96101dc
@github-actions github-actions Bot added parser HTML+ERB parser engine Herb engine and Rails template compilation labels Oct 21, 2026
@joelhawksley
joelhawksley force-pushed the joelhawksley-add-erb-rubocop branch from 319ea1a to 7a8b82f Compare October 21, 2026 21:22
marcoroth pushed a commit that referenced this pull request Oct 28, 2026
Bumps RuboCop from 1.84.0 to 1.92.0.

1.84.0 has an autocorrect bug: with the `parser_whitequark` engine,
`Style/RedundantParentheses` rewrites `(x rescue false) || y` to `x
rescue false || y`, which changes behavior. It was fixed in 1.89.0 by
rubocop/rubocop#15472. This was found while testing #2260.

New offenses from the upgrade:
- `Style/SelectByKind`: autocorrected `select { |x| x.is_a?(Foo) }` to
`grep(Foo)`.
- `Lint/InterpolationCheck`: excluded for `test/`, where single-quoted
ERB strings containing `#{}` are intentional.
- `Style/OneClassPerFile`: excluded for the Rust analysis Ruby fixtures.

`bundle exec rubocop` reports no offenses, and `rake test:all` passes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c C source for the core parser, lexer, and AST config Herb configuration file handling and config options documentation Improvements or additions to documentation engine Herb engine and Rails template compilation java Java bindings and the org.herb package node @herb-tools/node native Node.js addon node-wasm @herb-tools/node-wasm WebAssembly parser for Node.js parser HTML+ERB parser rbs RBS type signatures in sig/ ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging rust Rust bindings and the Herb Rust crate typescript TypeScript source across the javascript/ packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants