Skip to content
Merged
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
2 changes: 1 addition & 1 deletion bin/force_update_snapshots
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -e # Exit on error

FORCE_UPDATE_SNAPSHOTS=true bundle exec rake test
FORCE_UPDATE_SNAPSHOTS=true bundle exec rake test:all

echo ""
echo "Force updated snapshots!"
2 changes: 1 addition & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ errors:

- name: ERBCaseWithConditionsError
message:
template: "A `case` statement with `when`/`in` in a single ERB tag cannot be formatted. Use separate tags for `case` and its conditions."
template: "A `case` statement with `when`/`in` conditions in a single ERB tag cannot be reliably parsed, compiled, and formatted. Use separate ERB tags for `case` and its conditions (e.g., `<% case x %>` followed by `<% when y %>`)."
arguments: []

fields: []
Expand Down
4 changes: 3 additions & 1 deletion lib/herb/engine/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ def visit_erb_content_node(node)
end

def visit_erb_control_node(node, &_block)
apply_trim(node, node.content.value.strip)
if node.content
apply_trim(node, node.content.value.strip)
end

yield if block_given?
end
Expand Down
43 changes: 37 additions & 6 deletions src/analyze/analyze.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) {
}

static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
const parser_options_T* options = (const parser_options_T*) data;

if (node->type == AST_ERB_CONTENT_NODE) {
AST_ERB_CONTENT_NODE_T* erb_content_node = (AST_ERB_CONTENT_NODE_T*) node;

Expand All @@ -78,9 +80,7 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
);
}

