Skip to content

Analysis: Improve how render calls resolve to partials - #2229

Open
marcoroth wants to merge 27 commits into
mainfrom
partial-index-discovery
Open

Analysis: Improve how render calls resolve to partials#2229
marcoroth wants to merge 27 commits into
mainfrom
partial-index-discovery

Conversation

@marcoroth

Copy link
Copy Markdown
Owner

This pull request teaches the partial index about Rails' view_paths instead of a single guessed root, and stops discarding templates that Rails will render but the globs do not match.

Running herb actionview check against a large application reported 118 unresolved render calls. Most of them pointed at partials that exist on disk. Two causes, both ours.

The first is a template that carries a handler but no format. Rails reads a filename as name.format.handler, and the format segment is optional, so _icon.erb is a real partial that Rails serves for any format. PartialResolution::EXTENSIONS already agrees, and the filesystem walk already finds it. PartialIndex#build_with_config then replaced that walk with the linter's file list, whose globs enumerate HTML formats (**/*.html.erb, **/*.html, **/*.rhtml), and every such partial vanished from the index.

The config's globs still decide what the linter and formatter look at. The index just keeps what the walk found.

The second is that a Rails application has an ordered list of view paths, not one. The application comes first, then each engine, then anything pushed on at runtime, and a name resolves against each in turn. The index held a single view_root and derived every partial name relative to it, so a template under an engine could not be named at all and was silently skipped.

PartialIndex now takes the list, in precedence order:

PartialIndex::new(view_roots: &[PathBuf], templates: Vec<String>)
PartialIndex.new(view_roots, templates)
new PartialIndex(viewRoots: string[], declarations)

The shared primitive reports which root matched, so position can order two partials that share a name. An earlier view path shadows a later one, which extension precedence alone could not express, and a sibling render resolves within the root that owns the calling template.

check gained a section for templates whose filename omits the format, since these resolve in Rails but are easy to write by accident:

 Templates without a format:
 Rails reads a template filename as `name.format.handler`. Without a format it matches every one.

   ! app/views/shared/_icon.erb
   ! app/views/layouts/admin.erb
   ! app/views/user_mailer/signup.erb

Both entry points and partials appear, because a formatless mailer view is a template in both directions: we were failing to resolve into these files and failing to walk out of them.

render "layouts/page.html.erb" names a partial that exists, but the extension is part of the name, so appending another one never matched. The file was reported as unresolved at the call site and as unused at the same time. Resolution now strips a known template extension from the name first.

@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries typescript TypeScript source across the javascript/ packages rbs RBS type signatures in sig/ rubygem The herb RubyGem and its packaging rust Rust bindings and the Herb Rust crate analysis labels Aug 14, 2026
@nx-cloud

nx-cloud Bot commented Aug 14, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 07e02b9

Command Status Duration Result
nx run-many -t test --all --parallel --exclude=... ✅ Succeeded 3m 26s View ↗
nx run-many -t build --all --exclude=herb-langu... ✅ Succeeded 55s View ↗
nx build @herb-tools/tailwind-class-sorter ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-15 16:18:25 UTC

@marcoroth marcoroth changed the title Analysis: Resolve partials against Rails' ordered view paths Analysis: Improve how render calls resolve to partials Aug 14, 2026
@marcoroth
marcoroth force-pushed the partial-index-discovery branch from 67ca7d1 to 3cc85b3 Compare August 14, 2026 04:57
@github-actions github-actions Bot added linter @herb-tools/linter for HTML+ERB templates language-server @herb-tools/language-server and Language Server Protocol support linter-rule Individual linter rules and their documentation language-service @herb-tools/language-service HTML+ERB language service labels Aug 14, 2026
@marcoroth
marcoroth force-pushed the partial-index-discovery branch from 6433449 to 2845ba4 Compare August 14, 2026 06:02
@github-actions

Copy link
Copy Markdown

🌿 Interactive Playground and Documentation Preview

A preview deployment has been built for this pull request. Try out the changes live in the interactive playground:


🌱 Grown from commit 07e02b9

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

Labels

analysis language-server @herb-tools/language-server and Language Server Protocol support language-service @herb-tools/language-service HTML+ERB language service linter @herb-tools/linter for HTML+ERB templates linter-autofix Linter autofix behavior linter-rule Individual linter rules and their documentation 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.

1 participant