herb 0.8.7 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +7 -0
  3. data/config.yml +12 -0
  4. data/ext/herb/extconf.rb +0 -4
  5. data/ext/herb/nodes.c +17 -9
  6. data/lib/herb/ast/nodes.rb +32 -8
  7. data/lib/herb/engine/debug_visitor.rb +1 -1
  8. data/lib/herb/version.rb +1 -1
  9. data/lib/herb.rb +30 -3
  10. data/sig/herb/ast/nodes.rbs +16 -8
  11. data/sig/serialized_ast_nodes.rbs +4 -0
  12. data/src/analyze.c +110 -15
  13. data/src/analyze_helpers.c +80 -12
  14. data/src/analyzed_ruby.c +1 -0
  15. data/src/ast_nodes.c +12 -4
  16. data/src/ast_pretty_print.c +52 -0
  17. data/src/include/analyze_helpers.h +7 -0
  18. data/src/include/analyzed_ruby.h +1 -0
  19. data/src/include/ast_nodes.h +8 -4
  20. data/src/include/location.h +4 -0
  21. data/src/include/prism_helpers.h +6 -0
  22. data/src/include/version.h +1 -1
  23. data/src/location.c +16 -0
  24. data/src/prism_helpers.c +188 -0
  25. data/templates/ext/herb/nodes.c.erb +2 -0
  26. data/templates/java/nodes.c.erb +3 -1
  27. data/templates/java/org/herb/ast/Nodes.java.erb +11 -0
  28. data/templates/javascript/packages/core/src/nodes.ts.erb +14 -0
  29. data/templates/javascript/packages/node/extension/nodes.cpp.erb +9 -0
  30. data/templates/lib/herb/ast/nodes.rb.erb +4 -0
  31. data/templates/rust/src/ast/nodes.rs.erb +10 -0
  32. data/templates/rust/src/nodes.rs.erb +4 -0
  33. data/templates/src/ast_nodes.c.erb +4 -0
  34. data/templates/src/ast_pretty_print.c.erb +14 -0
  35. data/templates/template.rb +11 -0
  36. data/templates/wasm/nodes.cpp.erb +6 -0
  37. data/vendor/prism/include/prism/version.h +2 -2
  38. data/vendor/prism/src/prism.c +48 -27
  39. data/vendor/prism/templates/java/org/prism/Loader.java.erb +1 -1
  40. data/vendor/prism/templates/javascript/src/deserialize.js.erb +1 -1
  41. data/vendor/prism/templates/lib/prism/compiler.rb.erb +2 -2
  42. data/vendor/prism/templates/lib/prism/node.rb.erb +24 -1
  43. data/vendor/prism/templates/lib/prism/serialize.rb.erb +1 -1
  44. data/vendor/prism/templates/lib/prism/visitor.rb.erb +2 -2
  45. data/vendor/prism/templates/sig/prism/node.rbs.erb +1 -0
  46. metadata +1 -1