if (!analyzed->valid
&& ((analyzed->case_node_count > 0 && analyzed->when_node_count > 0)
|| (analyzed->case_match_node_count > 0 && analyzed->in_node_count > 0))) {
if (options && options->strict && !analyzed->valid && has_inline_case_condition(analyzed)) {
append_erb_case_with_conditions_error(
erb_content_node->base.location.start,
erb_content_node->base.location.end,
Expand Down Expand Up @@ -279,6 +279,10 @@ static size_t process_case_structure(
hb_array_T* in_conditions = hb_array_init(8);
hb_array_T* non_when_non_in_children = hb_array_init(8);

analyzed_ruby_T* analyzed = erb_node->analyzed_ruby;
bool has_inline_when = has_case_node(analyzed) && has_when_node(analyzed);
bool has_inline_in = has_case_match_node(analyzed) && has_in_node(analyzed);

index++;

const control_type_t prelude_stop[] = { CONTROL_TYPE_WHEN, CONTROL_TYPE_IN, CONTROL_TYPE_END };
Expand All @@ -290,6 +294,33 @@ static size_t process_case_structure(
sizeof(prelude_stop) / sizeof(prelude_stop[0])
);

// Create a synthetic when/in node for inline when/in (e.g., <% case variable when "a" %>),
if (has_inline_when || has_inline_in) {
hb_array_T* statements = non_when_non_in_children;
non_when_non_in_children = hb_array_init(8);

position_T start_position =
erb_node->tag_closing ? erb_node->tag_closing->location.end : erb_node->content->location.end;
position_T end_position = start_position;

if (hb_array_size(statements) > 0) {
AST_NODE_T* last_child = hb_array_last(statements);
end_position = last_child->location.end;
}

if (has_inline_when) {
AST_NODE_T* synthetic_node = (AST_NODE_T*)
ast_erb_when_node_init(NULL, NULL, NULL, NULL, statements, start_position, end_position, hb_array_init(0));

hb_array_append(when_conditions, synthetic_node);
} else {
AST_NODE_T* synthetic_node = (AST_NODE_T*)
ast_erb_in_node_init(NULL, NULL, NULL, NULL, statements, start_position, end_position, hb_array_init(0));

hb_array_append(in_conditions, synthetic_node);
}
}

while (index < hb_array_size(array)) {
AST_ERB_CONTENT_NODE_T* next_erb = get_erb_content_at(array, index);

Expand Down Expand Up @@ -754,8 +785,8 @@ hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby
return new_array;
}

void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source, bool strict) {
herb_visit_node((AST_NODE_T*) document, analyze_erb_content, NULL);
void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source, const parser_options_T* options) {
herb_visit_node((AST_NODE_T*) document, analyze_erb_content, (void*) options);

analyze_ruby_context_T* context = malloc(sizeof(analyze_ruby_context_T));

Expand Down Expand Up @@ -784,7 +815,7 @@ void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source,

herb_analyze_parse_errors(document, source);

herb_parser_match_html_tags_post_analyze(document, strict);
herb_parser_match_html_tags_post_analyze(document, options);

hb_array_free(&context->ruby_context_stack);

Expand Down
43 changes: 24 additions & 19 deletions src/analyze/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,79 +6,84 @@
#include "../include/util/string.h"

bool has_if_node(analyzed_ruby_T* analyzed) {
return analyzed->if_node_count > 0;
return analyzed && analyzed->if_node_count > 0;
}

bool has_elsif_node(analyzed_ruby_T* analyzed) {
return analyzed->elsif_node_count > 0;
return analyzed && analyzed->elsif_node_count > 0;
}

bool has_else_node(analyzed_ruby_T* analyzed) {
return analyzed->else_node_count > 0;
return analyzed && analyzed->else_node_count > 0;
}

bool has_end(analyzed_ruby_T* analyzed) {
return analyzed->end_count > 0;
return analyzed && analyzed->end_count > 0;
}

bool has_block_node(analyzed_ruby_T* analyzed) {
return analyzed->block_node_count > 0;
return analyzed && analyzed->block_node_count > 0;
}

bool has_block_closing(analyzed_ruby_T* analyzed) {
return analyzed->block_closing_count > 0;
return analyzed && analyzed->block_closing_count > 0;
}

bool has_case_node(analyzed_ruby_T* analyzed) {
return analyzed->case_node_count > 0;
return analyzed && analyzed->case_node_count > 0;
}

bool has_case_match_node(analyzed_ruby_T* analyzed) {
return analyzed->case_match_node_count > 0;
return analyzed && analyzed->case_match_node_count > 0;
}

bool has_when_node(analyzed_ruby_T* analyzed) {
return analyzed->when_node_count > 0;
return analyzed && analyzed->when_node_count > 0;
}

bool has_in_node(analyzed_ruby_T* analyzed) {
return analyzed->in_node_count > 0;
return analyzed && analyzed->in_node_count > 0;
}

bool has_for_node(analyzed_ruby_T* analyzed) {
return analyzed->for_node_count > 0;
return analyzed && analyzed->for_node_count > 0;
}

bool has_while_node(analyzed_ruby_T* analyzed) {
return analyzed->while_node_count > 0;
return analyzed && analyzed->while_node_count > 0;
}

bool has_until_node(analyzed_ruby_T* analyzed) {
return analyzed->until_node_count > 0;
return analyzed && analyzed->until_node_count > 0;
}

bool has_begin_node(analyzed_ruby_T* analyzed) {
return analyzed->begin_node_count > 0;
return analyzed && analyzed->begin_node_count > 0;
}

bool has_rescue_node(analyzed_ruby_T* analyzed) {
return analyzed->rescue_node_count > 0;
return analyzed && analyzed->rescue_node_count > 0;
}

bool has_ensure_node(analyzed_ruby_T* analyzed) {
return analyzed->ensure_node_count > 0;
return analyzed && analyzed->ensure_node_count > 0;
}

bool has_unless_node(analyzed_ruby_T* analyzed) {
return analyzed->unless_node_count > 0;
return analyzed && analyzed->unless_node_count > 0;
}

bool has_yield_node(analyzed_ruby_T* analyzed) {
return analyzed->yield_node_count > 0;
return analyzed && analyzed->yield_node_count > 0;
}

bool has_then_keyword(analyzed_ruby_T* analyzed) {
return analyzed->then_keyword_count > 0;
return analyzed && analyzed->then_keyword_count > 0;
}

bool has_inline_case_condition(analyzed_ruby_T* analyzed) {
return (has_case_node(analyzed) && has_when_node(analyzed))
|| (has_case_match_node(analyzed) && has_in_node(analyzed));
}

bool has_error_message(analyzed_ruby_T* analyzed, const char* message) {
Expand Down
2 changes: 1 addition & 1 deletion src/herb.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ HERB_EXPORTED_FUNCTION AST_DOCUMENT_NODE_T* herb_parse(const char* source, const

herb_parser_deinit(&parser);

if (parser_options.analyze) { herb_analyze_parse_tree(document, source, parser_options.strict); }
if (parser_options.analyze) { herb_analyze_parse_tree(document, source, &parser_options); }

return document;
}
Expand Down
3 changes: 2 additions & 1 deletion src/include/analyze/analyze.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "analyzed_ruby.h"
#include "../ast_nodes.h"
#include "../parser.h"
#include "../util/hb_array.h"

typedef struct ANALYZE_RUBY_CONTEXT_STRUCT {
Expand Down Expand Up @@ -39,7 +40,7 @@ typedef struct {
} invalid_erb_context_T;

void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source);
void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source, bool strict);
void herb_analyze_parse_tree(AST_DOCUMENT_NODE_T* document, const char* source, const parser_options_T* options);

hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context);
bool transform_erb_nodes(const AST_NODE_T* node, void* data);
Expand Down
1 change: 1 addition & 0 deletions src/include/analyze/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bool has_ensure_node(analyzed_ruby_T* analyzed);
bool has_unless_node(analyzed_ruby_T* analyzed);
bool has_yield_node(analyzed_ruby_T* analyzed);
bool has_then_keyword(analyzed_ruby_T* analyzed);
bool has_inline_case_condition(analyzed_ruby_T* analyzed);

bool has_error_message(analyzed_ruby_T* anlayzed, const char* message);

Expand Down
6 changes: 3 additions & 3 deletions src/include/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ typedef struct PARSER_OPTIONS_STRUCT {

typedef struct MATCH_TAGS_CONTEXT_STRUCT {
hb_array_T* errors;
bool strict;
const parser_options_T* options;
} match_tags_context_T;

extern const parser_options_T HERB_DEFAULT_PARSER_OPTIONS;
Expand All @@ -45,10 +45,10 @@ void herb_parser_init(parser_T* parser, lexer_T* lexer, parser_options_T options

AST_DOCUMENT_NODE_T* herb_parser_parse(parser_T* parser);

void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document, bool strict);
void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document, const parser_options_T* options);
void herb_parser_deinit(parser_T* parser);

void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, bool strict);
void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, const parser_options_T* options);
bool match_tags_visitor(const AST_NODE_T* node, void* data);

#endif
29 changes: 19 additions & 10 deletions src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -1536,9 +1536,18 @@ static size_t find_implicit_close_index(hb_array_T* nodes, size_t start_idx, hb_
return hb_array_size(nodes);
}

static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T* errors, bool strict);

