Skip to content

ActionView: Dependency analysis, state tracing, reverse node index - #1667

Draft
marcoroth wants to merge 1 commit into
mainfrom
dependency-tracking
Draft

ActionView: Dependency analysis, state tracing, reverse node index#1667
marcoroth wants to merge 1 commit into
mainfrom
dependency-tracking

Conversation

@marcoroth

@marcoroth marcoroth commented Apr 21, 2026

Copy link
Copy Markdown
Owner

This pull request introduces template dependency analysis, a system that statically traces which pieces of state (instance variables, constants, locals) each template depends on, how that state flows through render calls into partials, and, down to the AST node, which parts of a template are affected when a given piece of state changes.

Motivation

The render graph introduced in #1663 answers "which templates render which partials?". This pull request answers the next question reactivity needs: "when @user changes, which templates, and which exact nodes inside them, need to re-render?"

This is the dependency-tracking foundation for reactive rendering: a reverse index from state to affected templates and nodes. When state changes at runtime, or a template changes on disk during development, this index tells us precisely what to re-evaluate instead of re-rendering the world.

The node_path coordinates it produces are the same child-index paths the Syntax Tree Diff Engine (#1518) emits, so diff operations and dependency lookups address the tree in the same coordinate space.

How it works

Herb::ActionView::TemplateDependencies analyzes each template with three visitors:

  1. DependencyCollector walks the HTML+ERB AST and, via Prism, the embedded Ruby, collecting instance variables, constants, declared strict locals, received locals, render calls (with their locals and collections), helper calls (checked against the ActionView helper registry plus scanned app/helpers/), and unknown calls.

  2. NodeDependencyCollector finds the specific AST nodes affected by a given state name, text content, expressions, conditionals, attributes, and render calls, each reported with its node_path (child indices from the document root) and source location.

  3. LocalScanner pre-scans templates for local assignments so locals aren't misclassified as unknown calls.

State flow is traced through the render graph: affected_templates starts at an entry point, follows render calls whose locals or collections reference the state (including renamed locals, render "card", user: @author flows @author into the partial as user), and returns every reachable template that carries it.

API

dependencies = Herb::ActionView::TemplateDependencies.new("/path/to/project")

Full manifest for one template

result = dependencies.analyze("app/views/posts/show.html.erb")
result.instance_variables  # => ["@post", "@comments"]
result.locals_received     # => { "author" => "@post.user" }
result.render_calls        # => [{ partial: "comments/comment", locals: {...}, collection: ... }]

Which templates are affected when @post changes?

dependencies.affected_templates("app/views/posts/show.html.erb", "@post")
# => ["app/views/posts/show.html.erb", "app/views/comments/_comment.html.erb"]

Which nodes inside a template are affected?

dependencies.affected_nodes("app/views/posts/show.html.erb", "@post")
# => [{ node_path: [0, 1], type: :text_content, expression: "@post.title", location: "3:8" }, ...]

Reverse index: state name => affected nodes

dependencies.dependency_index("app/views/posts/show.html.erb")

CLI

Manifest for all templates or one template:

bundle exec herb actionview dependencies                               
bundle exec herb actionview dependencies app/views/posts/show.html.erb

herb actionview check now also flags dependency warnings:

  • Instance variables in partials, partials should receive data as locals so state flow stays traceable for reactivity.
  • Unknown method calls, methods not found in the ActionView helper registry or app/helpers/.

@github-actions github-actions Bot added ruby Ruby source for the gem and its libraries rbs RBS type signatures in sig/ rubygem The herb RubyGem and its packaging labels Apr 21, 2026
@pkg-pr-new

pkg-pr-new Bot commented Apr 21, 2026

Copy link
Copy Markdown
npx https://pkg.pr.new/@herb-tools/formatter@1667
npx https://pkg.pr.new/@herb-tools/language-server@1667
npx https://pkg.pr.new/@herb-tools/linter@1667

commit: 3445e59

@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 3445e59

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

Labels

rbs RBS type signatures in sig/ ruby Ruby source for the gem and its libraries rubygem The herb RubyGem and its packaging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant