Skip to content

Commit 875047f

Browse files
authored
Parser: Transform Action View tag helpers inside control flow blocks (#1447)
This pull request fixes a bug in the `action_view_helpers` parser analysis where tag helpers inside control flow blocks were not being transformed into `HTMLElementNode` AST representations. They remained as `ERBContentNode`, making them invisible to consumers relying on the transformed AST. For example, the following template with `action_view_helpers: true`: ```erb <% if condition? %> <%= tag.img src: "/image.png", alt: "Photo" %> <% end %> ``` Previously produced: ```js @ ERBIfNode (location: (1:0)-(3:9)) ├── statements: (1 item) │ └── @ ERBContentNode (location: (2:2)-(2:48)) │ ├── tag_opening: "<%=" (location: (2:2)-(2:5)) │ ├── content: " tag.img src: \"/image.png\", alt: \"Photo\" " (location: (2:5)-(2:46)) │ └── tag_closing: "%>" (location: (2:46)-(2:48)) ``` Now correctly produces: ```js @ ERBIfNode (location: (1:0)-(3:9)) ├── statements: (1 item) │ └── @ HTMLElementNode (location: (2:2)-(2:48)) │ ├── open_tag: │ │ └── @ ERBOpenTagNode (location: (2:2)-(2:48)) │ │ ├── tag_opening: "<%=" (location: (2:2)-(2:5)) │ │ ├── content: " tag.img src: \"/image.png\", alt: \"Photo\" " (location: (2:5)-(2:46)) │ │ ├── tag_closing: "%>" (location: (2:46)-(2:48)) │ │ ├── tag_name: "img" (location: (2:10)-(2:13)) │ │ └── children: (2 items) │ │ ├── @ HTMLAttributeNode (name: "src", value: "/image.png") │ │ └── @ HTMLAttributeNode (name: "alt", value: "Photo") │ │ │ ├── tag_name: "img" (location: (2:10)-(2:13)) │ ├── body: [] │ ├── close_tag: ∅ │ ├── is_void: true │ └── element_source: "ActionView::Helpers::TagHelper#tag" ```
1 parent 8477bea commit 875047f

7 files changed

+629
-15
lines changed

src/analyze/action_view/tag_helpers.c

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,21 +1062,8 @@ static AST_NODE_T* transform_link_to_helper(
10621062
return (AST_NODE_T*) element;
10631063
}
10641064

1065-
void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
1066-
if (!node || !context) { return; }
1067-
1068-
hb_array_T* array = NULL;
1069-
1070-
switch (node->type) {
1071-
case AST_DOCUMENT_NODE: array = ((AST_DOCUMENT_NODE_T*) node)->children; break;
1072-
case AST_HTML_ELEMENT_NODE: array = ((AST_HTML_ELEMENT_NODE_T*) node)->body; break;
1073-
case AST_HTML_OPEN_TAG_NODE: array = ((AST_HTML_OPEN_TAG_NODE_T*) node)->children; break;
1074-
case AST_HTML_ATTRIBUTE_VALUE_NODE: array = ((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children; break;
1075-
case AST_ERB_BLOCK_NODE: array = ((AST_ERB_BLOCK_NODE_T*) node)->body; break;
1076-
default: return;
1077-
}
1078-
1079-
if (!array) { return; }
1065+
void transform_tag_helper_array(hb_array_T* array, analyze_ruby_context_T* context) {
1066+
if (!array || !context) { return; }
10801067

10811068
for (size_t i = 0; i < hb_array_size(array); i++) {
10821069
AST_NODE_T* child = hb_array_get(array, i);
@@ -1231,6 +1218,45 @@ void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T*
12311218
}
12321219
}
12331220

1221+
void transform_tag_helper_blocks(const AST_NODE_T* node, analyze_ruby_context_T* context) {
1222+
if (!node || !context) { return; }
1223+
1224+
switch (node->type) {
1225+
case AST_DOCUMENT_NODE: transform_tag_helper_array(((AST_DOCUMENT_NODE_T*) node)->children, context); break;
1226+
case AST_HTML_ELEMENT_NODE: transform_tag_helper_array(((AST_HTML_ELEMENT_NODE_T*) node)->body, context); break;
1227+
case AST_HTML_CONDITIONAL_ELEMENT_NODE:
1228+
transform_tag_helper_array(((AST_HTML_CONDITIONAL_ELEMENT_NODE_T*) node)->body, context);
1229+
break;
1230+
case AST_HTML_OPEN_TAG_NODE:
1231+
transform_tag_helper_array(((AST_HTML_OPEN_TAG_NODE_T*) node)->children, context);
1232+
break;
1233+
case AST_HTML_ATTRIBUTE_VALUE_NODE:
1234+
transform_tag_helper_array(((AST_HTML_ATTRIBUTE_VALUE_NODE_T*) node)->children, context);
1235+
break;
1236+
case AST_ERB_BLOCK_NODE: transform_tag_helper_array(((AST_ERB_BLOCK_NODE_T*) node)->body, context); break;
1237+
case AST_ERB_IF_NODE: transform_tag_helper_array(((AST_ERB_IF_NODE_T*) node)->statements, context); break;
1238+
case AST_ERB_ELSE_NODE: transform_tag_helper_array(((AST_ERB_ELSE_NODE_T*) node)->statements, context); break;
1239+
case AST_ERB_UNLESS_NODE: transform_tag_helper_array(((AST_ERB_UNLESS_NODE_T*) node)->statements, context); break;
1240+
case AST_ERB_CASE_NODE:
1241+
transform_tag_helper_array(((AST_ERB_CASE_NODE_T*) node)->children, context);
1242+
transform_tag_helper_array(((AST_ERB_CASE_NODE_T*) node)->conditions, context);
1243+
break;
1244+
case AST_ERB_CASE_MATCH_NODE:
1245+
transform_tag_helper_array(((AST_ERB_CASE_MATCH_NODE_T*) node)->children, context);
1246+
transform_tag_helper_array(((AST_ERB_CASE_MATCH_NODE_T*) node)->conditions, context);
1247+
break;
1248+
case AST_ERB_WHEN_NODE: transform_tag_helper_array(((AST_ERB_WHEN_NODE_T*) node)->statements, context); break;
1249+
case AST_ERB_WHILE_NODE: transform_tag_helper_array(((AST_ERB_WHILE_NODE_T*) node)->statements, context); break;
1250+
case AST_ERB_UNTIL_NODE: transform_tag_helper_array(((AST_ERB_UNTIL_NODE_T*) node)->statements, context); break;
1251+
case AST_ERB_FOR_NODE: transform_tag_helper_array(((AST_ERB_FOR_NODE_T*) node)->statements, context); break;
1252+
case AST_ERB_BEGIN_NODE: transform_tag_helper_array(((AST_ERB_BEGIN_NODE_T*) node)->statements, context); break;
1253+
case AST_ERB_RESCUE_NODE: transform_tag_helper_array(((AST_ERB_RESCUE_NODE_T*) node)->statements, context); break;
1254+
case AST_ERB_ENSURE_NODE: transform_tag_helper_array(((AST_ERB_ENSURE_NODE_T*) node)->statements, context); break;
1255+
case AST_ERB_IN_NODE: transform_tag_helper_array(((AST_ERB_IN_NODE_T*) node)->statements, context); break;
1256+
default: break;
1257+
}
1258+
}
1259+
12341260
bool transform_tag_helper_nodes(const AST_NODE_T* node, void* data) {
12351261
analyze_ruby_context_T* context = (analyze_ruby_context_T*) data;
12361262

test/analyze/action_view/tag_helper/tag_test.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,5 +279,60 @@ class TagTest < Minitest::Spec
279279
assert_parsed_snapshot(template, action_view_helpers: true)
280280
assert_parsed_snapshot(template)
281281
end
282+
283+
test "tag.div inside if block" do
284+
assert_parsed_snapshot(<<~HTML, action_view_helpers: true)
285+
<% if condition? %>
286+
<%= tag.div id: "my-id" do %>
287+
Content
288+
<% end %>
289+
<% end %>
290+
HTML
291+
end
292+
293+
test "tag.img inside if block" do
294+
assert_parsed_snapshot(<<~HTML, action_view_helpers: true)
295+
<% if condition? %>
296+
<%= tag.img src: "/image.png", alt: "Photo" %>
297+
<% end %>
298+
HTML
299+
end
300+
301+
test "tag.div inside if/else branches" do
302+
assert_parsed_snapshot(<<~HTML, action_view_helpers: true)
303+
<% if condition? %>
304+
<%= tag.div id: "my-id" do %>
305+
Branch one
306+
<% end %>
307+
<% else %>
308+
<%= tag.span id: "my-id" do %>
309+
Branch two
310+
<% end %>
311+
<% end %>
312+
HTML
313+
end
314+
315+
test "tag.div inside case/when branches" do
316+
assert_parsed_snapshot(<<~HTML, action_view_helpers: true)
317+
<% case status %>
318+
<% when "active" %>
319+
<%= tag.div class: "active" do %>
320+
Active
321+
<% end %>
322+
<% when "inactive" %>
323+
<%= tag.div class: "inactive" do %>
324+
Inactive
325+
<% end %>
326+
<% end %>
327+
HTML
328+
end
329+
330+
test "tag.img inside each loop" do
331+
assert_parsed_snapshot(<<~HTML, action_view_helpers: true)
332+
<% @items.each do |item| %>
333+
<%= tag.img src: item.image_url, alt: item.name %>
334+
<% end %>
335+
HTML
336+
end
282337
end
283338
end

test/snapshots/analyze/action_view/tag_helper/tag_test/test_0038_tag.div_inside_if_block_08a29db5bf73b15e128a208d370a193c-ef4af315cb33925c38d24ea3c2e8a1cd.txt

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/snapshots/analyze/action_view/tag_helper/tag_test/test_0039_tag.img_inside_if_block_172928c57b9767e9ab8e8b140f070cf4-ef4af315cb33925c38d24ea3c2e8a1cd.txt

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)