Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,9 @@ describe("ActionViewTagHelperToHTMLRewriter", () => {
)
})

test("image_tag with ruby expression source wraps in image_path", () => {
test("image_tag with ruby expression source dispatches on the value", () => {
expect(transform('<%= image_tag user.avatar %>')).toBe(
'<img src="<%= image_path(user.avatar) %>" />'
'<img src="<%= (user.avatar).then { |value| value.is_a?(String) || value.is_a?(Symbol) ? image_path(value) : polymorphic_url(value) } %>" />'
)
})

Expand Down Expand Up @@ -859,9 +859,9 @@ describe("ActionViewTagHelperToHTMLRewriter", () => {
)
})

test("image_tag with instance variable method wraps in image_path", () => {
test("image_tag with instance variable method dispatches on the value", () => {
expect(transform('<%= image_tag @post.cover_image %>')).toBe(
'<img src="<%= image_path(@post.cover_image) %>" />'
'<img src="<%= (@post.cover_image).then { |value| value.is_a?(String) || value.is_a?(Symbol) ? image_path(value) : polymorphic_url(value) } %>" />'
)
})

Expand Down
27 changes: 27 additions & 0 deletions src/analyze/action_view/image_tag.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ bool image_tag_source_is_url(const char* source, size_t length) {
return false;
}

char* wrap_in_image_source_dispatch(
const char* source,
size_t source_length,
const char* path_options,
hb_allocator_T* allocator
) {
hb_buffer_T buffer;
hb_buffer_init(&buffer, source_length + 160, allocator);

hb_buffer_append(&buffer, "(");
hb_buffer_append_with_length(&buffer, source, source_length);
hb_buffer_append(&buffer, ").then { |value| value.is_a?(String) || value.is_a?(Symbol)");
hb_buffer_append(&buffer, " ? image_path(value");

if (path_options && strlen(path_options) > 0) {
hb_buffer_append(&buffer, ", ");
hb_buffer_append(&buffer, path_options);
}

hb_buffer_append(&buffer, ") : polymorphic_url(value) }");

char* result = hb_allocator_strdup(allocator, hb_buffer_value(&buffer));
hb_buffer_free(&buffer);

return result;
}

char* wrap_in_image_path(
const char* source,
size_t source_length,
Expand Down
8 changes: 5 additions & 3 deletions src/analyze/action_view/tag_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ extern char* wrap_in_url_for(const char*, size_t, hb_allocator_T*);
extern char* extract_link_to_href(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
extern bool detect_turbo_frame_tag(pm_call_node_t*, pm_parser_t*);
extern char* extract_turbo_frame_tag_id(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
extern bool turbo_frame_tag_id_is_static(pm_call_node_t*);
extern bool detect_javascript_include_tag(pm_call_node_t*, pm_parser_t*);
extern char* extract_javascript_include_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
extern char* wrap_in_javascript_path(const char*, size_t, const char*, hb_allocator_T*);
extern bool javascript_include_tag_source_is_url(const char*, size_t);
extern bool detect_image_tag(pm_call_node_t*, pm_parser_t*);
extern char* extract_image_tag_src(pm_call_node_t*, pm_parser_t*, hb_allocator_T*);
extern char* wrap_in_image_path(const char*, size_t, const char*, hb_allocator_T*);
extern char* wrap_in_image_source_dispatch(const char*, size_t, const char*, hb_allocator_T*);
extern bool image_tag_source_is_url(const char*, size_t);
extern bool detect_stylesheet_link_tag(pm_call_node_t*, pm_parser_t*);
extern char* wrap_in_stylesheet_path(const char*, size_t, const char*, hb_allocator_T*);
Expand Down Expand Up @@ -760,7 +762,7 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
position_T id_start, id_end;
prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
bool id_is_ruby_expression = !turbo_frame_tag_id_is_static(parse_context->info->call_node);

AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
Expand Down Expand Up @@ -850,7 +852,7 @@ static AST_NODE_T* transform_tag_helper_with_attributes(
source_attribute_value = wrap_in_image_path(quoted_source, quoted_length, path_options, allocator);
hb_allocator_dealloc(allocator, quoted_source);
} else if (!source_is_string && !is_url && !source_is_path_helper) {
source_attribute_value = wrap_in_image_path(source_value, source_length, path_options, allocator);
source_attribute_value = wrap_in_image_source_dispatch(source_value, source_length, path_options, allocator);
}

AST_HTML_ATTRIBUTE_NODE_T* source_attribute =
Expand Down Expand Up @@ -1415,7 +1417,7 @@ static AST_NODE_T* transform_erb_block_to_tag_helper(
pm_node_t* first_argument = parse_context->info->call_node->arguments->arguments.nodes[0];
position_T id_start, id_end;
prism_node_location_to_positions(&first_argument->location, parse_context, &id_start, &id_end);
bool id_is_ruby_expression = (first_argument->type != PM_STRING_NODE && first_argument->type != PM_SYMBOL_NODE);
bool id_is_ruby_expression = !turbo_frame_tag_id_is_static(parse_context->info->call_node);

AST_HTML_ATTRIBUTE_NODE_T* id_attribute =
id_is_ruby_expression ? create_html_attribute_with_ruby_literal("id", id_value, id_start, id_end, allocator)
Expand Down
116 changes: 106 additions & 10 deletions src/analyze/action_view/turbo_frame_tag.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,98 @@
#include "../../include/lib/hb_allocator.h"
#include "../../include/lib/hb_buffer.h"

#include <prism.h>
#include <stdbool.h>
#include <string.h>

static bool turbo_frame_tag_literal_id(pm_node_t* node, const char** source, size_t* length) {
if (node->type == PM_STRING_NODE) {
pm_string_node_t* string_node = (pm_string_node_t*) node;
*source = (const char*) pm_string_source(&string_node->unescaped);
*length = pm_string_length(&string_node->unescaped);

return true;
}

if (node->type == PM_SYMBOL_NODE) {
pm_symbol_node_t* symbol_node = (pm_symbol_node_t*) node;
*source = (const char*) pm_string_source(&symbol_node->unescaped);
*length = pm_string_length(&symbol_node->unescaped);

return true;
}

return false;
}

// `turbo_frame_tag *ids` is `dom_id(*ids)` when the first id is a record or Class, `ids.join("_")` otherwise.
static char* extract_turbo_frame_tag_ids(
pm_arguments_node_t* arguments,
size_t count,
pm_parser_t* parser,
hb_allocator_T* allocator
) {
(void) parser;

hb_buffer_T buffer;
hb_buffer_init(&buffer, 128, allocator);

bool all_literal = true;

for (size_t index = 0; index < count; index++) {
const char* literal = NULL;
size_t literal_length = 0;

if (!turbo_frame_tag_literal_id(arguments->arguments.nodes[index], &literal, &literal_length)) {
all_literal = false;
break;
}

if (index > 0) { hb_buffer_append(&buffer, "_"); }
hb_buffer_append_with_length(&buffer, literal, literal_length);
}

if (!all_literal) {
hb_buffer_clear(&buffer);
hb_buffer_append(&buffer, "[");

for (size_t index = 0; index < count; index++) {
pm_node_t* node = arguments->arguments.nodes[index];

if (index > 0) { hb_buffer_append(&buffer, ", "); }
hb_buffer_append_with_length(&buffer, (const char*) node->location.start, node->location.end - node->location.start);
}

hb_buffer_append(&buffer, "].then { |ids| ids.first.respond_to?(:to_key) || ids.first.is_a?(Class)");
hb_buffer_append(&buffer, " ? dom_id(*ids) : ids.join(\"_\") }");
}

char* result = hb_allocator_strdup(allocator, hb_buffer_value(&buffer));
hb_buffer_free(&buffer);

return result;
}

char* extract_turbo_frame_tag_id(pm_call_node_t* call_node, pm_parser_t* parser, hb_allocator_T* allocator) {
if (!call_node || !call_node->arguments) { return NULL; }

pm_arguments_node_t* arguments = call_node->arguments;
if (!arguments->arguments.size) { return NULL; }

size_t positional_count = 0;

for (size_t index = 0; index < arguments->arguments.size; index++) {
pm_node_type_t type = arguments->arguments.nodes[index]->type;

if (type == PM_KEYWORD_HASH_NODE || type == PM_SPLAT_NODE) { break; }

positional_count++;
}

if (positional_count == 0) { return NULL; }

if (positional_count > 1) { return extract_turbo_frame_tag_ids(arguments, positional_count, parser, allocator); }

pm_node_t* first_argument = arguments->arguments.nodes[0];

if (first_argument->type == PM_STRING_NODE) {
Expand All @@ -36,18 +119,31 @@ char* extract_turbo_frame_tag_id(pm_call_node_t* call_node, pm_parser_t* parser,
}
}

const char* prefix = "dom_id(";
const char* suffix = ")";
hb_buffer_T buffer;
hb_buffer_init(&buffer, source_length + 128, allocator);

size_t prefix_length = strlen(prefix);
size_t suffix_length = strlen(suffix);
size_t total_length = prefix_length + source_length + suffix_length;
char* result = hb_allocator_alloc(allocator, total_length + 1);
hb_buffer_append(&buffer, "(");
hb_buffer_append_with_length(&buffer, source, source_length);
hb_buffer_append(&buffer, ").then { |value| value.respond_to?(:to_key) || value.is_a?(Class)");
hb_buffer_append(&buffer, " ? dom_id(value) : value.to_s }");

memcpy(result, prefix, prefix_length);
memcpy(result + prefix_length, source, source_length);
memcpy(result + prefix_length + source_length, suffix, suffix_length);
result[total_length] = '\0';
char* result = hb_allocator_strdup(allocator, hb_buffer_value(&buffer));
hb_buffer_free(&buffer);

return result;
}

bool turbo_frame_tag_id_is_static(pm_call_node_t* call_node) {
if (!call_node || !call_node->arguments) { return false; }

pm_arguments_node_t* arguments = call_node->arguments;

for (size_t index = 0; index < arguments->arguments.size; index++) {
pm_node_t* node = arguments->arguments.nodes[index];

if (node->type == PM_KEYWORD_HASH_NODE || node->type == PM_SPLAT_NODE) { break; }
if (node->type != PM_STRING_NODE && node->type != PM_SYMBOL_NODE) { return false; }
}

return true;
}
18 changes: 18 additions & 0 deletions test/engine/action_view/turbo_frame_tag_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ class TurboFrameTagTest < Minitest::Spec
test "turbo_frame_tag with data attributes" do
assert_optimized_snapshot('<%= turbo_frame_tag "tray", data: { controller: "frame" } do %>Content<% end %>')
end

test "turbo_frame_tag with a string id held in a local" do
assert_optimized_snapshot(
"<%= turbo_frame_tag frame_id do %>Content<% end %>",
{ frame_id: "post_1" }
)
end

test "turbo_frame_tag with a string prefix and a string id held in a local" do
assert_optimized_snapshot(
'<%= turbo_frame_tag "notes", frame_id do %>Content<% end %>',
{ frame_id: "post_1" }
)
end

test "turbo_frame_tag with literal ids joins them" do
assert_optimized_snapshot('<%= turbo_frame_tag "notes", :list %>')
end
end
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading