diff --git a/.clang-format-ignore b/.clang-format-ignore index 933313065..4a60a6b42 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1,7 +1,7 @@ ext/herb/error_helpers.c ext/herb/nodes.c -src/analyze_missing_end.c -src/analyze_transform.c +src/analyze/missing_end.c +src/analyze/transform.c src/ast_nodes.c src/ast_pretty_print.c src/errors.c diff --git a/.gitignore b/.gitignore index 23736e2f7..0fae38c52 100644 --- a/.gitignore +++ b/.gitignore @@ -115,8 +115,8 @@ rust/src/union_types.rs rust/src/visitor.rs sig/serialized_ast_errors.rbs sig/serialized_ast_nodes.rbs -src/analyze_missing_end.c -src/analyze_transform.c +src/analyze/missing_end.c +src/analyze/transform.c src/ast_nodes.c src/ast_pretty_print.c src/errors.c diff --git a/ext/herb/extconf.rb b/ext/herb/extconf.rb index fce654bf6..827e2df01 100644 --- a/ext/herb/extconf.rb +++ b/ext/herb/extconf.rb @@ -11,6 +11,7 @@ prism_include_path = "#{prism_path}/include" $VPATH << "$(srcdir)/../../src" +$VPATH << "$(srcdir)/../../src/analyze" $VPATH << "$(srcdir)/../../src/util" $VPATH << prism_src_path $VPATH << "#{prism_src_path}/util" diff --git a/javascript/packages/node/binding.gyp b/javascript/packages/node/binding.gyp index 68ea9aa43..332cb0f43 100644 --- a/javascript/packages/node/binding.gyp +++ b/javascript/packages/node/binding.gyp @@ -10,13 +10,17 @@ "./extension/nodes.cpp", # Herb main source files - "./extension/libherb/analyze_conditional_elements.c", - "./extension/libherb/analyze_conditional_open_tags.c", - "./extension/libherb/analyze_helpers.c", - "./extension/libherb/analyze_missing_end.c", - "./extension/libherb/analyze_transform.c", - "./extension/libherb/analyze.c", - "./extension/libherb/analyzed_ruby.c", + "./extension/libherb/analyze/analyze.c", + "./extension/libherb/analyze/analyzed_ruby.c", + "./extension/libherb/analyze/builders.c", + "./extension/libherb/analyze/conditional_elements.c", + "./extension/libherb/analyze/conditional_open_tags.c", + "./extension/libherb/analyze/control_type.c", + "./extension/libherb/analyze/helpers.c", + "./extension/libherb/analyze/invalid_structures.c", + "./extension/libherb/analyze/missing_end.c", + "./extension/libherb/analyze/parse_errors.c", + "./extension/libherb/analyze/transform.c", "./extension/libherb/ast_node.c", "./extension/libherb/ast_nodes.c", "./extension/libherb/ast_pretty_print.c", diff --git a/rust/build.rs b/rust/build.rs index ca1515ed5..46101877c 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -86,7 +86,7 @@ fn main() { }; let mut builder = bindgen::Builder::default() - .header(include_dir.join("analyze.h").to_str().unwrap()) + .header(include_dir.join("analyze/analyze.h").to_str().unwrap()) .header(include_dir.join("herb.h").to_str().unwrap()) .header(include_dir.join("ast_nodes.h").to_str().unwrap()) .header(include_dir.join("errors.h").to_str().unwrap()) diff --git a/src/analyze.c b/src/analyze.c deleted file mode 100644 index 45e523208..000000000 --- a/src/analyze.c +++ /dev/null @@ -1,1653 +0,0 @@ -#include "include/analyze.h" -#include "include/analyze_conditional_elements.h" -#include "include/analyze_conditional_open_tags.h" -#include "include/analyze_helpers.h" -#include "include/analyzed_ruby.h" -#include "include/ast_node.h" -#include "include/ast_nodes.h" -#include "include/errors.h" -#include "include/extract.h" -#include "include/location.h" -#include "include/parser.h" -#include "include/position.h" -#include "include/prism_helpers.h" -#include "include/token_struct.h" -#include "include/util/hb_array.h" -#include "include/util/hb_string.h" -#include "include/util/string.h" -#include "include/visitor.h" - -#include -#include -#include -#include -#include - -static position_T erb_content_end_position(const AST_ERB_CONTENT_NODE_T* erb_node) { - if (erb_node->tag_closing != NULL) { - return erb_node->tag_closing->location.end; - } else if (erb_node->content != NULL) { - return erb_node->content->location.end; - } else { - return erb_node->tag_opening->location.end; - } -} - -static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) { - analyzed_ruby_T* analyzed = init_analyzed_ruby(source); - - pm_visit_node(analyzed->root, search_if_nodes, analyzed); - pm_visit_node(analyzed->root, search_block_nodes, analyzed); - pm_visit_node(analyzed->root, search_case_nodes, analyzed); - pm_visit_node(analyzed->root, search_case_match_nodes, analyzed); - pm_visit_node(analyzed->root, search_while_nodes, analyzed); - pm_visit_node(analyzed->root, search_for_nodes, analyzed); - pm_visit_node(analyzed->root, search_until_nodes, analyzed); - pm_visit_node(analyzed->root, search_begin_nodes, analyzed); - pm_visit_node(analyzed->root, search_unless_nodes, analyzed); - pm_visit_node(analyzed->root, search_when_nodes, analyzed); - pm_visit_node(analyzed->root, search_in_nodes, analyzed); - - search_unexpected_elsif_nodes(analyzed); - search_unexpected_else_nodes(analyzed); - search_unexpected_end_nodes(analyzed); - search_unexpected_when_nodes(analyzed); - search_unexpected_in_nodes(analyzed); - - search_unexpected_rescue_nodes(analyzed); - search_unexpected_ensure_nodes(analyzed); - search_yield_nodes(analyzed->root, analyzed); - search_then_keywords(analyzed->root, analyzed); - search_unexpected_block_closing_nodes(analyzed); - - if (!analyzed->valid) { pm_visit_node(analyzed->root, search_unclosed_control_flows, analyzed); } - - return analyzed; -} - -static bool analyze_erb_content(const AST_NODE_T* node, void* data) { - if (node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* erb_content_node = (AST_ERB_CONTENT_NODE_T*) node; - - const char* opening = erb_content_node->tag_opening->value; - - if (!string_equals(opening, "<%%") && !string_equals(opening, "<%%=") && !string_equals(opening, "<%#") - && !string_equals(opening, "<%graphql")) { - analyzed_ruby_T* analyzed = herb_analyze_ruby(hb_string(erb_content_node->content->value)); - - erb_content_node->parsed = true; - erb_content_node->valid = analyzed->valid; - erb_content_node->analyzed_ruby = analyzed; - - if (!analyzed->valid && analyzed->unclosed_control_flow_count >= 2) { - append_erb_multiple_blocks_in_tag_error( - erb_content_node->base.location.start, - erb_content_node->base.location.end, - erb_content_node->base.errors - ); - } - - if (!analyzed->valid - && ((analyzed->case_node_count > 0 && analyzed->when_node_count > 0) - || (analyzed->case_match_node_count > 0 && analyzed->in_node_count > 0))) { - append_erb_case_with_conditions_error( - erb_content_node->base.location.start, - erb_content_node->base.location.end, - erb_content_node->base.errors - ); - } - } else { - erb_content_node->parsed = false; - erb_content_node->valid = true; - erb_content_node->analyzed_ruby = NULL; - } - } - - herb_visit_child_nodes(node, analyze_erb_content, data); - - return false; -} - -static size_t process_block_children( - AST_NODE_T* node, - hb_array_T* array, - size_t index, - hb_array_T* children_array, - analyze_ruby_context_T* context, - control_type_t parent_type -); - -static size_t process_subsequent_block( - AST_NODE_T* node, - hb_array_T* array, - size_t index, - AST_NODE_T** subsequent_out, - analyze_ruby_context_T* context, - control_type_t parent_type -); - -typedef struct { - control_type_t type; - uint32_t offset; - bool found; -} earliest_control_keyword_t; - -typedef struct { - earliest_control_keyword_t* result; - const uint8_t* source_start; -} location_walker_context_t; - -static bool control_type_is_block(control_type_t type) { - return type == CONTROL_TYPE_BLOCK; -} - -static bool control_type_is_yield(control_type_t type) { - return type == CONTROL_TYPE_YIELD; -} - -static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* data) { - if (!node) { return true; } - - location_walker_context_t* context = (location_walker_context_t*) data; - earliest_control_keyword_t* result = context->result; - - control_type_t current_type = CONTROL_TYPE_UNKNOWN; - uint32_t keyword_offset = UINT32_MAX; - - switch (node->type) { - case PM_IF_NODE: { - pm_if_node_t* if_node = (pm_if_node_t*) node; - current_type = CONTROL_TYPE_IF; - keyword_offset = (uint32_t) (if_node->if_keyword_loc.start - context->source_start); - break; - } - - case PM_UNLESS_NODE: { - pm_unless_node_t* unless_node = (pm_unless_node_t*) node; - current_type = CONTROL_TYPE_UNLESS; - keyword_offset = (uint32_t) (unless_node->keyword_loc.start - context->source_start); - break; - } - - case PM_CASE_NODE: { - pm_case_node_t* case_node = (pm_case_node_t*) node; - current_type = CONTROL_TYPE_CASE; - keyword_offset = (uint32_t) (case_node->case_keyword_loc.start - context->source_start); - break; - } - - case PM_CASE_MATCH_NODE: { - pm_case_match_node_t* case_match_node = (pm_case_match_node_t*) node; - current_type = CONTROL_TYPE_CASE_MATCH; - keyword_offset = (uint32_t) (case_match_node->case_keyword_loc.start - context->source_start); - break; - } - - case PM_WHILE_NODE: { - pm_while_node_t* while_node = (pm_while_node_t*) node; - current_type = CONTROL_TYPE_WHILE; - keyword_offset = (uint32_t) (while_node->keyword_loc.start - context->source_start); - break; - } - - case PM_UNTIL_NODE: { - pm_until_node_t* until_node = (pm_until_node_t*) node; - current_type = CONTROL_TYPE_UNTIL; - keyword_offset = (uint32_t) (until_node->keyword_loc.start - context->source_start); - break; - } - - case PM_FOR_NODE: { - pm_for_node_t* for_node = (pm_for_node_t*) node; - current_type = CONTROL_TYPE_FOR; - keyword_offset = (uint32_t) (for_node->for_keyword_loc.start - context->source_start); - break; - } - - case PM_BEGIN_NODE: { - pm_begin_node_t* begin_node = (pm_begin_node_t*) node; - current_type = CONTROL_TYPE_BEGIN; - - if (begin_node->begin_keyword_loc.start != NULL) { - keyword_offset = (uint32_t) (begin_node->begin_keyword_loc.start - context->source_start); - } else { - keyword_offset = (uint32_t) (node->location.start - context->source_start); - } - break; - } - - case PM_YIELD_NODE: { - current_type = CONTROL_TYPE_YIELD; - keyword_offset = (uint32_t) (node->location.start - context->source_start); - break; - } - - case PM_CALL_NODE: { - pm_call_node_t* call = (pm_call_node_t*) node; - - if (call->block != NULL && call->block->type == PM_BLOCK_NODE) { - pm_block_node_t* block_node = (pm_block_node_t*) call->block; - - bool has_do_opening = is_do_block(block_node->opening_loc); - bool has_brace_opening = is_brace_block(block_node->opening_loc); - bool has_valid_brace_closing = is_closing_brace(block_node->closing_loc); - - if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) { - current_type = CONTROL_TYPE_BLOCK; - keyword_offset = (uint32_t) (node->location.start - context->source_start); - } - } - break; - } - - case PM_LAMBDA_NODE: { - pm_lambda_node_t* lambda = (pm_lambda_node_t*) node; - - bool has_do_opening = is_do_block(lambda->opening_loc); - bool has_brace_opening = is_brace_block(lambda->opening_loc); - bool has_valid_brace_closing = is_closing_brace(lambda->closing_loc); - - if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) { - current_type = CONTROL_TYPE_BLOCK; - keyword_offset = (uint32_t) (node->location.start - context->source_start); - } - - break; - } - - case PM_NEXT_NODE: - case PM_BREAK_NODE: - case PM_RETURN_NODE: { - current_type = CONTROL_TYPE_UNKNOWN; - keyword_offset = (uint32_t) (node->location.start - context->source_start); - break; - } - - default: break; - } - - if (keyword_offset != UINT32_MAX) { - bool should_update = !result->found; - - if (result->found) { - if (control_type_is_block(current_type) && control_type_is_yield(result->type)) { - should_update = true; - } else if (!(control_type_is_yield(current_type) && control_type_is_block(result->type))) { - should_update = keyword_offset < result->offset; - } - } - - if (should_update) { - result->type = current_type; - result->offset = keyword_offset; - result->found = true; - } - } - - return true; -} - -static control_type_t find_earliest_control_keyword(pm_node_t* root, const uint8_t* source_start) { - if (!root) { return CONTROL_TYPE_UNKNOWN; } - - earliest_control_keyword_t result = { .type = CONTROL_TYPE_UNKNOWN, .offset = UINT32_MAX, .found = false }; - - location_walker_context_t context = { .result = &result, .source_start = source_start }; - - pm_visit_node(root, find_earliest_control_keyword_walker, &context); - - return result.found ? result.type : CONTROL_TYPE_UNKNOWN; -} - -static control_type_t detect_control_type(AST_ERB_CONTENT_NODE_T* erb_node) { - if (!erb_node || erb_node->base.type != AST_ERB_CONTENT_NODE) { return CONTROL_TYPE_UNKNOWN; } - if (erb_node->tag_closing == NULL) { return CONTROL_TYPE_UNKNOWN; } - - analyzed_ruby_T* ruby = erb_node->analyzed_ruby; - - if (!ruby) { return CONTROL_TYPE_UNKNOWN; } - if (ruby->valid) { return CONTROL_TYPE_UNKNOWN; } - - pm_node_t* root = ruby->root; - - if (has_elsif_node(ruby)) { return CONTROL_TYPE_ELSIF; } - if (has_else_node(ruby)) { return CONTROL_TYPE_ELSE; } - if (has_end(ruby)) { return CONTROL_TYPE_END; } - if (has_when_node(ruby) && !has_case_node(ruby)) { return CONTROL_TYPE_WHEN; } - if (has_in_node(ruby) && !has_case_match_node(ruby)) { return CONTROL_TYPE_IN; } - if (has_rescue_node(ruby)) { return CONTROL_TYPE_RESCUE; } - if (has_ensure_node(ruby)) { return CONTROL_TYPE_ENSURE; } - if (has_block_closing(ruby)) { return CONTROL_TYPE_BLOCK_CLOSE; } - - if (ruby->unclosed_control_flow_count == 0 && !has_yield_node(ruby)) { return CONTROL_TYPE_UNKNOWN; } - - return find_earliest_control_keyword(root, ruby->parser.start); -} - -static bool is_subsequent_type(control_type_t parent_type, control_type_t child_type) { - switch (parent_type) { - case CONTROL_TYPE_IF: - case CONTROL_TYPE_ELSIF: return child_type == CONTROL_TYPE_ELSIF || child_type == CONTROL_TYPE_ELSE; - case CONTROL_TYPE_CASE: - case CONTROL_TYPE_CASE_MATCH: return child_type == CONTROL_TYPE_WHEN || child_type == CONTROL_TYPE_ELSE; - case CONTROL_TYPE_BEGIN: - return child_type == CONTROL_TYPE_RESCUE || child_type == CONTROL_TYPE_ELSE || child_type == CONTROL_TYPE_ENSURE; - case CONTROL_TYPE_RESCUE: return child_type == CONTROL_TYPE_RESCUE; - case CONTROL_TYPE_UNLESS: return child_type == CONTROL_TYPE_ELSE; - - default: return false; - } -} - -static bool is_terminator_type(control_type_t parent_type, control_type_t child_type) { - if (child_type == CONTROL_TYPE_END) { return true; } - - switch (parent_type) { - case CONTROL_TYPE_WHEN: return child_type == CONTROL_TYPE_WHEN || child_type == CONTROL_TYPE_ELSE; - case CONTROL_TYPE_IN: return child_type == CONTROL_TYPE_IN || child_type == CONTROL_TYPE_ELSE; - case CONTROL_TYPE_BLOCK: return child_type == CONTROL_TYPE_BLOCK_CLOSE; - - default: return is_subsequent_type(parent_type, child_type); - } -} - -static AST_NODE_T* create_control_node( - AST_ERB_CONTENT_NODE_T* erb_node, - hb_array_T* children, - AST_NODE_T* subsequent, - AST_ERB_END_NODE_T* end_node, - control_type_t control_type -) { - hb_array_T* errors = erb_node->base.errors; - erb_node->base.errors = NULL; - - position_T start_position = erb_node->tag_opening->location.start; - position_T end_position = erb_content_end_position(erb_node); - - if (end_node) { - end_position = end_node->base.location.end; - } else if (hb_array_size(children) > 0) { - AST_NODE_T* last_child = hb_array_last(children); - end_position = last_child->location.end; - } else if (subsequent) { - end_position = subsequent->location.end; - } - - token_T* tag_opening = erb_node->tag_opening; - token_T* content = erb_node->content; - token_T* tag_closing = erb_node->tag_closing; - - location_T* then_keyword = NULL; - - if (control_type == CONTROL_TYPE_IF || control_type == CONTROL_TYPE_ELSIF || control_type == CONTROL_TYPE_UNLESS - || control_type == CONTROL_TYPE_WHEN || control_type == CONTROL_TYPE_IN) { - const char* source = content ? content->value : NULL; - - if (control_type == CONTROL_TYPE_WHEN || control_type == CONTROL_TYPE_IN) { - if (source != NULL && strstr(source, "then") != NULL) { - then_keyword = get_then_keyword_location_wrapped(source, control_type == CONTROL_TYPE_IN); - } - } else if (control_type == CONTROL_TYPE_ELSIF) { - if (source != NULL && strstr(source, "then") != NULL) { - then_keyword = get_then_keyword_location_elsif_wrapped(source); - } - } else { - then_keyword = get_then_keyword_location(erb_node->analyzed_ruby, source); - } - - if (then_keyword != NULL && content != NULL) { - position_T content_start = content->location.start; - - then_keyword->start.line = content_start.line + then_keyword->start.line - 1; - then_keyword->start.column = content_start.column + then_keyword->start.column; - then_keyword->end.line = content_start.line + then_keyword->end.line - 1; - then_keyword->end.column = content_start.column + then_keyword->end.column; - } - } - - switch (control_type) { - case CONTROL_TYPE_IF: - case CONTROL_TYPE_ELSIF: { - return (AST_NODE_T*) ast_erb_if_node_init( - tag_opening, - content, - tag_closing, - then_keyword, - children, - subsequent, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_ELSE: { - return ( - AST_NODE_T* - ) ast_erb_else_node_init(tag_opening, content, tag_closing, children, start_position, end_position, errors); - } - - case CONTROL_TYPE_CASE: - case CONTROL_TYPE_CASE_MATCH: { - AST_ERB_ELSE_NODE_T* else_node = NULL; - if (subsequent && subsequent->type == AST_ERB_ELSE_NODE) { else_node = (AST_ERB_ELSE_NODE_T*) subsequent; } - - hb_array_T* when_conditions = hb_array_init(8); - hb_array_T* in_conditions = hb_array_init(8); - hb_array_T* non_when_non_in_children = hb_array_init(8); - - for (size_t i = 0; i < hb_array_size(children); i++) { - AST_NODE_T* child = hb_array_get(children, i); - - if (child && child->type == AST_ERB_WHEN_NODE) { - hb_array_append(when_conditions, child); - } else if (child && child->type == AST_ERB_IN_NODE) { - hb_array_append(in_conditions, child); - } else { - hb_array_append(non_when_non_in_children, child); - } - } - - hb_array_free(&children); - - if (hb_array_size(in_conditions) > 0) { - hb_array_free(&when_conditions); - - return (AST_NODE_T*) ast_erb_case_match_node_init( - tag_opening, - content, - tag_closing, - non_when_non_in_children, - in_conditions, - else_node, - end_node, - start_position, - end_position, - errors - ); - } else { - hb_array_free(&in_conditions); - - return (AST_NODE_T*) ast_erb_case_node_init( - tag_opening, - content, - tag_closing, - non_when_non_in_children, - when_conditions, - else_node, - end_node, - start_position, - end_position, - errors - ); - } - } - - case CONTROL_TYPE_WHEN: { - return (AST_NODE_T*) ast_erb_when_node_init( - tag_opening, - content, - tag_closing, - then_keyword, - children, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_IN: { - return (AST_NODE_T*) ast_erb_in_node_init( - tag_opening, - content, - tag_closing, - then_keyword, - children, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_BEGIN: { - AST_ERB_RESCUE_NODE_T* rescue_clause = NULL; - AST_ERB_ELSE_NODE_T* else_clause = NULL; - AST_ERB_ENSURE_NODE_T* ensure_clause = NULL; - - if (subsequent) { - if (subsequent->type == AST_ERB_RESCUE_NODE) { - rescue_clause = (AST_ERB_RESCUE_NODE_T*) subsequent; - } else if (subsequent->type == AST_ERB_ELSE_NODE) { - else_clause = (AST_ERB_ELSE_NODE_T*) subsequent; - } else if (subsequent->type == AST_ERB_ENSURE_NODE) { - ensure_clause = (AST_ERB_ENSURE_NODE_T*) subsequent; - } - } - - return (AST_NODE_T*) ast_erb_begin_node_init( - tag_opening, - content, - tag_closing, - children, - rescue_clause, - else_clause, - ensure_clause, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_RESCUE: { - AST_ERB_RESCUE_NODE_T* rescue_node = NULL; - - if (rescue_node && subsequent->type == AST_ERB_RESCUE_NODE) { rescue_node = (AST_ERB_RESCUE_NODE_T*) subsequent; } - - return (AST_NODE_T*) ast_erb_rescue_node_init( - tag_opening, - content, - tag_closing, - children, - rescue_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_ENSURE: { - return ( - AST_NODE_T* - ) ast_erb_ensure_node_init(tag_opening, content, tag_closing, children, start_position, end_position, errors); - } - - case CONTROL_TYPE_UNLESS: { - AST_ERB_ELSE_NODE_T* else_clause = NULL; - - if (subsequent && subsequent->type == AST_ERB_ELSE_NODE) { else_clause = (AST_ERB_ELSE_NODE_T*) subsequent; } - - return (AST_NODE_T*) ast_erb_unless_node_init( - tag_opening, - content, - tag_closing, - then_keyword, - children, - else_clause, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_WHILE: { - return (AST_NODE_T*) ast_erb_while_node_init( - tag_opening, - content, - tag_closing, - children, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_UNTIL: { - return (AST_NODE_T*) ast_erb_until_node_init( - tag_opening, - content, - tag_closing, - children, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_FOR: { - return (AST_NODE_T*) ast_erb_for_node_init( - tag_opening, - content, - tag_closing, - children, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_BLOCK: { - return (AST_NODE_T*) ast_erb_block_node_init( - tag_opening, - content, - tag_closing, - children, - end_node, - start_position, - end_position, - errors - ); - } - - case CONTROL_TYPE_YIELD: { - return ( - AST_NODE_T* - ) ast_erb_yield_node_init(tag_opening, content, tag_closing, start_position, end_position, errors); - } - - default: return NULL; - } -} - -static size_t process_control_structure( - AST_NODE_T* node, - hb_array_T* array, - size_t index, - hb_array_T* output_array, - analyze_ruby_context_T* context, - control_type_t initial_type -) { - AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) hb_array_get(array, index); - hb_array_T* children = hb_array_init(8); - - index++; - - if (initial_type == CONTROL_TYPE_CASE || initial_type == CONTROL_TYPE_CASE_MATCH) { - hb_array_T* when_conditions = hb_array_init(8); - hb_array_T* in_conditions = hb_array_init(8); - hb_array_T* non_when_non_in_children = hb_array_init(8); - - while (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (!next_node) { break; } - - if (next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* erb_content = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(erb_content); - - if (next_type == CONTROL_TYPE_WHEN || next_type == CONTROL_TYPE_IN || next_type == CONTROL_TYPE_END) { break; } - } - - hb_array_append(non_when_non_in_children, next_node); - index++; - } - - while (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (!next_node) { break; } - - if (next_node->type != AST_ERB_CONTENT_NODE) { - hb_array_append(non_when_non_in_children, next_node); - index++; - continue; - } - - AST_ERB_CONTENT_NODE_T* erb_content = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(erb_content); - - if (next_type == CONTROL_TYPE_WHEN) { - hb_array_T* when_statements = hb_array_init(8); - index++; - - index = process_block_children(node, array, index, when_statements, context, CONTROL_TYPE_WHEN); - - hb_array_T* when_errors = erb_content->base.errors; - erb_content->base.errors = NULL; - - location_T* then_keyword = NULL; - const char* source = erb_content->content ? erb_content->content->value : NULL; - - if (source != NULL && strstr(source, "then") != NULL) { - then_keyword = get_then_keyword_location_wrapped(source, false); - - if (then_keyword != NULL && erb_content->content != NULL) { - position_T content_start = erb_content->content->location.start; - - then_keyword->start.line = content_start.line + then_keyword->start.line - 1; - then_keyword->start.column = content_start.column + then_keyword->start.column; - then_keyword->end.line = content_start.line + then_keyword->end.line - 1; - then_keyword->end.column = content_start.column + then_keyword->end.column; - } - } - - position_T when_end_position = erb_content_end_position(erb_content); - - AST_ERB_WHEN_NODE_T* when_node = ast_erb_when_node_init( - erb_content->tag_opening, - erb_content->content, - erb_content->tag_closing, - then_keyword, - when_statements, - erb_content->tag_opening->location.start, - when_end_position, - when_errors - ); - - ast_node_free((AST_NODE_T*) erb_content); - - hb_array_append(when_conditions, (AST_NODE_T*) when_node); - - continue; - } else if (next_type == CONTROL_TYPE_IN) { - hb_array_T* in_statements = hb_array_init(8); - index++; - - index = process_block_children(node, array, index, in_statements, context, CONTROL_TYPE_IN); - - hb_array_T* in_errors = erb_content->base.errors; - erb_content->base.errors = NULL; - - location_T* in_then_keyword = NULL; - const char* in_source = erb_content->content ? erb_content->content->value : NULL; - - if (in_source != NULL && strstr(in_source, "then") != NULL) { - in_then_keyword = get_then_keyword_location_wrapped(in_source, true); - - if (in_then_keyword != NULL && erb_content->content != NULL) { - position_T content_start = erb_content->content->location.start; - - in_then_keyword->start.line = content_start.line + in_then_keyword->start.line - 1; - in_then_keyword->start.column = content_start.column + in_then_keyword->start.column; - in_then_keyword->end.line = content_start.line + in_then_keyword->end.line - 1; - in_then_keyword->end.column = content_start.column + in_then_keyword->end.column; - } - } - - position_T in_end_position = erb_content_end_position(erb_content); - - AST_ERB_IN_NODE_T* in_node = ast_erb_in_node_init( - erb_content->tag_opening, - erb_content->content, - erb_content->tag_closing, - in_then_keyword, - in_statements, - erb_content->tag_opening->location.start, - in_end_position, - in_errors - ); - - ast_node_free((AST_NODE_T*) erb_content); - - hb_array_append(in_conditions, (AST_NODE_T*) in_node); - - continue; - } else if (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_END) { - break; - } else { - hb_array_append(non_when_non_in_children, next_node); - index++; - } - } - - AST_ERB_ELSE_NODE_T* else_clause = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (next_type == CONTROL_TYPE_ELSE) { - hb_array_T* else_children = hb_array_init(8); - - index++; - - index = process_block_children(node, array, index, else_children, context, initial_type); - - hb_array_T* else_errors = next_erb->base.errors; - next_erb->base.errors = NULL; - - else_clause = ast_erb_else_node_init( - next_erb->tag_opening, - next_erb->content, - next_erb->tag_closing, - else_children, - next_erb->tag_opening->location.start, - erb_content_end_position(next_erb), - else_errors - ); - - ast_node_free((AST_NODE_T*) next_erb); - } - } - } - - AST_ERB_END_NODE_T* end_node = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* potential_end = hb_array_get(array, index); - - if (potential_end && potential_end->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* end_erb = (AST_ERB_CONTENT_NODE_T*) potential_end; - - if (detect_control_type(end_erb) == CONTROL_TYPE_END) { - hb_array_T* end_errors = end_erb->base.errors; - end_erb->base.errors = NULL; - - end_node = ast_erb_end_node_init( - end_erb->tag_opening, - end_erb->content, - end_erb->tag_closing, - end_erb->tag_opening->location.start, - erb_content_end_position(end_erb), - end_errors - ); - - ast_node_free((AST_NODE_T*) end_erb); - - index++; - } - } - } - - position_T start_position = erb_node->tag_opening->location.start; - position_T end_position = erb_content_end_position(erb_node); - - if (end_node) { - end_position = end_node->base.location.end; - } else if (else_clause) { - end_position = else_clause->base.location.end; - } else if (hb_array_size(when_conditions) > 0) { - AST_NODE_T* last_when = hb_array_last(when_conditions); - end_position = last_when->location.end; - } else if (hb_array_size(in_conditions) > 0) { - AST_NODE_T* last_in = hb_array_last(in_conditions); - end_position = last_in->location.end; - } - - if (hb_array_size(in_conditions) > 0) { - hb_array_T* case_match_errors = erb_node->base.errors; - erb_node->base.errors = NULL; - - AST_ERB_CASE_MATCH_NODE_T* case_match_node = ast_erb_case_match_node_init( - erb_node->tag_opening, - erb_node->content, - erb_node->tag_closing, - non_when_non_in_children, - in_conditions, - else_clause, - end_node, - start_position, - end_position, - case_match_errors - ); - - ast_node_free((AST_NODE_T*) erb_node); - - hb_array_append(output_array, (AST_NODE_T*) case_match_node); - hb_array_free(&when_conditions); - hb_array_free(&children); - - return index; - } - - hb_array_T* case_errors = erb_node->base.errors; - erb_node->base.errors = NULL; - - AST_ERB_CASE_NODE_T* case_node = ast_erb_case_node_init( - erb_node->tag_opening, - erb_node->content, - erb_node->tag_closing, - non_when_non_in_children, - when_conditions, - else_clause, - end_node, - start_position, - end_position, - case_errors - ); - - ast_node_free((AST_NODE_T*) erb_node); - - hb_array_append(output_array, (AST_NODE_T*) case_node); - hb_array_free(&in_conditions); - hb_array_free(&children); - - return index; - } - - if (initial_type == CONTROL_TYPE_BEGIN) { - index = process_block_children(node, array, index, children, context, initial_type); - - AST_ERB_RESCUE_NODE_T* rescue_clause = NULL; - AST_ERB_ELSE_NODE_T* else_clause = NULL; - AST_ERB_ENSURE_NODE_T* ensure_clause = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (next_type == CONTROL_TYPE_RESCUE) { - AST_NODE_T* rescue_node = NULL; - index = process_subsequent_block(node, array, index, &rescue_node, context, initial_type); - rescue_clause = (AST_ERB_RESCUE_NODE_T*) rescue_node; - } - } - } - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (next_type == CONTROL_TYPE_ELSE) { - hb_array_T* else_children = hb_array_init(8); - - index++; - - index = process_block_children(node, array, index, else_children, context, initial_type); - - hb_array_T* else_errors = next_erb->base.errors; - next_erb->base.errors = NULL; - - else_clause = ast_erb_else_node_init( - next_erb->tag_opening, - next_erb->content, - next_erb->tag_closing, - else_children, - next_erb->tag_opening->location.start, - erb_content_end_position(next_erb), - else_errors - ); - - ast_node_free((AST_NODE_T*) next_erb); - } - } - } - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (next_type == CONTROL_TYPE_ENSURE) { - hb_array_T* ensure_children = hb_array_init(8); - - index++; - - while (index < hb_array_size(array)) { - AST_NODE_T* child = hb_array_get(array, index); - - if (!child) { break; } - - if (child->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* child_erb = (AST_ERB_CONTENT_NODE_T*) child; - control_type_t child_type = detect_control_type(child_erb); - - if (child_type == CONTROL_TYPE_END) { break; } - } - - hb_array_append(ensure_children, child); - index++; - } - - hb_array_T* ensure_errors = next_erb->base.errors; - next_erb->base.errors = NULL; - - ensure_clause = ast_erb_ensure_node_init( - next_erb->tag_opening, - next_erb->content, - next_erb->tag_closing, - ensure_children, - next_erb->tag_opening->location.start, - erb_content_end_position(next_erb), - ensure_errors - ); - - ast_node_free((AST_NODE_T*) next_erb); - } - } - } - - AST_ERB_END_NODE_T* end_node = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* potential_end = hb_array_get(array, index); - - if (potential_end && potential_end->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* end_erb = (AST_ERB_CONTENT_NODE_T*) potential_end; - - if (detect_control_type(end_erb) == CONTROL_TYPE_END) { - hb_array_T* end_errors = end_erb->base.errors; - end_erb->base.errors = NULL; - - end_node = ast_erb_end_node_init( - end_erb->tag_opening, - end_erb->content, - end_erb->tag_closing, - end_erb->tag_opening->location.start, - erb_content_end_position(end_erb), - end_errors - ); - - ast_node_free((AST_NODE_T*) end_erb); - - index++; - } - } - } - - position_T start_position = erb_node->tag_opening->location.start; - position_T end_position = erb_content_end_position(erb_node); - - if (end_node) { - end_position = end_node->base.location.end; - } else if (ensure_clause) { - end_position = ensure_clause->base.location.end; - } else if (else_clause) { - end_position = else_clause->base.location.end; - } else if (rescue_clause) { - end_position = rescue_clause->base.location.end; - } - - hb_array_T* begin_errors = erb_node->base.errors; - erb_node->base.errors = NULL; - - AST_ERB_BEGIN_NODE_T* begin_node = ast_erb_begin_node_init( - erb_node->tag_opening, - erb_node->content, - erb_node->tag_closing, - children, - rescue_clause, - else_clause, - ensure_clause, - end_node, - start_position, - end_position, - begin_errors - ); - - ast_node_free((AST_NODE_T*) erb_node); - - hb_array_append(output_array, (AST_NODE_T*) begin_node); - return index; - } - - if (initial_type == CONTROL_TYPE_BLOCK) { - index = process_block_children(node, array, index, children, context, initial_type); - - AST_ERB_END_NODE_T* end_node = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* potential_close = hb_array_get(array, index); - - if (potential_close && potential_close->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* close_erb = (AST_ERB_CONTENT_NODE_T*) potential_close; - control_type_t close_type = detect_control_type(close_erb); - - if (close_type == CONTROL_TYPE_BLOCK_CLOSE || close_type == CONTROL_TYPE_END) { - hb_array_T* end_errors = close_erb->base.errors; - close_erb->base.errors = NULL; - - position_T close_end_pos = erb_content_end_position(close_erb); - - end_node = ast_erb_end_node_init( - close_erb->tag_opening, - close_erb->content, - close_erb->tag_closing, - close_erb->tag_opening->location.start, - close_end_pos, - end_errors - ); - - ast_node_free((AST_NODE_T*) close_erb); - - index++; - } - } - } - - position_T start_position = erb_node->tag_opening->location.start; - position_T end_position = erb_content_end_position(erb_node); - - if (end_node) { - end_position = end_node->base.location.end; - } else if (hb_array_size(children) > 0) { - AST_NODE_T* last_child = hb_array_last(children); - end_position = last_child->location.end; - } - - hb_array_T* block_errors = erb_node->base.errors; - erb_node->base.errors = NULL; - - AST_ERB_BLOCK_NODE_T* block_node = ast_erb_block_node_init( - erb_node->tag_opening, - erb_node->content, - erb_node->tag_closing, - children, - end_node, - start_position, - end_position, - block_errors - ); - - ast_node_free((AST_NODE_T*) erb_node); - - hb_array_append(output_array, (AST_NODE_T*) block_node); - return index; - } - - index = process_block_children(node, array, index, children, context, initial_type); - - AST_NODE_T* subsequent = NULL; - AST_ERB_END_NODE_T* end_node = NULL; - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (is_subsequent_type(initial_type, next_type)) { - index = process_subsequent_block(node, array, index, &subsequent, context, initial_type); - } - } - } - - if (index < hb_array_size(array)) { - AST_NODE_T* potential_end = hb_array_get(array, index); - - if (potential_end && potential_end->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* end_erb = (AST_ERB_CONTENT_NODE_T*) potential_end; - - if (detect_control_type(end_erb) == CONTROL_TYPE_END) { - hb_array_T* end_errors = end_erb->base.errors; - end_erb->base.errors = NULL; - - position_T end_erb_final_pos = erb_content_end_position(end_erb); - - end_node = ast_erb_end_node_init( - end_erb->tag_opening, - end_erb->content, - end_erb->tag_closing, - end_erb->tag_opening->location.start, - end_erb_final_pos, - end_errors - ); - - ast_node_free((AST_NODE_T*) end_erb); - - index++; - } - } - } - - AST_NODE_T* control_node = create_control_node(erb_node, children, subsequent, end_node, initial_type); - - if (control_node) { - ast_node_free((AST_NODE_T*) erb_node); - hb_array_append(output_array, control_node); - } else { - hb_array_free(&children); - } - - return index; -} - -static size_t process_subsequent_block( - AST_NODE_T* node, - hb_array_T* array, - size_t index, - AST_NODE_T** subsequent_out, - analyze_ruby_context_T* context, - control_type_t parent_type -) { - AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) hb_array_get(array, index); - control_type_t type = detect_control_type(erb_node); - hb_array_T* children = hb_array_init(8); - - index++; - - index = process_block_children(node, array, index, children, context, parent_type); - - AST_NODE_T* subsequent_node = create_control_node(erb_node, children, NULL, NULL, type); - - if (subsequent_node) { - ast_node_free((AST_NODE_T*) erb_node); - } else { - hb_array_free(&children); - } - - if (index < hb_array_size(array)) { - AST_NODE_T* next_node = hb_array_get(array, index); - - if (next_node && next_node->type == AST_ERB_CONTENT_NODE) { - AST_ERB_CONTENT_NODE_T* next_erb = (AST_ERB_CONTENT_NODE_T*) next_node; - control_type_t next_type = detect_control_type(next_erb); - - if (is_subsequent_type(parent_type, next_type) - && !(type == CONTROL_TYPE_RESCUE && (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_ENSURE))) { - - AST_NODE_T** next_subsequent = NULL; - - switch (type) { - case CONTROL_TYPE_ELSIF: { - if (subsequent_node->type == AST_ERB_IF_NODE) { - next_subsequent = &(((AST_ERB_IF_NODE_T*) subsequent_node)->subsequent); - } - - break; - } - - case CONTROL_TYPE_RESCUE: { - if (subsequent_node->type == AST_ERB_RESCUE_NODE && next_type == CONTROL_TYPE_RESCUE) { - AST_NODE_T* next_rescue_node = NULL; - index = process_subsequent_block(node, array, index, &next_rescue_node, context, parent_type); - - if (next_rescue_node) { - ((AST_ERB_RESCUE_NODE_T*) subsequent_node)->subsequent = (AST_ERB_RESCUE_NODE_T*) next_rescue_node; - } - - next_subsequent = NULL; - } - - break; - } - - default: break; - } - - if (next_subsequent) { - index = process_subsequent_block(node, array, index, next_subsequent, context, parent_type); - } - } - } - } - - *subsequent_out = subsequent_node; - return index; -} - -static size_t process_block_children( - AST_NODE_T* node, - hb_array_T* array, - size_t index, - hb_array_T* children_array, - analyze_ruby_context_T* context, - control_type_t parent_type -) { - while (index < hb_array_size(array)) { - AST_NODE_T* child = hb_array_get(array, index); - - if (!child) { break; } - - if (child->type != AST_ERB_CONTENT_NODE) { - hb_array_append(children_array, child); - index++; - continue; - } - - AST_ERB_CONTENT_NODE_T* erb_content = (AST_ERB_CONTENT_NODE_T*) child; - control_type_t child_type = detect_control_type(erb_content); - - if (is_terminator_type(parent_type, child_type)) { break; } - - if (child_type == CONTROL_TYPE_IF || child_type == CONTROL_TYPE_CASE || child_type == CONTROL_TYPE_CASE_MATCH - || child_type == CONTROL_TYPE_BEGIN || child_type == CONTROL_TYPE_UNLESS || child_type == CONTROL_TYPE_WHILE - || child_type == CONTROL_TYPE_UNTIL || child_type == CONTROL_TYPE_FOR || child_type == CONTROL_TYPE_BLOCK) { - hb_array_T* temp_array = hb_array_init(1); - size_t new_index = process_control_structure(node, array, index, temp_array, context, child_type); - - if (hb_array_size(temp_array) > 0) { hb_array_append(children_array, hb_array_first(temp_array)); } - - hb_array_free(&temp_array); - - index = new_index; - continue; - } - - hb_array_append(children_array, child); - index++; - } - - return index; -} - -hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context) { - hb_array_T* new_array = hb_array_init(hb_array_size(array)); - size_t index = 0; - - while (index < hb_array_size(array)) { - AST_NODE_T* item = hb_array_get(array, index); - - if (!item) { break; } - - if (item->type != AST_ERB_CONTENT_NODE) { - hb_array_append(new_array, item); - index++; - continue; - } - - AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) item; - control_type_t type = detect_control_type(erb_node); - - switch (type) { - case CONTROL_TYPE_IF: - case CONTROL_TYPE_CASE: - case CONTROL_TYPE_CASE_MATCH: - case CONTROL_TYPE_BEGIN: - case CONTROL_TYPE_UNLESS: - case CONTROL_TYPE_WHILE: - case CONTROL_TYPE_UNTIL: - case CONTROL_TYPE_FOR: - case CONTROL_TYPE_BLOCK: - index = process_control_structure(node, array, index, new_array, context, type); - continue; - - case CONTROL_TYPE_YIELD: { - AST_NODE_T* yield_node = create_control_node(erb_node, NULL, NULL, NULL, type); - - if (yield_node) { - ast_node_free((AST_NODE_T*) erb_node); - hb_array_append(new_array, yield_node); - } else { - hb_array_append(new_array, item); - } - - index++; - break; - } - - default: - hb_array_append(new_array, item); - index++; - break; - } - } - - return new_array; -} - -static bool detect_invalid_erb_structures(const AST_NODE_T* node, void* data) { - invalid_erb_context_T* context = (invalid_erb_context_T*) data; - - if (node->type == AST_HTML_ATTRIBUTE_NAME_NODE) { return false; } - - bool is_loop_node = - (node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE || node->type == AST_ERB_FOR_NODE - || node->type == AST_ERB_BLOCK_NODE); - - bool is_begin_node = (node->type == AST_ERB_BEGIN_NODE); - - if (is_loop_node) { context->loop_depth++; } - - if (is_begin_node) { context->rescue_depth++; } - - if (node->type == AST_ERB_CONTENT_NODE) { - const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) node; - - if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { - analyzed_ruby_T* analyzed = content_node->analyzed_ruby; - - // =begin - if (has_error_message(analyzed, "embedded document meets end of file")) { - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return true; - } - - // =end - if (has_error_message(analyzed, "unexpected '=', ignoring it") - && has_error_message(analyzed, "unexpected 'end', ignoring it")) { - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return true; - } - - const char* keyword = NULL; - - if (context->loop_depth == 0) { - if (has_error_message(analyzed, "Invalid break")) { - keyword = "`<% break %>`"; - } else if (has_error_message(analyzed, "Invalid next")) { - keyword = "`<% next %>`"; - } else if (has_error_message(analyzed, "Invalid redo")) { - keyword = "`<% redo %>`"; - } - } else { - if (has_error_message(analyzed, "Invalid redo") || has_error_message(analyzed, "Invalid break") - || has_error_message(analyzed, "Invalid next")) { - - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return true; - } - } - - if (context->rescue_depth == 0) { - if (has_error_message(analyzed, "Invalid retry without rescue")) { keyword = "`<% retry %>`"; } - } else { - if (has_error_message(analyzed, "Invalid retry without rescue")) { - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return true; - } - } - - if (keyword == NULL) { keyword = erb_keyword_from_analyzed_ruby(analyzed); } - - if (keyword != NULL && !token_value_empty(content_node->tag_closing)) { - append_erb_control_flow_scope_error(keyword, node->location.start, node->location.end, node->errors); - } - } - } - - if (node->type == AST_ERB_IF_NODE) { - const AST_ERB_IF_NODE_T* if_node = (const AST_ERB_IF_NODE_T*) node; - - if (if_node->end_node == NULL) { check_erb_node_for_missing_end(node); } - - if (if_node->statements != NULL) { - for (size_t i = 0; i < hb_array_size(if_node->statements); i++) { - AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(if_node->statements, i); - - if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } - } - } - - AST_NODE_T* subsequent = if_node->subsequent; - - while (subsequent != NULL) { - if (subsequent->type == AST_ERB_CONTENT_NODE) { - const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) subsequent; - - if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { - analyzed_ruby_T* analyzed = content_node->analyzed_ruby; - const char* keyword = erb_keyword_from_analyzed_ruby(analyzed); - - if (!token_value_empty(content_node->tag_closing)) { - append_erb_control_flow_scope_error( - keyword, - subsequent->location.start, - subsequent->location.end, - subsequent->errors - ); - } - } - } - - if (subsequent->type == AST_ERB_IF_NODE) { - const AST_ERB_IF_NODE_T* elsif_node = (const AST_ERB_IF_NODE_T*) subsequent; - - if (elsif_node->statements != NULL) { - for (size_t i = 0; i < hb_array_size(elsif_node->statements); i++) { - AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(elsif_node->statements, i); - - if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } - } - } - - subsequent = elsif_node->subsequent; - } else if (subsequent->type == AST_ERB_ELSE_NODE) { - const AST_ERB_ELSE_NODE_T* else_node = (const AST_ERB_ELSE_NODE_T*) subsequent; - - if (else_node->statements != NULL) { - for (size_t i = 0; i < hb_array_size(else_node->statements); i++) { - AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(else_node->statements, i); - - if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } - } - } - - break; - } else { - break; - } - } - } - - if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE - || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE - || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { - herb_visit_child_nodes(node, detect_invalid_erb_structures, context); - } - - if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE - || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE - || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { - check_erb_node_for_missing_end(node); - - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return false; - } - - if (node->type == AST_ERB_IF_NODE) { - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return false; - } - - bool result = true; - - if (is_loop_node) { context->loop_depth--; } - if (is_begin_node) { context->rescue_depth--; } - - return result; -} - -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); - - analyze_ruby_context_T* context = malloc(sizeof(analyze_ruby_context_T)); - - if (!context) { return; } - - context->document = document; - context->parent = NULL; - context->ruby_context_stack = hb_array_init(8); - - herb_visit_node((AST_NODE_T*) document, transform_erb_nodes, context); - herb_transform_conditional_elements(document); - herb_transform_conditional_open_tags(document); - - invalid_erb_context_T* invalid_context = malloc(sizeof(invalid_erb_context_T)); - - if (!invalid_context) { - hb_array_free(&context->ruby_context_stack); - free(context); - return; - } - - invalid_context->loop_depth = 0; - invalid_context->rescue_depth = 0; - - herb_visit_node((AST_NODE_T*) document, detect_invalid_erb_structures, invalid_context); - - herb_analyze_parse_errors(document, source); - - herb_parser_match_html_tags_post_analyze(document, strict); - - hb_array_free(&context->ruby_context_stack); - - free(context); - free(invalid_context); -} - -static void parse_erb_content_errors(AST_NODE_T* erb_node, const char* source) { - if (!erb_node || erb_node->type != AST_ERB_CONTENT_NODE) { return; } - AST_ERB_CONTENT_NODE_T* content_node = (AST_ERB_CONTENT_NODE_T*) erb_node; - - if (!content_node->content || !content_node->content->value) { return; } - - const char* content = content_node->content->value; - if (strlen(content) == 0) { return; } - - pm_parser_t parser; - pm_options_t options = { 0, .partial_script = true }; - pm_parser_init(&parser, (const uint8_t*) content, strlen(content), &options); - - pm_node_t* root = pm_parse(&parser); - - const pm_diagnostic_t* error = (const pm_diagnostic_t*) parser.error_list.head; - - if (error != NULL) { - RUBY_PARSE_ERROR_T* parse_error = - ruby_parse_error_from_prism_error_with_positions(error, erb_node->location.start, erb_node->location.end); - - hb_array_append(erb_node->errors, parse_error); - } - - pm_node_destroy(&parser, root); - pm_parser_free(&parser); - pm_options_free(&options); -} - -void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source) { - char* extracted_ruby = herb_extract_ruby_with_semicolons(source); - - if (!extracted_ruby) { return; } - - pm_parser_t parser; - pm_options_t options = { 0, .partial_script = true }; - pm_parser_init(&parser, (const uint8_t*) extracted_ruby, strlen(extracted_ruby), &options); - - pm_node_t* root = pm_parse(&parser); - - for (const pm_diagnostic_t* error = (const pm_diagnostic_t*) parser.error_list.head; error != NULL; - error = (const pm_diagnostic_t*) error->node.next) { - size_t error_offset = (size_t) (error->location.start - parser.start); - - if (strstr(error->message, "unexpected ';'") != NULL) { - if (error_offset < strlen(extracted_ruby) && extracted_ruby[error_offset] == ';') { - if (error_offset >= strlen(source) || source[error_offset] != ';') { - AST_NODE_T* erb_node = find_erb_content_at_offset(document, source, error_offset); - - if (erb_node) { parse_erb_content_errors(erb_node, source); } - - continue; - } - } - } - - RUBY_PARSE_ERROR_T* parse_error = ruby_parse_error_from_prism_error(error, (AST_NODE_T*) document, source, &parser); - hb_array_append(document->base.errors, parse_error); - } - - pm_node_destroy(&parser, root); - pm_parser_free(&parser); - pm_options_free(&options); - free(extracted_ruby); -} diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c new file mode 100644 index 000000000..7880640e8 --- /dev/null +++ b/src/analyze/analyze.c @@ -0,0 +1,793 @@ +#include "../include/analyze/analyze.h" +#include "../include/analyze/analyzed_ruby.h" +#include "../include/analyze/builders.h" +#include "../include/analyze/conditional_elements.h" +#include "../include/analyze/conditional_open_tags.h" +#include "../include/analyze/control_type.h" +#include "../include/analyze/helpers.h" +#include "../include/analyze/invalid_structures.h" +#include "../include/ast_node.h" +#include "../include/ast_nodes.h" +#include "../include/errors.h" +#include "../include/location.h" +#include "../include/parser.h" +#include "../include/position.h" +#include "../include/token_struct.h" +#include "../include/util/hb_array.h" +#include "../include/util/hb_string.h" +#include "../include/util/string.h" +#include "../include/visitor.h" + +#include +#include +#include +#include +#include + +static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) { + analyzed_ruby_T* analyzed = init_analyzed_ruby(source); + + pm_visit_node(analyzed->root, search_if_nodes, analyzed); + pm_visit_node(analyzed->root, search_block_nodes, analyzed); + pm_visit_node(analyzed->root, search_case_nodes, analyzed); + pm_visit_node(analyzed->root, search_case_match_nodes, analyzed); + pm_visit_node(analyzed->root, search_while_nodes, analyzed); + pm_visit_node(analyzed->root, search_for_nodes, analyzed); + pm_visit_node(analyzed->root, search_until_nodes, analyzed); + pm_visit_node(analyzed->root, search_begin_nodes, analyzed); + pm_visit_node(analyzed->root, search_unless_nodes, analyzed); + pm_visit_node(analyzed->root, search_when_nodes, analyzed); + pm_visit_node(analyzed->root, search_in_nodes, analyzed); + + search_unexpected_elsif_nodes(analyzed); + search_unexpected_else_nodes(analyzed); + search_unexpected_end_nodes(analyzed); + search_unexpected_when_nodes(analyzed); + search_unexpected_in_nodes(analyzed); + + search_unexpected_rescue_nodes(analyzed); + search_unexpected_ensure_nodes(analyzed); + search_yield_nodes(analyzed->root, analyzed); + search_then_keywords(analyzed->root, analyzed); + search_unexpected_block_closing_nodes(analyzed); + + if (!analyzed->valid) { pm_visit_node(analyzed->root, search_unclosed_control_flows, analyzed); } + + return analyzed; +} + +static bool analyze_erb_content(const AST_NODE_T* node, void* data) { + if (node->type == AST_ERB_CONTENT_NODE) { + AST_ERB_CONTENT_NODE_T* erb_content_node = (AST_ERB_CONTENT_NODE_T*) node; + + const char* opening = erb_content_node->tag_opening->value; + + if (!string_equals(opening, "<%%") && !string_equals(opening, "<%%=") && !string_equals(opening, "<%#") + && !string_equals(opening, "<%graphql")) { + analyzed_ruby_T* analyzed = herb_analyze_ruby(hb_string(erb_content_node->content->value)); + + erb_content_node->parsed = true; + erb_content_node->valid = analyzed->valid; + erb_content_node->analyzed_ruby = analyzed; + + if (!analyzed->valid && analyzed->unclosed_control_flow_count >= 2) { + append_erb_multiple_blocks_in_tag_error( + erb_content_node->base.location.start, + erb_content_node->base.location.end, + erb_content_node->base.errors + ); + } + + if (!analyzed->valid + && ((analyzed->case_node_count > 0 && analyzed->when_node_count > 0) + || (analyzed->case_match_node_count > 0 && analyzed->in_node_count > 0))) { + append_erb_case_with_conditions_error( + erb_content_node->base.location.start, + erb_content_node->base.location.end, + erb_content_node->base.errors + ); + } + } else { + erb_content_node->parsed = false; + erb_content_node->valid = true; + erb_content_node->analyzed_ruby = NULL; + } + } + + herb_visit_child_nodes(node, analyze_erb_content, data); + + return false; +} + +static size_t process_block_children( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* children_array, + analyze_ruby_context_T* context, + control_type_t parent_type +); + +static size_t process_subsequent_block( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + AST_NODE_T** subsequent_out, + analyze_ruby_context_T* context, + control_type_t parent_type +); + +// --- Helper functions for structure processing --- + +static bool control_type_matches_any(control_type_t type, const control_type_t* list, size_t count) { + if (!list) { return false; } + + for (size_t i = 0; i < count; i++) { + if (type == list[i]) { return true; } + } + + return false; +} + +static AST_ERB_CONTENT_NODE_T* get_erb_content_at(hb_array_T* array, size_t index) { + if (index >= hb_array_size(array)) { return NULL; } + + AST_NODE_T* node = hb_array_get(array, index); + + if (!node || node->type != AST_ERB_CONTENT_NODE) { return NULL; } + + return (AST_ERB_CONTENT_NODE_T*) node; +} + +static bool peek_control_type( + hb_array_T* array, + size_t index, + control_type_t* out_type, + AST_ERB_CONTENT_NODE_T** out_node +) { + AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index); + + if (!erb_node) { return false; } + + if (out_type) { *out_type = detect_control_type(erb_node); } + if (out_node) { *out_node = erb_node; } + + return true; +} + +static void collect_children_until( + hb_array_T* array, + size_t* index, + hb_array_T* destination, + const control_type_t* stop_types, + size_t stop_count +) { + while (*index < hb_array_size(array)) { + AST_NODE_T* child = hb_array_get(array, *index); + + if (!child) { break; } + + if (child->type == AST_ERB_CONTENT_NODE) { + control_type_t child_type = detect_control_type((AST_ERB_CONTENT_NODE_T*) child); + + if (stop_count > 0 && control_type_matches_any(child_type, stop_types, stop_count)) { break; } + } + + hb_array_append(destination, child); + + (*index)++; + } +} + +static AST_ERB_END_NODE_T* build_end_node(AST_ERB_CONTENT_NODE_T* end_erb) { + if (!end_erb) { return NULL; } + + hb_array_T* end_errors = end_erb->base.errors; + end_erb->base.errors = NULL; + + AST_ERB_END_NODE_T* end_node = ast_erb_end_node_init( + end_erb->tag_opening, + end_erb->content, + end_erb->tag_closing, + end_erb->tag_opening->location.start, + erb_content_end_position(end_erb), + end_errors + ); + + ast_node_free((AST_NODE_T*) end_erb); + + return end_node; +} + +static AST_ERB_END_NODE_T* consume_end_node( + hb_array_T* array, + size_t* index, + const control_type_t* allowed_types, + size_t allowed_count +) { + if (allowed_count == 0 || !allowed_types) { return NULL; } + + AST_ERB_CONTENT_NODE_T* candidate = get_erb_content_at(array, *index); + + if (!candidate) { return NULL; } + + control_type_t candidate_type = detect_control_type(candidate); + + if (!control_type_matches_any(candidate_type, allowed_types, allowed_count)) { return NULL; } + + (*index)++; + + return build_end_node(candidate); +} + +// --- Structure processing functions --- + +static size_t process_case_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context +); + +static size_t process_begin_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context +); + +static size_t process_generic_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context, + control_type_t initial_type +); + +static size_t process_control_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context, + control_type_t initial_type +) { + switch (initial_type) { + case CONTROL_TYPE_CASE: + case CONTROL_TYPE_CASE_MATCH: return process_case_structure(node, array, index, output_array, context); + + case CONTROL_TYPE_BEGIN: return process_begin_structure(node, array, index, output_array, context); + + default: return process_generic_structure(node, array, index, output_array, context, initial_type); + } +} + +static size_t process_case_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context +) { + AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index); + if (!erb_node) { return index; } + + hb_array_T* when_conditions = hb_array_init(8); + hb_array_T* in_conditions = hb_array_init(8); + hb_array_T* non_when_non_in_children = hb_array_init(8); + + index++; + + const control_type_t prelude_stop[] = { CONTROL_TYPE_WHEN, CONTROL_TYPE_IN, CONTROL_TYPE_END }; + collect_children_until( + array, + &index, + non_when_non_in_children, + prelude_stop, + sizeof(prelude_stop) / sizeof(prelude_stop[0]) + ); + + while (index < hb_array_size(array)) { + AST_ERB_CONTENT_NODE_T* next_erb = get_erb_content_at(array, index); + + if (!next_erb) { + AST_NODE_T* next_node = hb_array_get(array, index); + + if (!next_node) { break; } + + hb_array_append(non_when_non_in_children, next_node); + index++; + continue; + } + + control_type_t next_type = detect_control_type(next_erb); + + if (next_type == CONTROL_TYPE_WHEN || next_type == CONTROL_TYPE_IN) { + hb_array_T* statements = hb_array_init(8); + index++; + index = process_block_children(node, array, index, statements, context, next_type); + + hb_array_T* cond_errors = next_erb->base.errors; + next_erb->base.errors = NULL; + + location_T* then_keyword = compute_then_keyword(next_erb, next_type); + position_T cond_start = next_erb->tag_opening->location.start; + position_T cond_end = erb_content_end_position(next_erb); + + AST_NODE_T* condition_node; + + if (next_type == CONTROL_TYPE_WHEN) { + condition_node = (AST_NODE_T*) ast_erb_when_node_init( + next_erb->tag_opening, + next_erb->content, + next_erb->tag_closing, + then_keyword, + statements, + cond_start, + cond_end, + cond_errors + ); + } else { + condition_node = (AST_NODE_T*) ast_erb_in_node_init( + next_erb->tag_opening, + next_erb->content, + next_erb->tag_closing, + then_keyword, + statements, + cond_start, + cond_end, + cond_errors + ); + } + + ast_node_free((AST_NODE_T*) next_erb); + hb_array_append(next_type == CONTROL_TYPE_WHEN ? when_conditions : in_conditions, condition_node); + continue; + } + + if (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_END) { break; } + + hb_array_append(non_when_non_in_children, (AST_NODE_T*) next_erb); + index++; + } + + AST_ERB_ELSE_NODE_T* else_clause = NULL; + control_type_t next_type = CONTROL_TYPE_UNKNOWN; + AST_ERB_CONTENT_NODE_T* next_erb = NULL; + + if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ELSE) { + hb_array_T* else_children = hb_array_init(8); + index++; + + index = process_block_children(node, array, index, else_children, context, CONTROL_TYPE_CASE); + + hb_array_T* else_errors = next_erb->base.errors; + next_erb->base.errors = NULL; + + else_clause = ast_erb_else_node_init( + next_erb->tag_opening, + next_erb->content, + next_erb->tag_closing, + else_children, + next_erb->tag_opening->location.start, + erb_content_end_position(next_erb), + else_errors + ); + + ast_node_free((AST_NODE_T*) next_erb); + } + + const control_type_t end_types[] = { CONTROL_TYPE_END }; + AST_ERB_END_NODE_T* end_node = consume_end_node(array, &index, end_types, sizeof(end_types) / sizeof(end_types[0])); + + position_T start_position = erb_node->tag_opening->location.start; + position_T end_position = erb_content_end_position(erb_node); + + if (end_node) { + end_position = end_node->base.location.end; + } else if (else_clause) { + end_position = else_clause->base.location.end; + } else if (hb_array_size(when_conditions) > 0) { + AST_NODE_T* last_when = hb_array_last(when_conditions); + end_position = last_when->location.end; + } else if (hb_array_size(in_conditions) > 0) { + AST_NODE_T* last_in = hb_array_last(in_conditions); + end_position = last_in->location.end; + } + + hb_array_T* node_errors = erb_node->base.errors; + erb_node->base.errors = NULL; + + if (hb_array_size(in_conditions) > 0) { + AST_ERB_CASE_MATCH_NODE_T* case_match_node = ast_erb_case_match_node_init( + erb_node->tag_opening, + erb_node->content, + erb_node->tag_closing, + non_when_non_in_children, + in_conditions, + else_clause, + end_node, + start_position, + end_position, + node_errors + ); + + ast_node_free((AST_NODE_T*) erb_node); + hb_array_append(output_array, (AST_NODE_T*) case_match_node); + hb_array_free(&when_conditions); + return index; + } + + AST_ERB_CASE_NODE_T* case_node = ast_erb_case_node_init( + erb_node->tag_opening, + erb_node->content, + erb_node->tag_closing, + non_when_non_in_children, + when_conditions, + else_clause, + end_node, + start_position, + end_position, + node_errors + ); + + ast_node_free((AST_NODE_T*) erb_node); + hb_array_append(output_array, (AST_NODE_T*) case_node); + hb_array_free(&in_conditions); + + return index; +} + +static size_t process_begin_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context +) { + AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index); + if (!erb_node) { return index; } + hb_array_T* children = hb_array_init(8); + + index++; + index = process_block_children(node, array, index, children, context, CONTROL_TYPE_BEGIN); + + AST_ERB_RESCUE_NODE_T* rescue_clause = NULL; + AST_ERB_ELSE_NODE_T* else_clause = NULL; + AST_ERB_ENSURE_NODE_T* ensure_clause = NULL; + + control_type_t next_type = CONTROL_TYPE_UNKNOWN; + AST_ERB_CONTENT_NODE_T* next_erb = NULL; + + if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_RESCUE) { + AST_NODE_T* rescue_node = NULL; + index = process_subsequent_block(node, array, index, &rescue_node, context, CONTROL_TYPE_BEGIN); + rescue_clause = (AST_ERB_RESCUE_NODE_T*) rescue_node; + } + + if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ELSE) { + hb_array_T* else_children = hb_array_init(8); + index++; + + index = process_block_children(node, array, index, else_children, context, CONTROL_TYPE_BEGIN); + + hb_array_T* else_errors = next_erb->base.errors; + next_erb->base.errors = NULL; + + else_clause = ast_erb_else_node_init( + next_erb->tag_opening, + next_erb->content, + next_erb->tag_closing, + else_children, + next_erb->tag_opening->location.start, + erb_content_end_position(next_erb), + else_errors + ); + + ast_node_free((AST_NODE_T*) next_erb); + } + + if (peek_control_type(array, index, &next_type, &next_erb) && next_type == CONTROL_TYPE_ENSURE) { + hb_array_T* ensure_children = hb_array_init(8); + index++; + + const control_type_t ensure_stop[] = { CONTROL_TYPE_END }; + collect_children_until(array, &index, ensure_children, ensure_stop, sizeof(ensure_stop) / sizeof(ensure_stop[0])); + + hb_array_T* ensure_errors = next_erb->base.errors; + next_erb->base.errors = NULL; + + ensure_clause = ast_erb_ensure_node_init( + next_erb->tag_opening, + next_erb->content, + next_erb->tag_closing, + ensure_children, + next_erb->tag_opening->location.start, + erb_content_end_position(next_erb), + ensure_errors + ); + + ast_node_free((AST_NODE_T*) next_erb); + } + + const control_type_t end_types[] = { CONTROL_TYPE_END }; + AST_ERB_END_NODE_T* end_node = consume_end_node(array, &index, end_types, sizeof(end_types) / sizeof(end_types[0])); + + position_T start_position = erb_node->tag_opening->location.start; + position_T end_position = erb_content_end_position(erb_node); + + if (end_node) { + end_position = end_node->base.location.end; + } else if (ensure_clause) { + end_position = ensure_clause->base.location.end; + } else if (else_clause) { + end_position = else_clause->base.location.end; + } else if (rescue_clause) { + end_position = rescue_clause->base.location.end; + } + + hb_array_T* begin_errors = erb_node->base.errors; + erb_node->base.errors = NULL; + + AST_ERB_BEGIN_NODE_T* begin_node = ast_erb_begin_node_init( + erb_node->tag_opening, + erb_node->content, + erb_node->tag_closing, + children, + rescue_clause, + else_clause, + ensure_clause, + end_node, + start_position, + end_position, + begin_errors + ); + + ast_node_free((AST_NODE_T*) erb_node); + hb_array_append(output_array, (AST_NODE_T*) begin_node); + + return index; +} + +static size_t process_generic_structure( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* output_array, + analyze_ruby_context_T* context, + control_type_t initial_type +) { + AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index); + if (!erb_node) { return index; } + hb_array_T* children = hb_array_init(8); + + index++; + index = process_block_children(node, array, index, children, context, initial_type); + + AST_NODE_T* subsequent = NULL; + control_type_t next_type = CONTROL_TYPE_UNKNOWN; + + if (peek_control_type(array, index, &next_type, NULL) && is_subsequent_type(initial_type, next_type)) { + index = process_subsequent_block(node, array, index, &subsequent, context, initial_type); + } + + AST_ERB_END_NODE_T* end_node = NULL; + + if (initial_type == CONTROL_TYPE_BLOCK) { + const control_type_t block_end_types[] = { CONTROL_TYPE_BLOCK_CLOSE, CONTROL_TYPE_END }; + end_node = consume_end_node(array, &index, block_end_types, sizeof(block_end_types) / sizeof(block_end_types[0])); + } else { + const control_type_t default_end_types[] = { CONTROL_TYPE_END }; + end_node = + consume_end_node(array, &index, default_end_types, sizeof(default_end_types) / sizeof(default_end_types[0])); + } + + AST_NODE_T* control_node = create_control_node(erb_node, children, subsequent, end_node, initial_type); + + if (control_node) { + ast_node_free((AST_NODE_T*) erb_node); + hb_array_append(output_array, control_node); + } else { + hb_array_free(&children); + } + + return index; +} + +static size_t process_subsequent_block( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + AST_NODE_T** subsequent_out, + analyze_ruby_context_T* context, + control_type_t parent_type +) { + AST_ERB_CONTENT_NODE_T* erb_node = get_erb_content_at(array, index); + + if (!erb_node) { return index; } + + control_type_t type = detect_control_type(erb_node); + hb_array_T* children = hb_array_init(8); + + index++; + + index = process_block_children(node, array, index, children, context, parent_type); + + AST_NODE_T* subsequent_node = create_control_node(erb_node, children, NULL, NULL, type); + + if (subsequent_node) { + ast_node_free((AST_NODE_T*) erb_node); + } else { + hb_array_free(&children); + } + + control_type_t next_type = CONTROL_TYPE_UNKNOWN; + + if (peek_control_type(array, index, &next_type, NULL) && is_subsequent_type(parent_type, next_type) + && !(type == CONTROL_TYPE_RESCUE && (next_type == CONTROL_TYPE_ELSE || next_type == CONTROL_TYPE_ENSURE))) { + + AST_NODE_T** next_subsequent = NULL; + + switch (type) { + case CONTROL_TYPE_ELSIF: { + if (subsequent_node && subsequent_node->type == AST_ERB_IF_NODE) { + next_subsequent = &(((AST_ERB_IF_NODE_T*) subsequent_node)->subsequent); + } + + break; + } + + case CONTROL_TYPE_RESCUE: { + if (subsequent_node && subsequent_node->type == AST_ERB_RESCUE_NODE && next_type == CONTROL_TYPE_RESCUE) { + AST_NODE_T* next_rescue_node = NULL; + index = process_subsequent_block(node, array, index, &next_rescue_node, context, parent_type); + + if (next_rescue_node) { + ((AST_ERB_RESCUE_NODE_T*) subsequent_node)->subsequent = (AST_ERB_RESCUE_NODE_T*) next_rescue_node; + } + + next_subsequent = NULL; + } + + break; + } + + default: break; + } + + if (next_subsequent) { + index = process_subsequent_block(node, array, index, next_subsequent, context, parent_type); + } + } + + *subsequent_out = subsequent_node; + return index; +} + +static size_t process_block_children( + AST_NODE_T* node, + hb_array_T* array, + size_t index, + hb_array_T* children_array, + analyze_ruby_context_T* context, + control_type_t parent_type +) { + while (index < hb_array_size(array)) { + AST_NODE_T* child = hb_array_get(array, index); + + if (!child) { break; } + + if (child->type != AST_ERB_CONTENT_NODE) { + hb_array_append(children_array, child); + index++; + continue; + } + + AST_ERB_CONTENT_NODE_T* erb_content = (AST_ERB_CONTENT_NODE_T*) child; + control_type_t child_type = detect_control_type(erb_content); + + if (is_terminator_type(parent_type, child_type)) { break; } + + if (is_compound_control_type(child_type)) { + hb_array_T* temp_array = hb_array_init(1); + size_t new_index = process_control_structure(node, array, index, temp_array, context, child_type); + + if (hb_array_size(temp_array) > 0) { hb_array_append(children_array, hb_array_first(temp_array)); } + + hb_array_free(&temp_array); + + index = new_index; + continue; + } + + hb_array_append(children_array, child); + index++; + } + + return index; +} + +hb_array_T* rewrite_node_array(AST_NODE_T* node, hb_array_T* array, analyze_ruby_context_T* context) { + hb_array_T* new_array = hb_array_init(hb_array_size(array)); + size_t index = 0; + + while (index < hb_array_size(array)) { + AST_NODE_T* item = hb_array_get(array, index); + + if (!item) { break; } + + if (item->type != AST_ERB_CONTENT_NODE) { + hb_array_append(new_array, item); + index++; + continue; + } + + AST_ERB_CONTENT_NODE_T* erb_node = (AST_ERB_CONTENT_NODE_T*) item; + control_type_t type = detect_control_type(erb_node); + + if (is_compound_control_type(type)) { + index = process_control_structure(node, array, index, new_array, context, type); + continue; + } + + if (type == CONTROL_TYPE_YIELD) { + AST_NODE_T* yield_node = create_control_node(erb_node, NULL, NULL, NULL, type); + + if (yield_node) { + ast_node_free((AST_NODE_T*) erb_node); + hb_array_append(new_array, yield_node); + } else { + hb_array_append(new_array, item); + } + + index++; + continue; + } + + hb_array_append(new_array, item); + index++; + } + + 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); + + analyze_ruby_context_T* context = malloc(sizeof(analyze_ruby_context_T)); + + if (!context) { return; } + + context->document = document; + context->parent = NULL; + context->ruby_context_stack = hb_array_init(8); + + herb_visit_node((AST_NODE_T*) document, transform_erb_nodes, context); + herb_transform_conditional_elements(document); + herb_transform_conditional_open_tags(document); + + invalid_erb_context_T* invalid_context = malloc(sizeof(invalid_erb_context_T)); + + if (!invalid_context) { + hb_array_free(&context->ruby_context_stack); + free(context); + return; + } + + invalid_context->loop_depth = 0; + invalid_context->rescue_depth = 0; + + herb_visit_node((AST_NODE_T*) document, detect_invalid_erb_structures, invalid_context); + + herb_analyze_parse_errors(document, source); + + herb_parser_match_html_tags_post_analyze(document, strict); + + hb_array_free(&context->ruby_context_stack); + + free(context); + free(invalid_context); +} diff --git a/src/analyzed_ruby.c b/src/analyze/analyzed_ruby.c similarity index 95% rename from src/analyzed_ruby.c rename to src/analyze/analyzed_ruby.c index dd7f7fd4a..30f534186 100644 --- a/src/analyzed_ruby.c +++ b/src/analyze/analyzed_ruby.c @@ -1,5 +1,5 @@ -#include "include/analyzed_ruby.h" -#include "include/util/hb_string.h" +#include "../include/analyze/analyzed_ruby.h" +#include "../include/util/hb_string.h" #include #include diff --git a/src/analyze/builders.c b/src/analyze/builders.c new file mode 100644 index 000000000..88a54582e --- /dev/null +++ b/src/analyze/builders.c @@ -0,0 +1,314 @@ +#include "../include/analyze/builders.h" +#include "../include/analyze/analyze.h" +#include "../include/ast_nodes.h" +#include "../include/location.h" +#include "../include/position.h" +#include "../include/prism_helpers.h" +#include "../include/token_struct.h" +#include "../include/util/hb_array.h" + +#include +#include + +position_T erb_content_end_position(const AST_ERB_CONTENT_NODE_T* erb_node) { + if (erb_node->tag_closing != NULL) { + return erb_node->tag_closing->location.end; + } else if (erb_node->content != NULL) { + return erb_node->content->location.end; + } else { + return erb_node->tag_opening->location.end; + } +} + +location_T* compute_then_keyword(AST_ERB_CONTENT_NODE_T* erb_node, control_type_t control_type) { + if (control_type != CONTROL_TYPE_IF && control_type != CONTROL_TYPE_ELSIF && control_type != CONTROL_TYPE_UNLESS + && control_type != CONTROL_TYPE_WHEN && control_type != CONTROL_TYPE_IN) { + return NULL; + } + + token_T* content = erb_node->content; + const char* source = content ? content->value : NULL; + location_T* then_keyword = NULL; + + if (control_type == CONTROL_TYPE_WHEN || control_type == CONTROL_TYPE_IN) { + if (source != NULL && strstr(source, "then") != NULL) { + then_keyword = get_then_keyword_location_wrapped(source, control_type == CONTROL_TYPE_IN); + } + } else if (control_type == CONTROL_TYPE_ELSIF) { + if (source != NULL && strstr(source, "then") != NULL) { + then_keyword = get_then_keyword_location_elsif_wrapped(source); + } + } else { + then_keyword = get_then_keyword_location(erb_node->analyzed_ruby, source); + } + + if (then_keyword != NULL && content != NULL) { + position_T content_start = content->location.start; + + then_keyword->start.line = content_start.line + then_keyword->start.line - 1; + then_keyword->start.column = content_start.column + then_keyword->start.column; + then_keyword->end.line = content_start.line + then_keyword->end.line - 1; + then_keyword->end.column = content_start.column + then_keyword->end.column; + } + + return then_keyword; +} + +typedef struct { + AST_ERB_CONTENT_NODE_T* erb; + hb_array_T* children; + AST_NODE_T* subsequent; + AST_ERB_END_NODE_T* end_node; + token_T* tag_opening; + token_T* content; + token_T* tag_closing; + location_T* then_keyword; + position_T start_position; + position_T end_position; + hb_array_T* errors; + control_type_t control_type; +} control_builder_context_T; + +typedef AST_NODE_T* (*control_builder_fn)(control_builder_context_T* context); + +typedef struct { + control_type_t type; + control_builder_fn builder; +} control_builder_entry_T; + +static AST_NODE_T* build_if_node(control_builder_context_T* context); +static AST_NODE_T* build_else_node(control_builder_context_T* context); +static AST_NODE_T* build_when_node(control_builder_context_T* context); +static AST_NODE_T* build_in_node(control_builder_context_T* context); +static AST_NODE_T* build_rescue_node(control_builder_context_T* context); +static AST_NODE_T* build_ensure_node(control_builder_context_T* context); +static AST_NODE_T* build_unless_node(control_builder_context_T* context); +static AST_NODE_T* build_while_node(control_builder_context_T* context); +static AST_NODE_T* build_until_node(control_builder_context_T* context); +static AST_NODE_T* build_for_node(control_builder_context_T* context); +static AST_NODE_T* build_block_node(control_builder_context_T* context); +static AST_NODE_T* build_yield_node(control_builder_context_T* context); + +static const control_builder_entry_T CONTROL_BUILDERS[] = { + { CONTROL_TYPE_IF, build_if_node }, { CONTROL_TYPE_ELSIF, build_if_node }, + { CONTROL_TYPE_ELSE, build_else_node }, { CONTROL_TYPE_WHEN, build_when_node }, + { CONTROL_TYPE_IN, build_in_node }, { CONTROL_TYPE_RESCUE, build_rescue_node }, + { CONTROL_TYPE_ENSURE, build_ensure_node }, { CONTROL_TYPE_UNLESS, build_unless_node }, + { CONTROL_TYPE_WHILE, build_while_node }, { CONTROL_TYPE_UNTIL, build_until_node }, + { CONTROL_TYPE_FOR, build_for_node }, { CONTROL_TYPE_BLOCK, build_block_node }, + { CONTROL_TYPE_YIELD, build_yield_node }, +}; + +static control_builder_fn lookup_control_builder(control_type_t type) { + for (size_t i = 0; i < sizeof(CONTROL_BUILDERS) / sizeof(CONTROL_BUILDERS[0]); i++) { + if (CONTROL_BUILDERS[i].type == type) { return CONTROL_BUILDERS[i].builder; } + } + + return NULL; +} + +AST_NODE_T* create_control_node( + AST_ERB_CONTENT_NODE_T* erb_node, + hb_array_T* children, + AST_NODE_T* subsequent, + AST_ERB_END_NODE_T* end_node, + control_type_t control_type +) { + control_builder_context_T context = { .erb = erb_node, + .children = children, + .subsequent = subsequent, + .end_node = end_node, + .tag_opening = erb_node->tag_opening, + .content = erb_node->content, + .tag_closing = erb_node->tag_closing, + .then_keyword = compute_then_keyword(erb_node, control_type), + .start_position = erb_node->tag_opening->location.start, + .end_position = erb_content_end_position(erb_node), + .errors = erb_node->base.errors, + .control_type = control_type }; + + erb_node->base.errors = NULL; + + if (context.end_node) { + context.end_position = context.end_node->base.location.end; + } else if (hb_array_size(context.children) > 0) { + AST_NODE_T* last_child = hb_array_last(context.children); + context.end_position = last_child->location.end; + } else if (context.subsequent) { + context.end_position = context.subsequent->location.end; + } + + control_builder_fn builder = lookup_control_builder(control_type); + + if (!builder) { return NULL; } + + return builder(&context); +} + +static AST_NODE_T* build_if_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_if_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->then_keyword, + context->children, + context->subsequent, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_else_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_else_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_when_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_when_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->then_keyword, + context->children, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_in_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_in_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->then_keyword, + context->children, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_rescue_node(control_builder_context_T* context) { + AST_ERB_RESCUE_NODE_T* rescue_node = NULL; + + if (context->subsequent && context->subsequent->type == AST_ERB_RESCUE_NODE) { + rescue_node = (AST_ERB_RESCUE_NODE_T*) context->subsequent; + } + + return (AST_NODE_T*) ast_erb_rescue_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + rescue_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_ensure_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_ensure_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_unless_node(control_builder_context_T* context) { + AST_ERB_ELSE_NODE_T* else_clause = NULL; + + if (context->subsequent && context->subsequent->type == AST_ERB_ELSE_NODE) { + else_clause = (AST_ERB_ELSE_NODE_T*) context->subsequent; + } + + return (AST_NODE_T*) ast_erb_unless_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->then_keyword, + context->children, + else_clause, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_while_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_while_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_until_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_until_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_for_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_for_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_block_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_block_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->children, + context->end_node, + context->start_position, + context->end_position, + context->errors + ); +} + +static AST_NODE_T* build_yield_node(control_builder_context_T* context) { + return (AST_NODE_T*) ast_erb_yield_node_init( + context->tag_opening, + context->content, + context->tag_closing, + context->start_position, + context->end_position, + context->errors + ); +} diff --git a/src/analyze_conditional_elements.c b/src/analyze/conditional_elements.c similarity index 98% rename from src/analyze_conditional_elements.c rename to src/analyze/conditional_elements.c index b15ab5593..1643cc7bc 100644 --- a/src/analyze_conditional_elements.c +++ b/src/analyze/conditional_elements.c @@ -1,14 +1,14 @@ -#include "include/analyze_conditional_elements.h" -#include "include/ast_nodes.h" -#include "include/element_source.h" -#include "include/errors.h" -#include "include/token_struct.h" -#include "include/util.h" -#include "include/util/hb_array.h" -#include "include/util/hb_buffer.h" -#include "include/util/hb_string.h" -#include "include/util/string.h" -#include "include/visitor.h" +#include "../include/analyze/conditional_elements.h" +#include "../include/ast_nodes.h" +#include "../include/element_source.h" +#include "../include/errors.h" +#include "../include/token_struct.h" +#include "../include/util.h" +#include "../include/util/hb_array.h" +#include "../include/util/hb_buffer.h" +#include "../include/util/hb_string.h" +#include "../include/util/string.h" +#include "../include/visitor.h" #include #include diff --git a/src/analyze_conditional_open_tags.c b/src/analyze/conditional_open_tags.c similarity index 98% rename from src/analyze_conditional_open_tags.c rename to src/analyze/conditional_open_tags.c index c068fbfc0..dfaec9dd3 100644 --- a/src/analyze_conditional_open_tags.c +++ b/src/analyze/conditional_open_tags.c @@ -1,12 +1,12 @@ -#include "include/analyze_conditional_open_tags.h" -#include "include/ast_nodes.h" -#include "include/element_source.h" -#include "include/errors.h" -#include "include/token_struct.h" -#include "include/util.h" -#include "include/util/hb_array.h" -#include "include/util/hb_string.h" -#include "include/visitor.h" +#include "../include/analyze/conditional_open_tags.h" +#include "../include/ast_nodes.h" +#include "../include/element_source.h" +#include "../include/errors.h" +#include "../include/token_struct.h" +#include "../include/util.h" +#include "../include/util/hb_array.h" +#include "../include/util/hb_string.h" +#include "../include/visitor.h" #include #include diff --git a/src/analyze/control_type.c b/src/analyze/control_type.c new file mode 100644 index 000000000..47ae08662 --- /dev/null +++ b/src/analyze/control_type.c @@ -0,0 +1,250 @@ +#include "../include/analyze/control_type.h" +#include "../include/analyze/analyze.h" +#include "../include/analyze/analyzed_ruby.h" +#include "../include/analyze/helpers.h" +#include "../include/ast_node.h" + +#include +#include +#include + +typedef struct { + control_type_t type; + uint32_t offset; + bool found; +} earliest_control_keyword_T; + +typedef struct { + earliest_control_keyword_T* result; + const uint8_t* source_start; +} location_walker_context_T; + +static bool control_type_is_block(control_type_t type) { + return type == CONTROL_TYPE_BLOCK; +} + +static bool control_type_is_yield(control_type_t type) { + return type == CONTROL_TYPE_YIELD; +} + +static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* data) { + if (!node) { return true; } + + location_walker_context_T* context = (location_walker_context_T*) data; + earliest_control_keyword_T* result = context->result; + + control_type_t current_type = CONTROL_TYPE_UNKNOWN; + uint32_t keyword_offset = UINT32_MAX; + + switch (node->type) { + case PM_IF_NODE: { + pm_if_node_t* if_node = (pm_if_node_t*) node; + current_type = CONTROL_TYPE_IF; + keyword_offset = (uint32_t) (if_node->if_keyword_loc.start - context->source_start); + break; + } + + case PM_UNLESS_NODE: { + pm_unless_node_t* unless_node = (pm_unless_node_t*) node; + current_type = CONTROL_TYPE_UNLESS; + keyword_offset = (uint32_t) (unless_node->keyword_loc.start - context->source_start); + break; + } + + case PM_CASE_NODE: { + pm_case_node_t* case_node = (pm_case_node_t*) node; + current_type = CONTROL_TYPE_CASE; + keyword_offset = (uint32_t) (case_node->case_keyword_loc.start - context->source_start); + break; + } + + case PM_CASE_MATCH_NODE: { + pm_case_match_node_t* case_match_node = (pm_case_match_node_t*) node; + current_type = CONTROL_TYPE_CASE_MATCH; + keyword_offset = (uint32_t) (case_match_node->case_keyword_loc.start - context->source_start); + break; + } + + case PM_WHILE_NODE: { + pm_while_node_t* while_node = (pm_while_node_t*) node; + current_type = CONTROL_TYPE_WHILE; + keyword_offset = (uint32_t) (while_node->keyword_loc.start - context->source_start); + break; + } + + case PM_UNTIL_NODE: { + pm_until_node_t* until_node = (pm_until_node_t*) node; + current_type = CONTROL_TYPE_UNTIL; + keyword_offset = (uint32_t) (until_node->keyword_loc.start - context->source_start); + break; + } + + case PM_FOR_NODE: { + pm_for_node_t* for_node = (pm_for_node_t*) node; + current_type = CONTROL_TYPE_FOR; + keyword_offset = (uint32_t) (for_node->for_keyword_loc.start - context->source_start); + break; + } + + case PM_BEGIN_NODE: { + pm_begin_node_t* begin_node = (pm_begin_node_t*) node; + current_type = CONTROL_TYPE_BEGIN; + + if (begin_node->begin_keyword_loc.start != NULL) { + keyword_offset = (uint32_t) (begin_node->begin_keyword_loc.start - context->source_start); + } else { + keyword_offset = (uint32_t) (node->location.start - context->source_start); + } + break; + } + + case PM_YIELD_NODE: { + current_type = CONTROL_TYPE_YIELD; + keyword_offset = (uint32_t) (node->location.start - context->source_start); + break; + } + + case PM_CALL_NODE: { + pm_call_node_t* call = (pm_call_node_t*) node; + + if (call->block != NULL && call->block->type == PM_BLOCK_NODE) { + pm_block_node_t* block_node = (pm_block_node_t*) call->block; + + bool has_do_opening = is_do_block(block_node->opening_loc); + bool has_brace_opening = is_brace_block(block_node->opening_loc); + bool has_valid_brace_closing = is_closing_brace(block_node->closing_loc); + + if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) { + current_type = CONTROL_TYPE_BLOCK; + keyword_offset = (uint32_t) (node->location.start - context->source_start); + } + } + break; + } + + case PM_LAMBDA_NODE: { + pm_lambda_node_t* lambda = (pm_lambda_node_t*) node; + + bool has_do_opening = is_do_block(lambda->opening_loc); + bool has_brace_opening = is_brace_block(lambda->opening_loc); + bool has_valid_brace_closing = is_closing_brace(lambda->closing_loc); + + if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) { + current_type = CONTROL_TYPE_BLOCK; + keyword_offset = (uint32_t) (node->location.start - context->source_start); + } + + break; + } + + case PM_NEXT_NODE: + case PM_BREAK_NODE: + case PM_RETURN_NODE: { + current_type = CONTROL_TYPE_UNKNOWN; + keyword_offset = (uint32_t) (node->location.start - context->source_start); + break; + } + + default: break; + } + + if (keyword_offset != UINT32_MAX) { + bool should_update = !result->found; + + if (result->found) { + if (control_type_is_block(current_type) && control_type_is_yield(result->type)) { + should_update = true; + } else if (!(control_type_is_yield(current_type) && control_type_is_block(result->type))) { + should_update = keyword_offset < result->offset; + } + } + + if (should_update) { + result->type = current_type; + result->offset = keyword_offset; + result->found = true; + } + } + + return true; +} + +static control_type_t find_earliest_control_keyword(pm_node_t* root, const uint8_t* source_start) { + if (!root) { return CONTROL_TYPE_UNKNOWN; } + + earliest_control_keyword_T result = { .type = CONTROL_TYPE_UNKNOWN, .offset = UINT32_MAX, .found = false }; + + location_walker_context_T context = { .result = &result, .source_start = source_start }; + + pm_visit_node(root, find_earliest_control_keyword_walker, &context); + + return result.found ? result.type : CONTROL_TYPE_UNKNOWN; +} + +control_type_t detect_control_type(AST_ERB_CONTENT_NODE_T* erb_node) { + if (!erb_node || erb_node->base.type != AST_ERB_CONTENT_NODE) { return CONTROL_TYPE_UNKNOWN; } + if (erb_node->tag_closing == NULL) { return CONTROL_TYPE_UNKNOWN; } + + analyzed_ruby_T* ruby = erb_node->analyzed_ruby; + + if (!ruby) { return CONTROL_TYPE_UNKNOWN; } + if (ruby->valid) { return CONTROL_TYPE_UNKNOWN; } + + pm_node_t* root = ruby->root; + + if (has_elsif_node(ruby)) { return CONTROL_TYPE_ELSIF; } + if (has_else_node(ruby)) { return CONTROL_TYPE_ELSE; } + if (has_end(ruby)) { return CONTROL_TYPE_END; } + if (has_when_node(ruby) && !has_case_node(ruby)) { return CONTROL_TYPE_WHEN; } + if (has_in_node(ruby) && !has_case_match_node(ruby)) { return CONTROL_TYPE_IN; } + if (has_rescue_node(ruby)) { return CONTROL_TYPE_RESCUE; } + if (has_ensure_node(ruby)) { return CONTROL_TYPE_ENSURE; } + if (has_block_closing(ruby)) { return CONTROL_TYPE_BLOCK_CLOSE; } + + if (ruby->unclosed_control_flow_count == 0 && !has_yield_node(ruby)) { return CONTROL_TYPE_UNKNOWN; } + + return find_earliest_control_keyword(root, ruby->parser.start); +} + +bool is_subsequent_type(control_type_t parent_type, control_type_t child_type) { + switch (parent_type) { + case CONTROL_TYPE_IF: + case CONTROL_TYPE_ELSIF: return child_type == CONTROL_TYPE_ELSIF || child_type == CONTROL_TYPE_ELSE; + case CONTROL_TYPE_CASE: + case CONTROL_TYPE_CASE_MATCH: return child_type == CONTROL_TYPE_WHEN || child_type == CONTROL_TYPE_ELSE; + case CONTROL_TYPE_BEGIN: + return child_type == CONTROL_TYPE_RESCUE || child_type == CONTROL_TYPE_ELSE || child_type == CONTROL_TYPE_ENSURE; + case CONTROL_TYPE_RESCUE: return child_type == CONTROL_TYPE_RESCUE; + case CONTROL_TYPE_UNLESS: return child_type == CONTROL_TYPE_ELSE; + + default: return false; + } +} + +bool is_terminator_type(control_type_t parent_type, control_type_t child_type) { + if (child_type == CONTROL_TYPE_END) { return true; } + + switch (parent_type) { + case CONTROL_TYPE_WHEN: return child_type == CONTROL_TYPE_WHEN || child_type == CONTROL_TYPE_ELSE; + case CONTROL_TYPE_IN: return child_type == CONTROL_TYPE_IN || child_type == CONTROL_TYPE_ELSE; + case CONTROL_TYPE_BLOCK: return child_type == CONTROL_TYPE_BLOCK_CLOSE; + + default: return is_subsequent_type(parent_type, child_type); + } +} + +bool is_compound_control_type(control_type_t type) { + switch (type) { + case CONTROL_TYPE_IF: + case CONTROL_TYPE_CASE: + case CONTROL_TYPE_CASE_MATCH: + case CONTROL_TYPE_BEGIN: + case CONTROL_TYPE_UNLESS: + case CONTROL_TYPE_WHILE: + case CONTROL_TYPE_UNTIL: + case CONTROL_TYPE_FOR: + case CONTROL_TYPE_BLOCK: return true; + + default: return false; + } +} diff --git a/src/analyze_helpers.c b/src/analyze/helpers.c similarity index 99% rename from src/analyze_helpers.c rename to src/analyze/helpers.c index b21af7c71..e75d35f29 100644 --- a/src/analyze_helpers.c +++ b/src/analyze/helpers.c @@ -2,8 +2,8 @@ #include #include -#include "include/analyzed_ruby.h" -#include "include/util/string.h" +#include "../include/analyze/analyzed_ruby.h" +#include "../include/util/string.h" bool has_if_node(analyzed_ruby_T* analyzed) { return analyzed->if_node_count > 0; diff --git a/src/analyze/invalid_structures.c b/src/analyze/invalid_structures.c new file mode 100644 index 000000000..56ffe4353 --- /dev/null +++ b/src/analyze/invalid_structures.c @@ -0,0 +1,185 @@ +#include "../include/analyze/invalid_structures.h" +#include "../include/analyze/analyze.h" +#include "../include/analyze/analyzed_ruby.h" +#include "../include/analyze/helpers.h" +#include "../include/ast_node.h" +#include "../include/ast_nodes.h" +#include "../include/errors.h" +#include "../include/token_struct.h" +#include "../include/util/hb_array.h" +#include "../include/visitor.h" + +#include +#include + +bool detect_invalid_erb_structures(const AST_NODE_T* node, void* data) { + invalid_erb_context_T* context = (invalid_erb_context_T*) data; + + if (node->type == AST_HTML_ATTRIBUTE_NAME_NODE) { return false; } + + bool is_begin_node = (node->type == AST_ERB_BEGIN_NODE); + bool is_loop_node = + (node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE || node->type == AST_ERB_FOR_NODE + || node->type == AST_ERB_BLOCK_NODE); + + if (is_loop_node) { context->loop_depth++; } + if (is_begin_node) { context->rescue_depth++; } + + if (node->type == AST_ERB_CONTENT_NODE) { + const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) node; + + if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { + analyzed_ruby_T* analyzed = content_node->analyzed_ruby; + + // =begin + if (has_error_message(analyzed, "embedded document meets end of file")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + + // =end + if (has_error_message(analyzed, "unexpected '=', ignoring it") + && has_error_message(analyzed, "unexpected 'end', ignoring it")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + + const char* keyword = NULL; + + if (context->loop_depth == 0) { + if (has_error_message(analyzed, "Invalid break")) { + keyword = "`<% break %>`"; + } else if (has_error_message(analyzed, "Invalid next")) { + keyword = "`<% next %>`"; + } else if (has_error_message(analyzed, "Invalid redo")) { + keyword = "`<% redo %>`"; + } + } else { + if (has_error_message(analyzed, "Invalid redo") || has_error_message(analyzed, "Invalid break") + || has_error_message(analyzed, "Invalid next")) { + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + } + + if (context->rescue_depth == 0) { + if (has_error_message(analyzed, "Invalid retry without rescue")) { keyword = "`<% retry %>`"; } + } else { + if (has_error_message(analyzed, "Invalid retry without rescue")) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return true; + } + } + + if (keyword == NULL) { keyword = erb_keyword_from_analyzed_ruby(analyzed); } + + if (keyword != NULL && !token_value_empty(content_node->tag_closing)) { + append_erb_control_flow_scope_error(keyword, node->location.start, node->location.end, node->errors); + } + } + } + + if (node->type == AST_ERB_IF_NODE) { + const AST_ERB_IF_NODE_T* if_node = (const AST_ERB_IF_NODE_T*) node; + + if (if_node->end_node == NULL) { check_erb_node_for_missing_end(node); } + + if (if_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(if_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(if_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + AST_NODE_T* subsequent = if_node->subsequent; + + while (subsequent != NULL) { + if (subsequent->type == AST_ERB_CONTENT_NODE) { + const AST_ERB_CONTENT_NODE_T* content_node = (const AST_ERB_CONTENT_NODE_T*) subsequent; + + if (content_node->parsed && !content_node->valid && content_node->analyzed_ruby != NULL) { + analyzed_ruby_T* analyzed = content_node->analyzed_ruby; + const char* keyword = erb_keyword_from_analyzed_ruby(analyzed); + + if (!token_value_empty(content_node->tag_closing)) { + append_erb_control_flow_scope_error( + keyword, + subsequent->location.start, + subsequent->location.end, + subsequent->errors + ); + } + } + } + + if (subsequent->type == AST_ERB_IF_NODE) { + const AST_ERB_IF_NODE_T* elsif_node = (const AST_ERB_IF_NODE_T*) subsequent; + + if (elsif_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(elsif_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(elsif_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + subsequent = elsif_node->subsequent; + } else if (subsequent->type == AST_ERB_ELSE_NODE) { + const AST_ERB_ELSE_NODE_T* else_node = (const AST_ERB_ELSE_NODE_T*) subsequent; + + if (else_node->statements != NULL) { + for (size_t i = 0; i < hb_array_size(else_node->statements); i++) { + AST_NODE_T* statement = (AST_NODE_T*) hb_array_get(else_node->statements, i); + + if (statement != NULL) { herb_visit_node(statement, detect_invalid_erb_structures, context); } + } + } + + break; + } else { + break; + } + } + } + + if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE + || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE + || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { + herb_visit_child_nodes(node, detect_invalid_erb_structures, context); + } + + if (node->type == AST_ERB_UNLESS_NODE || node->type == AST_ERB_WHILE_NODE || node->type == AST_ERB_UNTIL_NODE + || node->type == AST_ERB_FOR_NODE || node->type == AST_ERB_CASE_NODE || node->type == AST_ERB_CASE_MATCH_NODE + || node->type == AST_ERB_BEGIN_NODE || node->type == AST_ERB_BLOCK_NODE || node->type == AST_ERB_ELSE_NODE) { + check_erb_node_for_missing_end(node); + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return false; + } + + if (node->type == AST_ERB_IF_NODE) { + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return false; + } + + bool result = true; + + if (is_loop_node) { context->loop_depth--; } + if (is_begin_node) { context->rescue_depth--; } + + return result; +} diff --git a/src/analyze/parse_errors.c b/src/analyze/parse_errors.c new file mode 100644 index 000000000..001da9a20 --- /dev/null +++ b/src/analyze/parse_errors.c @@ -0,0 +1,75 @@ +#include "../include/analyze/analyze.h" +#include "../include/ast_node.h" +#include "../include/ast_nodes.h" +#include "../include/errors.h" +#include "../include/extract.h" +#include "../include/prism_helpers.h" + +#include +#include + +static void parse_erb_content_errors(AST_NODE_T* erb_node, const char* source) { + if (!erb_node || erb_node->type != AST_ERB_CONTENT_NODE) { return; } + AST_ERB_CONTENT_NODE_T* content_node = (AST_ERB_CONTENT_NODE_T*) erb_node; + + if (!content_node->content || !content_node->content->value) { return; } + + const char* content = content_node->content->value; + if (strlen(content) == 0) { return; } + + pm_parser_t parser; + pm_options_t options = { 0, .partial_script = true }; + pm_parser_init(&parser, (const uint8_t*) content, strlen(content), &options); + + pm_node_t* root = pm_parse(&parser); + + const pm_diagnostic_t* error = (const pm_diagnostic_t*) parser.error_list.head; + + if (error != NULL) { + RUBY_PARSE_ERROR_T* parse_error = + ruby_parse_error_from_prism_error_with_positions(error, erb_node->location.start, erb_node->location.end); + + hb_array_append(erb_node->errors, parse_error); + } + + pm_node_destroy(&parser, root); + pm_parser_free(&parser); + pm_options_free(&options); +} + +void herb_analyze_parse_errors(AST_DOCUMENT_NODE_T* document, const char* source) { + char* extracted_ruby = herb_extract_ruby_with_semicolons(source); + + if (!extracted_ruby) { return; } + + pm_parser_t parser; + pm_options_t options = { 0, .partial_script = true }; + pm_parser_init(&parser, (const uint8_t*) extracted_ruby, strlen(extracted_ruby), &options); + + pm_node_t* root = pm_parse(&parser); + + for (const pm_diagnostic_t* error = (const pm_diagnostic_t*) parser.error_list.head; error != NULL; + error = (const pm_diagnostic_t*) error->node.next) { + size_t error_offset = (size_t) (error->location.start - parser.start); + + if (strstr(error->message, "unexpected ';'") != NULL) { + if (error_offset < strlen(extracted_ruby) && extracted_ruby[error_offset] == ';') { + if (error_offset >= strlen(source) || source[error_offset] != ';') { + AST_NODE_T* erb_node = find_erb_content_at_offset(document, source, error_offset); + + if (erb_node) { parse_erb_content_errors(erb_node, source); } + + continue; + } + } + } + + RUBY_PARSE_ERROR_T* parse_error = ruby_parse_error_from_prism_error(error, (AST_NODE_T*) document, source, &parser); + hb_array_append(document->base.errors, parse_error); + } + + pm_node_destroy(&parser, root); + pm_parser_free(&parser); + pm_options_free(&options); + free(extracted_ruby); +} diff --git a/src/herb.c b/src/herb.c index cd07317fc..ad2b7e78e 100644 --- a/src/herb.c +++ b/src/herb.c @@ -1,5 +1,5 @@ #include "include/herb.h" -#include "include/analyze.h" +#include "include/analyze/analyze.h" #include "include/io.h" #include "include/lexer.h" #include "include/parser.h" diff --git a/src/include/analyze.h b/src/include/analyze/analyze.h similarity index 95% rename from src/include/analyze.h rename to src/include/analyze/analyze.h index ac8d7e73b..12e961718 100644 --- a/src/include/analyze.h +++ b/src/include/analyze/analyze.h @@ -2,8 +2,8 @@ #define HERB_ANALYZE_H #include "analyzed_ruby.h" -#include "ast_nodes.h" -#include "util/hb_array.h" +#include "../ast_nodes.h" +#include "../util/hb_array.h" typedef struct ANALYZE_RUBY_CONTEXT_STRUCT { AST_DOCUMENT_NODE_T* document; diff --git a/src/include/analyzed_ruby.h b/src/include/analyze/analyzed_ruby.h similarity index 93% rename from src/include/analyzed_ruby.h rename to src/include/analyze/analyzed_ruby.h index 9956871da..12387c755 100644 --- a/src/include/analyzed_ruby.h +++ b/src/include/analyze/analyzed_ruby.h @@ -1,8 +1,8 @@ #ifndef HERB_ANALYZED_RUBY_H #define HERB_ANALYZED_RUBY_H -#include "util/hb_array.h" -#include "util/hb_string.h" +#include "../util/hb_array.h" +#include "../util/hb_string.h" #include diff --git a/src/include/analyze/builders.h b/src/include/analyze/builders.h new file mode 100644 index 000000000..9880769c5 --- /dev/null +++ b/src/include/analyze/builders.h @@ -0,0 +1,21 @@ +#ifndef HERB_ANALYZE_BUILDERS_H +#define HERB_ANALYZE_BUILDERS_H + +#include "analyze.h" +#include "../ast_nodes.h" +#include "../location.h" +#include "../position.h" + +position_T erb_content_end_position(const AST_ERB_CONTENT_NODE_T* erb_node); + +location_T* compute_then_keyword(AST_ERB_CONTENT_NODE_T* erb_node, control_type_t control_type); + +AST_NODE_T* create_control_node( + AST_ERB_CONTENT_NODE_T* erb_node, + hb_array_T* children, + AST_NODE_T* subsequent, + AST_ERB_END_NODE_T* end_node, + control_type_t control_type +); + +#endif diff --git a/src/include/analyze_conditional_elements.h b/src/include/analyze/conditional_elements.h similarity index 86% rename from src/include/analyze_conditional_elements.h rename to src/include/analyze/conditional_elements.h index c642a6a9e..e74c401ae 100644 --- a/src/include/analyze_conditional_elements.h +++ b/src/include/analyze/conditional_elements.h @@ -1,7 +1,7 @@ #ifndef HERB_ANALYZE_CONDITIONAL_ELEMENTS_H #define HERB_ANALYZE_CONDITIONAL_ELEMENTS_H -#include "ast_nodes.h" +#include "../ast_nodes.h" void herb_transform_conditional_elements(AST_DOCUMENT_NODE_T* document); diff --git a/src/include/analyze_conditional_open_tags.h b/src/include/analyze/conditional_open_tags.h similarity index 87% rename from src/include/analyze_conditional_open_tags.h rename to src/include/analyze/conditional_open_tags.h index 194ac5dab..5038a5305 100644 --- a/src/include/analyze_conditional_open_tags.h +++ b/src/include/analyze/conditional_open_tags.h @@ -1,7 +1,7 @@ #ifndef HERB_ANALYZE_CONDITIONAL_OPEN_TAGS_H #define HERB_ANALYZE_CONDITIONAL_OPEN_TAGS_H -#include "ast_nodes.h" +#include "../ast_nodes.h" void herb_transform_conditional_open_tags(AST_DOCUMENT_NODE_T* document); diff --git a/src/include/analyze/control_type.h b/src/include/analyze/control_type.h new file mode 100644 index 000000000..7c45f48e2 --- /dev/null +++ b/src/include/analyze/control_type.h @@ -0,0 +1,14 @@ +#ifndef HERB_ANALYZE_CONTROL_TYPE_H +#define HERB_ANALYZE_CONTROL_TYPE_H + +#include "analyze.h" +#include "../ast_nodes.h" + +#include + +control_type_t detect_control_type(AST_ERB_CONTENT_NODE_T* erb_node); +bool is_subsequent_type(control_type_t parent_type, control_type_t child_type); +bool is_terminator_type(control_type_t parent_type, control_type_t child_type); +bool is_compound_control_type(control_type_t type); + +#endif diff --git a/src/include/analyze_helpers.h b/src/include/analyze/helpers.h similarity index 99% rename from src/include/analyze_helpers.h rename to src/include/analyze/helpers.h index 8c4f94c61..a815d93ec 100644 --- a/src/include/analyze_helpers.h +++ b/src/include/analyze/helpers.h @@ -5,7 +5,7 @@ #include #include "analyzed_ruby.h" -#include "ast_node.h" +#include "../ast_node.h" bool has_if_node(analyzed_ruby_T* analyzed); bool has_elsif_node(analyzed_ruby_T* analyzed); diff --git a/src/include/analyze/invalid_structures.h b/src/include/analyze/invalid_structures.h new file mode 100644 index 000000000..28eb2c9de --- /dev/null +++ b/src/include/analyze/invalid_structures.h @@ -0,0 +1,11 @@ +#ifndef HERB_ANALYZE_INVALID_STRUCTURES_H +#define HERB_ANALYZE_INVALID_STRUCTURES_H + +#include "analyze.h" +#include "../ast_node.h" + +#include + +bool detect_invalid_erb_structures(const AST_NODE_T* node, void* data); + +#endif diff --git a/src/include/pretty_print.h b/src/include/pretty_print.h index 7874d84f4..3876320c5 100644 --- a/src/include/pretty_print.h +++ b/src/include/pretty_print.h @@ -1,7 +1,7 @@ #ifndef HERB_PRETTY_PRINT_H #define HERB_PRETTY_PRINT_H -#include "analyzed_ruby.h" +#include "analyze/analyzed_ruby.h" #include "ast_nodes.h" #include "location.h" #include "util/hb_buffer.h" diff --git a/src/include/prism_helpers.h b/src/include/prism_helpers.h index b85388028..1019e06ab 100644 --- a/src/include/prism_helpers.h +++ b/src/include/prism_helpers.h @@ -1,7 +1,7 @@ #ifndef HERB_PRISM_HELPERS_H #define HERB_PRISM_HELPERS_H -#include "analyzed_ruby.h" +#include "analyze/analyzed_ruby.h" #include "ast_nodes.h" #include "errors.h" #include "location.h" diff --git a/templates/src/analyze_missing_end.c.erb b/templates/src/analyze/missing_end.c.erb similarity index 93% rename from templates/src/analyze_missing_end.c.erb rename to templates/src/analyze/missing_end.c.erb index 291b89e4d..b4bd90df8 100644 --- a/templates/src/analyze_missing_end.c.erb +++ b/templates/src/analyze/missing_end.c.erb @@ -1,5 +1,5 @@ -#include "include/analyze_helpers.h" -#include "include/errors.h" +#include "../include/analyze/helpers.h" +#include "../include/errors.h" <%- nodes_with_end_node = nodes.select do |node| diff --git a/templates/src/analyze_transform.c.erb b/templates/src/analyze/transform.c.erb similarity index 91% rename from templates/src/analyze_transform.c.erb rename to templates/src/analyze/transform.c.erb index f77eb2b96..20a0eb6ad 100644 --- a/templates/src/analyze_transform.c.erb +++ b/templates/src/analyze/transform.c.erb @@ -1,5 +1,5 @@ -#include "include/analyze.h" -#include "include/visitor.h" +#include "../include/analyze/analyze.h" +#include "../include/visitor.h" bool transform_erb_nodes(const AST_NODE_T* node, void* data) { analyze_ruby_context_T* context = (analyze_ruby_context_T*) data; diff --git a/templates/src/ast_nodes.c.erb b/templates/src/ast_nodes.c.erb index 4622fb2a7..01dc75136 100644 --- a/templates/src/ast_nodes.c.erb +++ b/templates/src/ast_nodes.c.erb @@ -3,7 +3,7 @@ #include -#include "include/analyzed_ruby.h" +#include "include/analyze/analyzed_ruby.h" #include "include/ast_node.h" #include "include/ast_nodes.h" #include "include/errors.h" diff --git a/templates/src/ast_pretty_print.c.erb b/templates/src/ast_pretty_print.c.erb index 99c5790ef..192401b75 100644 --- a/templates/src/ast_pretty_print.c.erb +++ b/templates/src/ast_pretty_print.c.erb @@ -1,4 +1,4 @@ -#include "include/analyze_helpers.h" +#include "include/analyze/helpers.h" #include "include/ast_node.h" #include "include/ast_nodes.h" #include "include/errors.h" diff --git a/templates/src/include/ast_nodes.h.erb b/templates/src/include/ast_nodes.h.erb index 3958eb1b5..be00c08be 100644 --- a/templates/src/include/ast_nodes.h.erb +++ b/templates/src/include/ast_nodes.h.erb @@ -4,7 +4,7 @@ #include #include -#include "analyzed_ruby.h" +#include "analyze/analyzed_ruby.h" #include "element_source.h" #include "location.h" #include "position.h"