data/src/analyze.c CHANGED
@@ -43,6 +43,7 @@ static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) {
43
43
  search_rescue_nodes(analyzed);
44
44
  search_ensure_nodes(analyzed);
45
45
  search_yield_nodes(analyzed->root, analyzed);
46
+ search_then_keywords(analyzed->root, analyzed);
46
47
  search_block_closing_nodes(analyzed);
47
48
 
48
49
  if (!analyzed->valid) { pm_visit_node(analyzed->root, search_unclosed_control_flows, analyzed); }
@@ -112,6 +113,14 @@ typedef struct {
112
113
  const uint8_t* source_start;
113
114
  } location_walker_context_t;
114
115
 
116
+ static bool control_type_is_block(control_type_t type) {
117
+ return type == CONTROL_TYPE_BLOCK;
118
+ }
119
+
120
+ static bool control_type_is_yield(control_type_t type) {
121
+ return type == CONTROL_TYPE_YIELD;
122
+ }
123
+
115
124
  static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* data) {
116
125
  if (!node) { return true; }
117
126
 
@@ -194,14 +203,12 @@ static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* da
194
203
 
195
204
  if (call->block != NULL && call->block->type == PM_BLOCK_NODE) {
196
205
  pm_block_node_t* block_node = (pm_block_node_t*) call->block;
197
- size_t opening_length = block_node->opening_loc.end - block_node->opening_loc.start;
198
- bool has_do_opening =
199
- opening_length == 2 && block_node->opening_loc.start[0] == 'd' && block_node->opening_loc.start[1] == 'o';
200
- bool has_brace_opening = opening_length == 1 && block_node->opening_loc.start[0] == '{';
201
- bool has_closing_location = block_node->closing_loc.start != NULL && block_node->closing_loc.end != NULL
202
- && (block_node->closing_loc.end - block_node->closing_loc.start) > 0;
203
-
204
- if (has_do_opening || (has_brace_opening && !has_closing_location)) {
206
+
207
+ bool has_do_opening = is_do_block(block_node->opening_loc);
208
+ bool has_brace_opening = is_brace_block(block_node->opening_loc);
209
+ bool has_valid_brace_closing = is_closing_brace(block_node->closing_loc);
210
+
211
+ if (has_do_opening || (has_brace_opening && !has_valid_brace_closing)) {
205
212
  current_type = CONTROL_TYPE_BLOCK;
206
213
  keyword_offset = (uint32_t) (node->location.start - context->source_start);
207
214
  }
@@ -221,7 +228,17 @@ static bool find_earliest_control_keyword_walker(const pm_node_t* node, void* da
221
228
  }
222
229
 
223
230
  if (keyword_offset != UINT32_MAX) {
224
- if (!result->found || keyword_offset < result->offset) {
231
+ bool should_update = !result->found;
232
+
233
+ if (result->found) {
234
+ if (control_type_is_block(current_type) && control_type_is_yield(result->type)) {
235
+ should_update = true;
236
+ } else if (!(control_type_is_yield(current_type) && control_type_is_block(result->type))) {
237
+ should_update = keyword_offset < result->offset;
238
+ }
239
+ }
240
+
241
+ if (should_update) {
225
242
  result->type = current_type;
226
243
  result->offset = keyword_offset;
227
244
  result->found = true;
@@ -319,6 +336,34 @@ static AST_NODE_T* create_control_node(
319
336
  token_T* content = erb_node->content;
320
337
  token_T* tag_closing = erb_node->tag_closing;
321
338
 
339
+ location_T* then_keyword = NULL;
340
+
341
+ if (control_type == CONTROL_TYPE_IF || control_type == CONTROL_TYPE_ELSIF || control_type == CONTROL_TYPE_UNLESS
342
+ || control_type == CONTROL_TYPE_WHEN || control_type == CONTROL_TYPE_IN) {
343
+ const char* source = content ? content->value : NULL;
344
+
345
+ if (control_type == CONTROL_TYPE_WHEN || control_type == CONTROL_TYPE_IN) {
346
+ if (source != NULL && strstr(source, "then") != NULL) {
347
+ then_keyword = get_then_keyword_location_wrapped(source, control_type == CONTROL_TYPE_IN);
348
+ }
349
+ } else if (control_type == CONTROL_TYPE_ELSIF) {
350
+ if (source != NULL && strstr(source, "then") != NULL) {
351
+ then_keyword = get_then_keyword_location_elsif_wrapped(source);
352
+ }
353
+ } else {
354
+ then_keyword = get_then_keyword_location(erb_node->analyzed_ruby, source);
355
+ }
356
+
357
+ if (then_keyword != NULL && content != NULL) {
358
+ position_T content_start = content->location.start;
359
+
360
+ then_keyword->start.line = content_start.line + then_keyword->start.line - 1;
361
+ then_keyword->start.column = content_start.column + then_keyword->start.column;
362
+ then_keyword->end.line = content_start.line + then_keyword->end.line - 1;
363
+ then_keyword->end.column = content_start.column + then_keyword->end.column;
364
+ }
365
+ }
366
+
322
367
  switch (control_type) {
323
368
  case CONTROL_TYPE_IF:
324
369
  case CONTROL_TYPE_ELSIF: {
@@ -326,6 +371,7 @@ static AST_NODE_T* create_control_node(
326
371
  tag_opening,
327
372
  content,
328
373
  tag_closing,
374
+ then_keyword,
329
375
  children,
330
376
  subsequent,
331
377
  end_node,
@@ -398,15 +444,29 @@ static AST_NODE_T* create_control_node(
398
444
  }
399
445
 
400
446
  case CONTROL_TYPE_WHEN: {
401
- return (
402
- AST_NODE_T*
403
- ) ast_erb_when_node_init(tag_opening, content, tag_closing, children, start_position, end_position, errors);
447
+ return (AST_NODE_T*) ast_erb_when_node_init(
448
+ tag_opening,
449
+ content,
450
+ tag_closing,
451
+ then_keyword,
452
+ children,
453
+ start_position,
454
+ end_position,
455
+ errors
456
+ );
404
457
  }
405
458
 
406
459
  case CONTROL_TYPE_IN: {
407
- return (
408
- AST_NODE_T*
409
- ) ast_erb_in_node_init(tag_opening, content, tag_closing, children, start_position, end_position, errors);
460
+ return (AST_NODE_T*) ast_erb_in_node_init(
461
+ tag_opening,
462
+ content,
463
+ tag_closing,
464
+ then_keyword,
465
+ children,
466
+ start_position,
467
+ end_position,
468
+ errors
469
+ );
410
470
  }
411
471
 
412
472
  case CONTROL_TYPE_BEGIN: {
@@ -471,6 +531,7 @@ static AST_NODE_T* create_control_node(
471
531
  tag_opening,
472
532
  content,
473
533
  tag_closing,
534
+ then_keyword,
474
535
  children,
475
536
  else_clause,
476
537
  end_node,
@@ -599,10 +660,27 @@ static size_t process_control_structure(
599
660
  hb_array_T* when_errors = erb_content->base.errors;
600
661
  erb_content->base.errors = NULL;
601
662
 
663
+ location_T* then_keyword = NULL;
664
+ const char* source = erb_content->content ? erb_content->content->value : NULL;
665
+
666
+ if (source != NULL && strstr(source, "then") != NULL) {
667
+ then_keyword = get_then_keyword_location_wrapped(source, false);
668
+
669
+ if (then_keyword != NULL && erb_content->content != NULL) {
670
+ position_T content_start = erb_content->content->location.start;
671
+
672
+ then_keyword->start.line = content_start.line + then_keyword->start.line - 1;
673
+ then_keyword->start.column = content_start.column + then_keyword->start.column;
674
+ then_keyword->end.line = content_start.line + then_keyword->end.line - 1;
675
+ then_keyword->end.column = content_start.column + then_keyword->end.column;
676
+ }
677
+ }
678
+
602
679
  AST_ERB_WHEN_NODE_T* when_node = ast_erb_when_node_init(
603
680
  erb_content->tag_opening,
604
681
  erb_content->content,
605
682
  erb_content->tag_closing,
683
+ then_keyword,
606
684
  when_statements,
607
685
  erb_content->tag_opening->location.start,
608
686
  erb_content->tag_closing->location.end,
@@ -623,10 +701,27 @@ static size_t process_control_structure(
623
701
  hb_array_T* in_errors = erb_content->base.errors;
624
702
  erb_content->base.errors = NULL;
625
703
 
704
+ location_T* in_then_keyword = NULL;
705
+ const char* in_source = erb_content->content ? erb_content->content->value : NULL;
706
+
707
+ if (in_source != NULL && strstr(in_source, "then") != NULL) {
708
+ in_then_keyword = get_then_keyword_location_wrapped(in_source, true);
709
+
710
+ if (in_then_keyword != NULL && erb_content->content != NULL) {
711
+ position_T content_start = erb_content->content->location.start;
712
+
713
+ in_then_keyword->start.line = content_start.line + in_then_keyword->start.line - 1;
714
+ in_then_keyword->start.column = content_start.column + in_then_keyword->start.column;
715
+ in_then_keyword->end.line = content_start.line + in_then_keyword->end.line - 1;
716
+ in_then_keyword->end.column = content_start.column + in_then_keyword->end.column;
717
+ }
718
+ }
719
+
626
720
  AST_ERB_IN_NODE_T* in_node = ast_erb_in_node_init(
627
721
  erb_content->tag_opening,
628
722
  erb_content->content,
629
723
  erb_content->tag_closing,
724
+ in_then_keyword,
630
725
  in_statements,
631
726
  erb_content->tag_opening->location.start,
632
727
  erb_content->tag_closing->location.end,
@@ -76,6 +76,10 @@ bool has_yield_node(analyzed_ruby_T* analyzed) {
76
76
  return analyzed->yield_node_count > 0;
77
77
  }
78
78
 
79
+ bool has_then_keyword(analyzed_ruby_T* analyzed) {
80
+ return analyzed->then_keyword_count > 0;
81
+ }
82
+
79
83
  bool has_error_message(analyzed_ruby_T* anlayzed, const char* message) {
80
84
  for (const pm_diagnostic_t* error = (const pm_diagnostic_t*) anlayzed->parser.error_list.head; error != NULL;
81
85
  error = (const pm_diagnostic_t*) error->node.next) {
@@ -102,13 +106,13 @@ bool search_if_nodes(const pm_node_t* node, void* data) {
102
106
  return false;
103
107
  }
104
108
 
105
- static bool is_do_block(pm_location_t opening_location) {
109
+ bool is_do_block(pm_location_t opening_location) {
106
110
  size_t length = opening_location.end - opening_location.start;
107
111
 
108
112
  return length == 2 && opening_location.start[0] == 'd' && opening_location.start[1] == 'o';
109
113
  }
110
114
 
111
- static bool is_brace_block(pm_location_t opening_location) {
115
+ bool is_brace_block(pm_location_t opening_location) {
112
116
  size_t length = opening_location.end - opening_location.start;
113
117
 
114
118
  return length == 1 && opening_location.start[0] == '{';
@@ -118,6 +122,32 @@ static bool has_location(pm_location_t location) {
118
122
  return location.start != NULL && location.end != NULL && (location.end - location.start) > 0;
119
123
  }
120
124
 
125
+ static bool is_end_keyword(pm_location_t location) {
126
+ if (location.start == NULL || location.end == NULL) { return false; }
127
+
128
+ size_t length = location.end - location.start;
129
+
130
+ return length == 3 && location.start[0] == 'e' && location.start[1] == 'n' && location.start[2] == 'd';
131
+ }
132
+
133
+ bool is_closing_brace(pm_location_t location) {
134
+ if (location.start == NULL || location.end == NULL) { return false; }
135
+
136
+ size_t length = location.end - location.start;
137
+
138
+ return length == 1 && location.start[0] == '}';
139
+ }
140
+
141
+ bool has_valid_block_closing(pm_location_t opening_loc, pm_location_t closing_loc) {
142
+ if (is_do_block(opening_loc)) {
143
+ return is_end_keyword(closing_loc);
144
+ } else if (is_brace_block(opening_loc)) {
145
+ return is_closing_brace(closing_loc);
146
+ }
147
+
148
+ return false;
149
+ }
150
+
121
151
  bool search_block_nodes(const pm_node_t* node, void* data) {
122
152
  analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
123
153
 
@@ -125,7 +155,7 @@ bool search_block_nodes(const pm_node_t* node, void* data) {
125
155
  pm_block_node_t* block_node = (pm_block_node_t*) node;
126
156
 
127
157
  bool has_opening = is_do_block(block_node->opening_loc) || is_brace_block(block_node->opening_loc);
128
- bool is_unclosed = !has_location(block_node->closing_loc);
158
+ bool is_unclosed = !has_valid_block_closing(block_node->opening_loc, block_node->closing_loc);
129
159
 
130
160
  if (has_opening && is_unclosed) { analyzed->block_node_count++; }
131
161
  }
@@ -299,6 +329,42 @@ bool search_yield_nodes(const pm_node_t* node, void* data) {
299
329
  return false;
300
330
  }
301
331
 
332
+ bool search_then_keywords(const pm_node_t* node, void* data) {
333
+ analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
334
+
335
+ switch (node->type) {
336
+ case PM_IF_NODE: {
337
+ const pm_if_node_t* if_node = (const pm_if_node_t*) node;
338
+ if (if_node->then_keyword_loc.start != NULL && if_node->then_keyword_loc.end != NULL) {
339
+ analyzed->then_keyword_count++;
340
+ }
341
+ break;
342
+ }
343
+
344
+ case PM_UNLESS_NODE: {
345
+ const pm_unless_node_t* unless_node = (const pm_unless_node_t*) node;
346
+ if (unless_node->then_keyword_loc.start != NULL && unless_node->then_keyword_loc.end != NULL) {
347
+ analyzed->then_keyword_count++;
348
+ }
349
+ break;
350
+ }
351
+
352
+ case PM_WHEN_NODE: {
353
+ const pm_when_node_t* when_node = (const pm_when_node_t*) node;
354
+ if (when_node->then_keyword_loc.start != NULL && when_node->then_keyword_loc.end != NULL) {
355
+ analyzed->then_keyword_count++;
356
+ }
357
+ break;
358
+ }
359
+
360
+ default: break;
361
+ }
362
+
363
+ pm_visit_child_nodes(node, search_then_keywords, analyzed);
364
+
365
+ return false;
366
+ }
367
+
302
368
  static bool is_postfix_conditional(const pm_statements_node_t* statements, pm_location_t keyword_location) {
303
369
  if (statements == NULL) { return false; }
304
370
 
@@ -314,7 +380,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
314
380
  case PM_IF_NODE: {
315
381
  const pm_if_node_t* if_node = (const pm_if_node_t*) node;
316
382
 
317
- if (has_location(if_node->if_keyword_loc) && !has_location(if_node->end_keyword_loc)) {
383
+ if (has_location(if_node->if_keyword_loc) && !is_end_keyword(if_node->end_keyword_loc)) {
318
384
  if (!is_postfix_conditional(if_node->statements, if_node->if_keyword_loc)) {
319
385
  analyzed->unclosed_control_flow_count++;
320
386
  }
@@ -326,7 +392,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
326
392
  case PM_UNLESS_NODE: {
327
393
  const pm_unless_node_t* unless_node = (const pm_unless_node_t*) node;
328
394
 
329
- if (has_location(unless_node->keyword_loc) && !has_location(unless_node->end_keyword_loc)) {
395
+ if (has_location(unless_node->keyword_loc) && !is_end_keyword(unless_node->end_keyword_loc)) {
330
396
  if (!is_postfix_conditional(unless_node->statements, unless_node->keyword_loc)) {
331
397
  analyzed->unclosed_control_flow_count++;
332
398
  }
@@ -338,7 +404,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
338
404
  case PM_CASE_NODE: {
339
405
  const pm_case_node_t* case_node = (const pm_case_node_t*) node;
340
406
 
341
- if (has_location(case_node->case_keyword_loc) && !has_location(case_node->end_keyword_loc)) {
407
+ if (has_location(case_node->case_keyword_loc) && !is_end_keyword(case_node->end_keyword_loc)) {
342
408
  analyzed->unclosed_control_flow_count++;
343
409
  }
344
410
 
@@ -348,7 +414,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
348
414
  case PM_CASE_MATCH_NODE: {
349
415
  const pm_case_match_node_t* case_match_node = (const pm_case_match_node_t*) node;
350
416
 
351
- if (has_location(case_match_node->case_keyword_loc) && !has_location(case_match_node->end_keyword_loc)) {
417
+ if (has_location(case_match_node->case_keyword_loc) && !is_end_keyword(case_match_node->end_keyword_loc)) {
352
418
  analyzed->unclosed_control_flow_count++;
353
419
  }
354
420
 
@@ -358,7 +424,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
358
424
  case PM_WHILE_NODE: {
359
425
  const pm_while_node_t* while_node = (const pm_while_node_t*) node;
360
426
 
361
- if (has_location(while_node->keyword_loc) && !has_location(while_node->closing_loc)) {
427
+ if (has_location(while_node->keyword_loc) && !is_end_keyword(while_node->closing_loc)) {
362
428
  analyzed->unclosed_control_flow_count++;
363
429
  }
364
430
 
@@ -368,7 +434,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
368
434
  case PM_UNTIL_NODE: {
369
435
  const pm_until_node_t* until_node = (const pm_until_node_t*) node;
370
436
 
371
- if (has_location(until_node->keyword_loc) && !has_location(until_node->closing_loc)) {
437
+ if (has_location(until_node->keyword_loc) && !is_end_keyword(until_node->closing_loc)) {
372
438
  analyzed->unclosed_control_flow_count++;
373
439
  }
374
440
 
@@ -378,7 +444,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
378
444
  case PM_FOR_NODE: {
379
445
  const pm_for_node_t* for_node = (const pm_for_node_t*) node;
380
446
 
381
- if (has_location(for_node->for_keyword_loc) && !has_location(for_node->end_keyword_loc)) {
447
+ if (has_location(for_node->for_keyword_loc) && !is_end_keyword(for_node->end_keyword_loc)) {
382
448
  analyzed->unclosed_control_flow_count++;
383
449
  }
384
450
 
@@ -388,7 +454,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
388
454
  case PM_BEGIN_NODE: {
389
455
  const pm_begin_node_t* begin_node = (const pm_begin_node_t*) node;
390
456
 
391
- if (has_location(begin_node->begin_keyword_loc) && !has_location(begin_node->end_keyword_loc)) {
457
+ if (has_location(begin_node->begin_keyword_loc) && !is_end_keyword(begin_node->end_keyword_loc)) {
392
458
  analyzed->unclosed_control_flow_count++;
393
459
  }
394
460
 
@@ -399,7 +465,9 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
399
465
  const pm_block_node_t* block_node = (const pm_block_node_t*) node;
400
466
  bool has_opening = is_do_block(block_node->opening_loc) || is_brace_block(block_node->opening_loc);
401
467
 
402
- if (has_opening && !has_location(block_node->closing_loc)) { analyzed->unclosed_control_flow_count++; }
468
+ if (has_opening && !has_valid_block_closing(block_node->opening_loc, block_node->closing_loc)) {
469
+ analyzed->unclosed_control_flow_count++;
470
+ }
403
471
  break;
404
472
  }
405
473
 
data/src/analyzed_ruby.c CHANGED
@@ -30,6 +30,7 @@ analyzed_ruby_T* init_analyzed_ruby(hb_string_T source) {
30
30
  analyzed->ensure_node_count = 0;
31
31
  analyzed->unless_node_count = 0;
32
32
  analyzed->yield_node_count = 0;
33
+ analyzed->then_keyword_count = 0;
33
34
  analyzed->unclosed_control_flow_count = 0;
34
35
 
35
36
  return analyzed;
data/src/ast_nodes.c CHANGED
@@ -220,7 +220,7 @@ AST_ERB_ELSE_NODE_T* ast_erb_else_node_init(token_T* tag_opening, token_T* conte
220
220
  return erb_else_node;
221
221
  }
222
222
 
223
- AST_ERB_IF_NODE_T* ast_erb_if_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, hb_array_T* statements, struct AST_NODE_STRUCT* subsequent, struct AST_ERB_END_NODE_STRUCT* end_node, position_T start_position, position_T end_position, hb_array_T* errors) {
223
+ AST_ERB_IF_NODE_T* ast_erb_if_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, location_T* then_keyword, hb_array_T* statements, struct AST_NODE_STRUCT* subsequent, struct AST_ERB_END_NODE_STRUCT* end_node, position_T start_position, position_T end_position, hb_array_T* errors) {
224
224
  AST_ERB_IF_NODE_T* erb_if_node = malloc(sizeof(AST_ERB_IF_NODE_T));
225
225
 
226
226
  ast_node_init(&erb_if_node->base, AST_ERB_IF_NODE, start_position, end_position, errors);
@@ -228,6 +228,7 @@ AST_ERB_IF_NODE_T* ast_erb_if_node_init(token_T* tag_opening, token_T* content,
228
228
  erb_if_node->tag_opening = token_copy(tag_opening);
229
229
  erb_if_node->content = token_copy(content);
230
230
  erb_if_node->tag_closing = token_copy(tag_closing);
231
+ erb_if_node->then_keyword = then_keyword;
231
232
  erb_if_node->statements = statements;
232
233
  erb_if_node->subsequent = subsequent;
233
234
  erb_if_node->end_node = end_node;
@@ -249,7 +250,7 @@ AST_ERB_BLOCK_NODE_T* ast_erb_block_node_init(token_T* tag_opening, token_T* con
249
250
  return erb_block_node;
250
251
  }
251
252
 
252
- AST_ERB_WHEN_NODE_T* ast_erb_when_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, hb_array_T* statements, position_T start_position, position_T end_position, hb_array_T* errors) {
253
+ AST_ERB_WHEN_NODE_T* ast_erb_when_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, location_T* then_keyword, hb_array_T* statements, position_T start_position, position_T end_position, hb_array_T* errors) {
253
254
  AST_ERB_WHEN_NODE_T* erb_when_node = malloc(sizeof(AST_ERB_WHEN_NODE_T));
254
255
 
255
256
  ast_node_init(&erb_when_node->base, AST_ERB_WHEN_NODE, start_position, end_position, errors);
@@ -257,6 +258,7 @@ AST_ERB_WHEN_NODE_T* ast_erb_when_node_init(token_T* tag_opening, token_T* conte
257
258
  erb_when_node->tag_opening = token_copy(tag_opening);
258
259
  erb_when_node->content = token_copy(content);
259
260
  erb_when_node->tag_closing = token_copy(tag_closing);
261
+ erb_when_node->then_keyword = then_keyword;
260
262
  erb_when_node->statements = statements;
261
263
 
262
264
  return erb_when_node;
@@ -380,7 +382,7 @@ AST_ERB_BEGIN_NODE_T* ast_erb_begin_node_init(token_T* tag_opening, token_T* con
380
382
  return erb_begin_node;
381
383
  }
382
384
 
383
- AST_ERB_UNLESS_NODE_T* ast_erb_unless_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, hb_array_T* statements, struct AST_ERB_ELSE_NODE_STRUCT* else_clause, struct AST_ERB_END_NODE_STRUCT* end_node, position_T start_position, position_T end_position, hb_array_T* errors) {
385
+ AST_ERB_UNLESS_NODE_T* ast_erb_unless_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, location_T* then_keyword, hb_array_T* statements, struct AST_ERB_ELSE_NODE_STRUCT* else_clause, struct AST_ERB_END_NODE_STRUCT* end_node, position_T start_position, position_T end_position, hb_array_T* errors) {
384
386
  AST_ERB_UNLESS_NODE_T* erb_unless_node = malloc(sizeof(AST_ERB_UNLESS_NODE_T));
385
387
 
386
388
  ast_node_init(&erb_unless_node->base, AST_ERB_UNLESS_NODE, start_position, end_position, errors);
@@ -388,6 +390,7 @@ AST_ERB_UNLESS_NODE_T* ast_erb_unless_node_init(token_T* tag_opening, token_T* c
388
390
  erb_unless_node->tag_opening = token_copy(tag_opening);
389
391
  erb_unless_node->content = token_copy(content);
390
392
  erb_unless_node->tag_closing = token_copy(tag_closing);
393
+ erb_unless_node->then_keyword = then_keyword;
391
394
  erb_unless_node->statements = statements;
392
395
  erb_unless_node->else_clause = else_clause;
393
396
  erb_unless_node->end_node = end_node;
@@ -407,7 +410,7 @@ AST_ERB_YIELD_NODE_T* ast_erb_yield_node_init(token_T* tag_opening, token_T* con
407
410
  return erb_yield_node;
408
411
  }
409
412
 
410
- AST_ERB_IN_NODE_T* ast_erb_in_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, hb_array_T* statements, position_T start_position, position_T end_position, hb_array_T* errors) {
413
+ AST_ERB_IN_NODE_T* ast_erb_in_node_init(token_T* tag_opening, token_T* content, token_T* tag_closing, location_T* then_keyword, hb_array_T* statements, position_T start_position, position_T end_position, hb_array_T* errors) {
411
414
  AST_ERB_IN_NODE_T* erb_in_node = malloc(sizeof(AST_ERB_IN_NODE_T));
412
415
 
413
416
  ast_node_init(&erb_in_node->base, AST_ERB_IN_NODE, start_position, end_position, errors);
@@ -415,6 +418,7 @@ AST_ERB_IN_NODE_T* ast_erb_in_node_init(token_T* tag_opening, token_T* content,
415
418
  erb_in_node->tag_opening = token_copy(tag_opening);
416
419
  erb_in_node->content = token_copy(content);
417
420
  erb_in_node->tag_closing = token_copy(tag_closing);
421
+ erb_in_node->then_keyword = then_keyword;
418
422
  erb_in_node->statements = statements;
419
423
 
420
424
  return erb_in_node;
@@ -726,6 +730,7 @@ static void ast_free_erb_if_node(AST_ERB_IF_NODE_T* erb_if_node) {
726
730
  if (erb_if_node->tag_opening != NULL) { token_free(erb_if_node->tag_opening); }
727
731
  if (erb_if_node->content != NULL) { token_free(erb_if_node->content); }
728
732
  if (erb_if_node->tag_closing != NULL) { token_free(erb_if_node->tag_closing); }
733
+ if (erb_if_node->then_keyword != NULL) { free(erb_if_node->then_keyword); }
729
734
  if (erb_if_node->statements != NULL) {
730
735
  for (size_t i = 0; i < hb_array_size(erb_if_node->statements); i++) {
731
736
  AST_NODE_T* child = hb_array_get(erb_if_node->statements, i);
@@ -761,6 +766,7 @@ static void ast_free_erb_when_node(AST_ERB_WHEN_NODE_T* erb_when_node) {
761
766
  if (erb_when_node->tag_opening != NULL) { token_free(erb_when_node->tag_opening); }
762
767
  if (erb_when_node->content != NULL) { token_free(erb_when_node->content); }
763
768
  if (erb_when_node->tag_closing != NULL) { token_free(erb_when_node->tag_closing); }
769
+ if (erb_when_node->then_keyword != NULL) { free(erb_when_node->then_keyword); }
764
770
  if (erb_when_node->statements != NULL) {
765
771
  for (size_t i = 0; i < hb_array_size(erb_when_node->statements); i++) {
766
772
  AST_NODE_T* child = hb_array_get(erb_when_node->statements, i);
@@ -933,6 +939,7 @@ static void ast_free_erb_unless_node(AST_ERB_UNLESS_NODE_T* erb_unless_node) {
933
939
  if (erb_unless_node->tag_opening != NULL) { token_free(erb_unless_node->tag_opening); }
934
940
  if (erb_unless_node->content != NULL) { token_free(erb_unless_node->content); }
935
941
  if (erb_unless_node->tag_closing != NULL) { token_free(erb_unless_node->tag_closing); }
942
+ if (erb_unless_node->then_keyword != NULL) { free(erb_unless_node->then_keyword); }
936
943
  if (erb_unless_node->statements != NULL) {
937
944
  for (size_t i = 0; i < hb_array_size(erb_unless_node->statements); i++) {
938
945
  AST_NODE_T* child = hb_array_get(erb_unless_node->statements, i);
@@ -959,6 +966,7 @@ static void ast_free_erb_in_node(AST_ERB_IN_NODE_T* erb_in_node) {
959
966
  if (erb_in_node->tag_opening != NULL) { token_free(erb_in_node->tag_opening); }
960
967
  if (erb_in_node->content != NULL) { token_free(erb_in_node->content); }
961
968
  if (erb_in_node->tag_closing != NULL) { token_free(erb_in_node->tag_closing); }
969
+ if (erb_in_node->then_keyword != NULL) { free(erb_in_node->then_keyword); }
962
970
  if (erb_in_node->statements != NULL) {
963
971
  for (size_t i = 0; i < hb_array_size(erb_in_node->statements); i++) {
964
972
  AST_NODE_T* child = hb_array_get(erb_in_node->statements, i);
@@ -265,6 +265,19 @@ void ast_pretty_print_node(AST_NODE_T* node, const size_t indent, const size_t r
265
265
  pretty_print_token_property(erb_if_node->tag_opening, hb_string("tag_opening"), indent, relative_indent, false, buffer);
266
266
  pretty_print_token_property(erb_if_node->content, hb_string("content"), indent, relative_indent, false, buffer);
267
267
  pretty_print_token_property(erb_if_node->tag_closing, hb_string("tag_closing"), indent, relative_indent, false, buffer);
268
+ pretty_print_label(hb_string("then_keyword"), indent, relative_indent, false, buffer);
269
+ if (erb_if_node->then_keyword) {
270
+ char then_keyword_location_string[128];
271
+ sprintf(then_keyword_location_string, " (location: (%u:%u)-(%u:%u))\n",
272
+ erb_if_node->then_keyword->start.line,
273
+ erb_if_node->then_keyword->start.column,
274
+ erb_if_node->then_keyword->end.line,
275
+ erb_if_node->then_keyword->end.column);
276
+ hb_buffer_append(buffer, then_keyword_location_string);
277
+ } else {
278
+ hb_buffer_append(buffer, " ∅\n");
279
+ }
280
+
268
281
  pretty_print_array(hb_string("statements"), erb_if_node->statements, indent, relative_indent, false, buffer);
269
282
 
270
283
  pretty_print_label(hb_string("subsequent"), indent, relative_indent, false, buffer);
@@ -330,6 +343,19 @@ void ast_pretty_print_node(AST_NODE_T* node, const size_t indent, const size_t r
330
343
  pretty_print_token_property(erb_when_node->tag_opening, hb_string("tag_opening"), indent, relative_indent, false, buffer);
331
344
  pretty_print_token_property(erb_when_node->content, hb_string("content"), indent, relative_indent, false, buffer);
332
345
  pretty_print_token_property(erb_when_node->tag_closing, hb_string("tag_closing"), indent, relative_indent, false, buffer);
346
+ pretty_print_label(hb_string("then_keyword"), indent, relative_indent, false, buffer);
347
+ if (erb_when_node->then_keyword) {
348
+ char then_keyword_location_string[128];
349
+ sprintf(then_keyword_location_string, " (location: (%u:%u)-(%u:%u))\n",
350
+ erb_when_node->then_keyword->start.line,
351
+ erb_when_node->then_keyword->start.column,
352
+ erb_when_node->then_keyword->end.line,
353
+ erb_when_node->then_keyword->end.column);
354
+ hb_buffer_append(buffer, then_keyword_location_string);
355
+ } else {
356
+ hb_buffer_append(buffer, " ∅\n");
357
+ }
358
+
333
359
  pretty_print_array(hb_string("statements"), erb_when_node->statements, indent, relative_indent, true, buffer);
334
360
  } break;
335
361
 
@@ -602,6 +628,19 @@ void ast_pretty_print_node(AST_NODE_T* node, const size_t indent, const size_t r
602
628
  pretty_print_token_property(erb_unless_node->tag_opening, hb_string("tag_opening"), indent, relative_indent, false, buffer);
603
629
  pretty_print_token_property(erb_unless_node->content, hb_string("content"), indent, relative_indent, false, buffer);
604
630
  pretty_print_token_property(erb_unless_node->tag_closing, hb_string("tag_closing"), indent, relative_indent, false, buffer);
631
+ pretty_print_label(hb_string("then_keyword"), indent, relative_indent, false, buffer);
632
+ if (erb_unless_node->then_keyword) {
633
+ char then_keyword_location_string[128];
634
+ sprintf(then_keyword_location_string, " (location: (%u:%u)-(%u:%u))\n",
635
+ erb_unless_node->then_keyword->start.line,
636
+ erb_unless_node->then_keyword->start.column,
637
+ erb_unless_node->then_keyword->end.line,
638
+ erb_unless_node->then_keyword->end.column);
639
+ hb_buffer_append(buffer, then_keyword_location_string);
640
+ } else {
641
+ hb_buffer_append(buffer, " ∅\n");
642
+ }
643
+
605
644
  pretty_print_array(hb_string("statements"), erb_unless_node->statements, indent, relative_indent, false, buffer);
606
645
 
607
646
  pretty_print_label(hb_string("else_clause"), indent, relative_indent, false, buffer);
@@ -651,6 +690,19 @@ void ast_pretty_print_node(AST_NODE_T* node, const size_t indent, const size_t r
651
690
  pretty_print_token_property(erb_in_node->tag_opening, hb_string("tag_opening"), indent, relative_indent, false, buffer);
652
691
  pretty_print_token_property(erb_in_node->content, hb_string("content"), indent, relative_indent, false, buffer);
653
692
  pretty_print_token_property(erb_in_node->tag_closing, hb_string("tag_closing"), indent, relative_indent, false, buffer);
693
+ pretty_print_label(hb_string("then_keyword"), indent, relative_indent, false, buffer);
694
+ if (erb_in_node->then_keyword) {
695
+ char then_keyword_location_string[128];
696
+ sprintf(then_keyword_location_string, " (location: (%u:%u)-(%u:%u))\n",
697
+ erb_in_node->then_keyword->start.line,
698
+ erb_in_node->then_keyword->start.column,
699
+ erb_in_node->then_keyword->end.line,
700
+ erb_in_node->then_keyword->end.column);
701
+ hb_buffer_append(buffer, then_keyword_location_string);
702
+ } else {
703
+ hb_buffer_append(buffer, " ∅\n");
704
+ }
705
+
654
706
  pretty_print_array(hb_string("statements"), erb_in_node->statements, indent, relative_indent, true, buffer);
655
707
  } break;
656
708
 
@@ -25,9 +25,15 @@ bool has_rescue_node(analyzed_ruby_T* analyzed);
25
25
  bool has_ensure_node(analyzed_ruby_T* analyzed);
26
26
  bool has_unless_node(analyzed_ruby_T* analyzed);
27
27
  bool has_yield_node(analyzed_ruby_T* analyzed);
28
+ bool has_then_keyword(analyzed_ruby_T* analyzed);
28
29
 
29
30
  bool has_error_message(analyzed_ruby_T* anlayzed, const char* message);
30
31
 
32
+ bool is_do_block(pm_location_t opening_location);
33
+ bool is_brace_block(pm_location_t opening_location);
34
+ bool is_closing_brace(pm_location_t location);
35
+ bool has_valid_block_closing(pm_location_t opening_loc, pm_location_t closing_loc);
36
+
31
37
  bool search_if_nodes(const pm_node_t* node, void* data);
32
38
  bool search_block_nodes(const pm_node_t* node, void* data);
33
39
  bool search_case_nodes(const pm_node_t* node, void* data);
@@ -46,6 +52,7 @@ bool search_in_nodes(analyzed_ruby_T* analyzed);
46
52
  bool search_rescue_nodes(analyzed_ruby_T* analyzed);
47
53
  bool search_ensure_nodes(analyzed_ruby_T* analyzed);
48
54
  bool search_yield_nodes(const pm_node_t* node, void* data);
55
+ bool search_then_keywords(const pm_node_t* node, void* data);
49
56
  bool search_unclosed_control_flows(const pm_node_t* node, void* data);
50
57
 
51
58
  void check_erb_node_for_missing_end(const AST_NODE_T* node);
@@ -29,6 +29,7 @@ typedef struct ANALYZED_RUBY_STRUCT {
29
29
  int ensure_node_count;
30
30
  int unless_node_count;
31
31
  int yield_node_count;
32
+ int then_keyword_count;
32
33
  int unclosed_control_flow_count;
33
34
  } analyzed_ruby_T;
34
35