static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T* errors, bool strict) {
static hb_array_T* parser_build_elements_from_tags(
hb_array_T* nodes,
hb_array_T* errors,
const parser_options_T* options
);

static hb_array_T* parser_build_elements_from_tags(
hb_array_T* nodes,
hb_array_T* errors,
const parser_options_T* options
) {
bool strict = options ? options->strict : false;
hb_array_T* result = hb_array_init(hb_array_size(nodes));

for (size_t index = 0; index < hb_array_size(nodes); index++) {
Expand All @@ -1561,7 +1570,7 @@ static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T
hb_array_append(body, hb_array_get(nodes, j));
}

hb_array_T* processed_body = parser_build_elements_from_tags(body, errors, strict);
hb_array_T* processed_body = parser_build_elements_from_tags(body, errors, options);
hb_array_free(&body);

position_T end_position = open_tag->base.location.end;
Expand Down Expand Up @@ -1622,7 +1631,7 @@ static hb_array_T* parser_build_elements_from_tags(hb_array_T* nodes, hb_array_T
hb_array_append(body, hb_array_get(nodes, j));
}

hb_array_T* processed_body = parser_build_elements_from_tags(body, errors, strict);
hb_array_T* processed_body = parser_build_elements_from_tags(body, errors, options);
hb_array_free(&body);

hb_array_T* element_errors = hb_array_init(8);
Expand Down Expand Up @@ -1728,10 +1737,10 @@ void herb_parser_deinit(parser_T* parser) {
}
}

void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, bool strict) {
void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, const parser_options_T* options) {
if (nodes == NULL || hb_array_size(nodes) == 0) { return; }

hb_array_T* processed = parser_build_elements_from_tags(nodes, errors, strict);
hb_array_T* processed = parser_build_elements_from_tags(nodes, errors, options);

nodes->size = 0;

Expand All @@ -1741,7 +1750,7 @@ void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, bool strict

hb_array_free(&processed);

match_tags_context_T context = { .errors = errors, .strict = strict };
match_tags_context_T context = { .errors = errors, .options = options };

for (size_t i = 0; i < hb_array_size(nodes); i++) {
AST_NODE_T* node = (AST_NODE_T*) hb_array_get(nodes, i);
Expand All @@ -1751,8 +1760,8 @@ void match_tags_in_node_array(hb_array_T* nodes, hb_array_T* errors, bool strict
}
}

void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document, bool strict) {
void herb_parser_match_html_tags_post_analyze(AST_DOCUMENT_NODE_T* document, const parser_options_T* options) {
if (document == NULL) { return; }

match_tags_in_node_array(document->children, document->base.errors, strict);
match_tags_in_node_array(document->children, document->base.errors, options);
}
2 changes: 1 addition & 1 deletion templates/src/parser_match_tags.c.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ bool match_tags_visitor(const AST_NODE_T* node, void* data) {

<%- array_fields.each do |field| -%>
if (<%= node.human %>-><%= field.name %> != NULL) {
match_tags_in_node_array(<%= node.human %>-><%= field.name %>, context->errors, context->strict);
match_tags_in_node_array(<%= node.human %>-><%= field.name %>, context->errors, context->options);
}
<%- end -%>
<%- single_node_fields.each do |field| -%>
Expand Down
Loading
Loading