herb 0.8.9 → 0.8.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9b42d0123634348d9e777dbd6889093262e59df2ee067fbd5a9b9c3a73e701e
4
- data.tar.gz: 2f68d9250ae09827e1946c4b2ed83af9588af36e25522fe76b21eb191a8af2f0
3
+ metadata.gz: 99c86daa1824a8c955a69c6015f8930c49b43b3e4333cb4c16b30d62b0e9ba58
4
+ data.tar.gz: 8e71055933851fa346707121e806e85b59a62d4ef00a9e6c4e7014c26859cca1
5
5
  SHA512:
6
- metadata.gz: 66d65923cffe6d108b4cf48be595d270c56b180596d457a41dba859e81642f894ad5af027a7c8e809cda2ac3c012e73bcfb46c634a9c07f6ac0fbe0a0ddca54f
7
- data.tar.gz: d2b924c70779af2e4a2f4d9a34a5c97a9700df4b0c4c929397afdf57a226c108f0e5233854d9748abf4892e377703a0c05a51d6df374174168dff5d49e67e1a5
6
+ metadata.gz: 50134d7481f2d8bb436d9115d95e58af534a7be1ee4c24e5e193b738d32f605b4e767a49bec3b87b10956faaf1f7128c666e5db81dd4221ef163aa2e0857b505
7
+ data.tar.gz: 551022b3dd92e528d541696b829690bad55e749d2cff7993912cba3539b495266a8ee284cc2845c6d2c435eb029668bef9c8f699730270df0373ea5e0f8136b4
data/Makefile CHANGED
@@ -67,15 +67,19 @@ ifeq ($(os),Linux)
67
67
  endif
68
68
 
69
69
  ifeq ($(os),Darwin)
70
- brew_prefix := $(shell brew --prefix check)
71
- test_cflags = $(test_flags) -I$(brew_prefix)/include
72
- test_ldflags = -L$(brew_prefix)/lib -lcheck -lm $(prism_ldflags)
73
- llvm_path = $(shell brew --prefix llvm@21)
74
- cc = $(llvm_path)/bin/clang
75
- clang_format = $(llvm_path)/bin/clang-format
76
- clang_tidy = $(llvm_path)/bin/clang-tidy
70
+ llvm_version := 21
71
+ llvm_prefix ?= $(shell brew --prefix llvm@$(llvm_version))
72
+ check_prefix ?= $(shell brew --prefix check)
73
+
74
+ cc ?= $(llvm_prefix)/bin/clang
75
+ clang_format ?= $(llvm_prefix)/bin/clang-format
76
+ clang_tidy ?= $(llvm_prefix)/bin/clang-tidy
77
+
78
+ test_cflags = $(test_flags) -I$(check_prefix)/include
79
+ test_ldflags = -L$(check_prefix)/lib -lcheck -lm $(prism_ldflags)
77
80
  endif
78
81
 
82
+ .PHONY: all
79
83
  all: templates prism $(exec) $(lib_name) $(static_lib_name) test wasm clangd_config
80
84
 
81
85
  $(exec): $(objects)
@@ -91,39 +95,49 @@ $(static_lib_name): $(objects)
91
95
  src/%.o: src/%.c templates
92
96
  $(cc) -c $(flags) -fPIC $< -o $@
93
97
 
94
- test/%.o: test/%.c templates
98
+ test/%.o: test/%.c templates prism
95
99
  $(cc) -c $(test_cflags) $(test_flags) $(prism_flags) $< -o $@
96
100
 
101
+ .PHONY: test
97
102
  test: $(test_objects) $(non_main_objects)
98
103
  $(cc) $(test_objects) $(non_main_objects) $(test_cflags) $(test_ldflags) -o $(test_exec)
99
104
 
105
+ .PHONY: clean
100
106
  clean:
101
107
  rm -f $(exec) $(test_exec) $(lib_name) $(shared_lib_name) $(ruby_extension)
102
108
  rm -rf $(objects) $(test_objects) $(extension_objects) lib/herb/*.bundle tmp
103
109
  rm -rf $(prism_path)
104
110
  rake prism:clean
105
111
 
112
+ .PHONY: bundle_install
106
113
  bundle_install:
107
114
  bundle install
108
115
 
116
+ .PHONY: templates
109
117
  templates: bundle_install
110
118
  bundle exec rake templates
111
119
 
120
+ .PHONY: prism
112
121
  prism: bundle_install
113
122
  cd $(prism_path) && ruby templates/template.rb && make static && cd -
114
123
  rake prism:vendor
115
124
 
125
+ .PHONY: format
116
126
  format:
117
127
  $(clang_format) -i $(project_and_extension_files)
118
128
 
129
+ .PHONY: lint
119
130
  lint:
120
131
  $(clang_format) --dry-run --Werror $(project_and_extension_files)
121
132
 
133
+ .PHONY: tidy
122
134
  tidy:
123
135
  $(clang_tidy) $(project_files) -- $(flags)
124
136
 
137
+ .PHONY: clangd_config
125
138
  clangd_config:
126
139
  @echo "$(flags) $(test_cflags)" | tr ' ' '\n' | sort -u > compile_flags.txt
127
140
 
141
+ .PHONY: wasm
128
142
  wasm:
129
143
  cd wasm && make
data/config.yml CHANGED
@@ -178,6 +178,13 @@ errors:
178
178
 
179
179
  fields: []
180
180
 
181
+ - name: ERBCaseWithConditionsError
182
+ message:
183
+ template: "A `case` statement with `when`/`in` in a single ERB tag cannot be formatted. Use separate tags for `case` and its conditions."
184
+ arguments: []
185
+
186
+ fields: []
187
+
181
188
  warnings:
182
189
  fields: []
183
190
  types: []
@@ -341,6 +341,30 @@ static VALUE rb_erb_multiple_blocks_in_tag_error_from_c_struct(ERB_MULTIPLE_BLOC
341
341
  return rb_class_new_instance(3, args, ERBMultipleBlocksInTagError);
342
342
  };
343
343
 
344
+ static VALUE rb_erb_case_with_conditions_error_from_c_struct(ERB_CASE_WITH_CONDITIONS_ERROR_T* erb_case_with_conditions_error) {
345
+ if (erb_case_with_conditions_error == NULL) { return Qnil; }
346
+
347
+ ERROR_T* error = &erb_case_with_conditions_error->base;
348
+
349
+ VALUE Herb = rb_define_module("Herb");
350
+ VALUE Errors = rb_define_module_under(Herb, "Errors");
351
+ VALUE Error = rb_define_class_under(Errors, "Error", rb_cObject);
352
+ VALUE ERBCaseWithConditionsError = rb_define_class_under(Errors, "ERBCaseWithConditionsError", Error);
353
+
354
+ VALUE type = rb_utf8_str_new_cstr(error_type_to_string(error));
355
+ VALUE location = rb_location_from_c_struct(error->location);
356
+ VALUE message = rb_utf8_str_new_cstr(error->message);
357
+
358
+
359
+ VALUE args[3] = {
360
+ type,
361
+ location,
362
+ message
363
+ };
364
+
365
+ return rb_class_new_instance(3, args, ERBCaseWithConditionsError);
366
+ };
367
+
344
368
 
345
369
  VALUE rb_error_from_c_struct(ERROR_T* error) {
346
370
  if (!error) { return Qnil; }
@@ -358,6 +382,7 @@ VALUE rb_error_from_c_struct(ERROR_T* error) {
358
382
  case ERB_CONTROL_FLOW_SCOPE_ERROR: return rb_erb_control_flow_scope_error_from_c_struct((ERB_CONTROL_FLOW_SCOPE_ERROR_T*) error); break;
359
383
  case MISSINGERB_END_TAG_ERROR: return rb_missingerb_end_tag_error_from_c_struct((MISSINGERB_END_TAG_ERROR_T*) error); break;
360
384
  case ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR: return rb_erb_multiple_blocks_in_tag_error_from_c_struct((ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR_T*) error); break;
385
+ case ERB_CASE_WITH_CONDITIONS_ERROR: return rb_erb_case_with_conditions_error_from_c_struct((ERB_CASE_WITH_CONDITIONS_ERROR_T*) error); break;
361
386
  }
362
387
 
363
388
  return Qnil;
data/ext/herb/nodes.c CHANGED
@@ -464,7 +464,7 @@ static VALUE rb_erb_content_node_from_c_struct(AST_ERB_CONTENT_NODE_T* erb_conte
464
464
  VALUE erb_content_node_tag_opening = rb_token_from_c_struct(erb_content_node->tag_opening);
465
465
  VALUE erb_content_node_content = rb_token_from_c_struct(erb_content_node->content);
466
466
  VALUE erb_content_node_tag_closing = rb_token_from_c_struct(erb_content_node->tag_closing);
467
- /* #<Herb::Template::AnalyzedRubyField:0x00007ffb7f81b0a0 @name="analyzed_ruby", @options={kind: nil}> */
467
+ /* #<Herb::Template::AnalyzedRubyField:0x00007f8f1d2522c8 @name="analyzed_ruby", @options={kind: nil}> */
468
468
  VALUE erb_content_node_analyzed_ruby = Qnil;
469
469
  VALUE erb_content_node_parsed = (erb_content_node->parsed) ? Qtrue : Qfalse;
470
470
  VALUE erb_content_node_valid = (erb_content_node->valid) ? Qtrue : Qfalse;
@@ -297,18 +297,20 @@ module Herb
297
297
  end
298
298
 
299
299
  def add_context_aware_expression(code, context)
300
+ closing = code.include?("#") ? "\n))" : "))"
301
+
300
302
  case context
301
303
  when :attribute_value
302
304
  @engine.send(:with_buffer) {
303
- @engine.src << " << #{@attrfunc}((" << code << "))"
305
+ @engine.src << " << #{@attrfunc}((" << code << closing
304
306
  }
305
307
  when :script_content
306
308
  @engine.send(:with_buffer) {
307
- @engine.src << " << #{@jsfunc}((" << code << "))"
309
+ @engine.src << " << #{@jsfunc}((" << code << closing
308
310
  }
309
311
  when :style_content
310
312
  @engine.send(:with_buffer) {
311
- @engine.src << " << #{@cssfunc}((" << code << "))"
313
+ @engine.src << " << #{@cssfunc}((" << code << closing
312
314
  }
313
315
  else
314
316
  @engine.send(:add_expression_result_escaped, code)
@@ -178,7 +178,7 @@ module Herb
178
178
 
179
179
  debug_attributes = [
180
180
  create_debug_attribute("data-herb-debug-outline-type", view_type),
181
- create_debug_attribute("data-herb-debug-file-name", @filename&.basename&.to_s || "unknown"),
181
+ create_debug_attribute("data-herb-debug-file-name", component_display_name),
182
182
  create_debug_attribute("data-herb-debug-file-relative-path", @relative_file_path || "unknown"),
183
183
  create_debug_attribute("data-herb-debug-file-full-path", @filename&.to_s || "unknown")
184
184
  ]
@@ -231,7 +231,7 @@ module Herb
231
231
  debug_attributes = [
232
232
  create_debug_attribute("data-herb-debug-outline-type", outline_type),
233
233
  create_debug_attribute("data-herb-debug-erb", escaped_erb),
234
- create_debug_attribute("data-herb-debug-file-name", @filename&.basename&.to_s || "unknown"),
234
+ create_debug_attribute("data-herb-debug-file-name", component_display_name),
235
235
  create_debug_attribute("data-herb-debug-file-relative-path", @relative_file_path || "unknown"),
236
236
  create_debug_attribute("data-herb-debug-file-full-path", @filename&.to_s || "unknown"),
237
237
  create_debug_attribute("data-herb-debug-inserted", "true")
@@ -288,8 +288,43 @@ module Herb
288
288
  def component?
289
289
  return false unless @filename
290
290
 
291
+ @filename.to_s.match?(%r{(^|/)app/components/})
292
+ end
293
+
294
+ def sidecar_component?
295
+ return false unless component?
296
+ return false unless @filename
297
+
298
+ @filename.basename.to_s.match?(/\Acomponent\.(html\.erb|html\.herb|erb|herb)\z/)
299
+ end
300
+
301
+ def component_display_name
302
+ return @filename&.basename&.to_s || "unknown" unless @filename
303
+
304
+ basename = @filename.basename.to_s
291
305
  path = @filename.to_s
292
- path.include?("/components/")
306
+
307
+ if sidecar_component? && (match = path.match(%r{/components/(.+)/component\.[^/]+\z}))
308
+ return match[1].split("/").map { |s| classify(s) }.join("::")
309
+ end
310
+
311
+ if component?
312
+ path_without_ext = path.sub(/\.(?:html\.erb|html\.herb|erb|herb)\z/, "")
313
+
314
+ if (match = path_without_ext.match(%r{/components/(.+)\z}))
315
+ return match[1].split("/").map { |s| classify(s) }.join("::")
316
+ end
317
+ end
318
+
319
+ basename
320
+ end
321
+
322
+ def classify(name)
323
+ if name.respond_to?(:camelize)
324
+ name.camelize
325
+ else
326
+ name.split(/[_-]/).map(&:capitalize).join
327
+ end
293
328
  end
294
329
 
295
330
  def in_head_context?
data/lib/herb/engine.rb CHANGED
@@ -212,11 +212,15 @@ module Herb
212
212
  end
213
213
 
214
214
  def add_expression_result(code)
215
- with_buffer { @src << " << (" << code << ").to_s" }
215
+ with_buffer {
216
+ @src << " << (" << code << comment_aware_newline(code) << ").to_s"
217
+ }
216
218
  end
217
219
 
218
220
  def add_expression_result_escaped(code)
219
- with_buffer { @src << " << " << @escapefunc << "((" << code << "))" }
221
+ with_buffer {
222
+ @src << " << " << @escapefunc << "((" << code << comment_aware_newline(code) << "))"
223
+ }
220
224
  end
221
225
 
222
226
  def add_expression_block(indicator, code)
@@ -228,11 +232,19 @@ module Herb
228
232
  end
229
233
 
230
234
  def add_expression_block_result(code)
231
- with_buffer { @src << " << " << code }
235
+ with_buffer {
236
+ @src << " << " << code << comment_aware_newline(code)
237
+ }
232
238
  end
233
239
 
234
240
  def add_expression_block_result_escaped(code)
235
- with_buffer { @src << " << " << @escapefunc << "(" << code << ")" }
241
+ with_buffer {
242
+ @src << " << " << @escapefunc << "(" << code << comment_aware_newline(code) << ")"
243
+ }
244
+ end
245
+
246
+ def comment_aware_newline(code)
247
+ code.include?("#") ? "\n" : ""
236
248
  end
237
249
 
238
250
  def add_postamble(postamble)
data/lib/herb/errors.rb CHANGED
@@ -518,5 +518,25 @@ module Herb
518
518
  end
519
519
  end
520
520
 
521
+ class ERBCaseWithConditionsError < Error
522
+ include Colors
523
+
524
+ #: () -> String
525
+ def inspect
526
+ tree_inspect.rstrip.gsub(/\s+$/, "")
527
+ end
528
+
529
+ #: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
530
+ def tree_inspect(indent: 0, depth: 0, depth_limit: 25)
531
+ output = +""
532
+
533
+ output += white("@ #{bold(red(error_name))} #{dimmed("(location: #{location.tree_inspect})\n")}")
534
+ output += white("└── message: #{green(message.inspect)}\n")
535
+ output += %(\n)
536
+
537
+ output.gsub(/^/, " " * indent)
538
+ end
539
+ end
540
+
521
541
  end
522
542
  end
data/lib/herb/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # typed: true
3
3
 
4
4
  module Herb
5
- VERSION = "0.8.9"
5
+ VERSION = "0.8.10"
6
6
  end
@@ -57,6 +57,12 @@ module Herb
57
57
 
58
58
  def component?: () -> untyped
59
59
 
60
+ def sidecar_component?: () -> untyped
61
+
62
+ def component_display_name: () -> untyped
63
+
64
+ def classify: (untyped name) -> untyped
65
+
60
66
  def in_head_context?: () -> untyped
61
67
 
62
68
  def in_script_or_style_context?: () -> untyped
data/sig/herb/engine.rbs CHANGED
@@ -51,6 +51,8 @@ module Herb
51
51
 
52
52
  def add_expression_block_result_escaped: (untyped code) -> untyped
53
53
 
54
+ def comment_aware_newline: (untyped code) -> untyped
55
+
54
56
  def add_postamble: (untyped postamble) -> untyped
55
57
 
56
58
  def with_buffer: () ?{ (?) -> untyped } -> untyped
data/sig/herb/errors.rbs CHANGED
@@ -253,5 +253,15 @@ module Herb
253
253
  # : (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
254
254
  def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
255
255
  end
256
+
257
+ class ERBCaseWithConditionsError < Error
258
+ include Colors
259
+
260
+ # : () -> String
261
+ def inspect: () -> String
262
+
263
+ # : (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
264
+ def tree_inspect: (?indent: Integer, ?depth: Integer, ?depth_limit: Integer) -> String
265
+ end
256
266
  end
257
267
  end
@@ -61,4 +61,7 @@ module Herb
61
61
  type serialized_erb_multiple_blocks_in_tag_error = serialized_error & {
62
62
  }
63
63
 
64
+ type serialized_erb_case_with_conditions_error = serialized_error & {
65
+ }
66
+
64
67
  end
data/src/analyze.c CHANGED
@@ -14,6 +14,7 @@
14
14
  #include "include/util.h"
15
15
  #include "include/util/hb_array.h"
16
16
  #include "include/util/hb_string.h"
17
+ #include "include/util/string.h"
17
18
  #include "include/visitor.h"
18
19
 
19
20
  #include <prism.h>
@@ -34,17 +35,20 @@ static analyzed_ruby_T* herb_analyze_ruby(hb_string_T source) {
34
35
  pm_visit_node(analyzed->root, search_until_nodes, analyzed);
35
36
  pm_visit_node(analyzed->root, search_begin_nodes, analyzed);
36
37
  pm_visit_node(analyzed->root, search_unless_nodes, analyzed);
38
+ pm_visit_node(analyzed->root, search_when_nodes, analyzed);
39
+ pm_visit_node(analyzed->root, search_in_nodes, analyzed);
37
40
 
38
- search_elsif_nodes(analyzed);
39
- search_else_nodes(analyzed);
40
- search_end_nodes(analyzed);
41
- search_when_nodes(analyzed);
42
- search_in_nodes(analyzed);
43
- search_rescue_nodes(analyzed);
44
- search_ensure_nodes(analyzed);
41
+ search_unexpected_elsif_nodes(analyzed);
42
+ search_unexpected_else_nodes(analyzed);
43
+ search_unexpected_end_nodes(analyzed);
44
+ search_unexpected_when_nodes(analyzed);
45
+ search_unexpected_in_nodes(analyzed);
46
+
47
+ search_unexpected_rescue_nodes(analyzed);
48
+ search_unexpected_ensure_nodes(analyzed);
45
49
  search_yield_nodes(analyzed->root, analyzed);
46
50
  search_then_keywords(analyzed->root, analyzed);
47
- search_block_closing_nodes(analyzed);
51
+ search_unexpected_block_closing_nodes(analyzed);
48
52
 
49
53
  if (!analyzed->valid) { pm_visit_node(analyzed->root, search_unclosed_control_flows, analyzed); }
50
54
 
@@ -57,8 +61,8 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
57
61
 
58
62
  const char* opening = erb_content_node->tag_opening->value;
59
63
 
60
- if (strcmp(opening, "<%%") != 0 && strcmp(opening, "<%%=") != 0 && strcmp(opening, "<%#") != 0
61
- && strcmp(opening, "<%graphql") != 0) {
64
+ if (!string_equals(opening, "<%%") && !string_equals(opening, "<%%=") && !string_equals(opening, "<%#")
65
+ && !string_equals(opening, "<%graphql")) {
62
66
  analyzed_ruby_T* analyzed = herb_analyze_ruby(hb_string(erb_content_node->content->value));
63
67
 
64
68
  erb_content_node->parsed = true;
@@ -72,6 +76,16 @@ static bool analyze_erb_content(const AST_NODE_T* node, void* data) {
72
76
  erb_content_node->base.errors
73
77
  );
74
78
  }
79
+
80
+ if (!analyzed->valid
81
+ && ((analyzed->case_node_count > 0 && analyzed->when_node_count > 0)
82
+ || (analyzed->case_match_node_count > 0 && analyzed->in_node_count > 0))) {
83
+ append_erb_case_with_conditions_error(
84
+ erb_content_node->base.location.start,
85
+ erb_content_node->base.location.end,
86
+ erb_content_node->base.errors
87
+ );
88
+ }
75
89
  } else {
76
90
  erb_content_node->parsed = false;
77
91
  erb_content_node->valid = true;
@@ -274,8 +288,8 @@ static control_type_t detect_control_type(AST_ERB_CONTENT_NODE_T* erb_node) {
274
288
  if (has_elsif_node(ruby)) { return CONTROL_TYPE_ELSIF; }
275
289
  if (has_else_node(ruby)) { return CONTROL_TYPE_ELSE; }
276
290
  if (has_end(ruby)) { return CONTROL_TYPE_END; }
277
- if (has_when_node(ruby)) { return CONTROL_TYPE_WHEN; }
278
- if (has_in_node(ruby)) { return CONTROL_TYPE_IN; }
291
+ if (has_when_node(ruby) && !has_case_node(ruby)) { return CONTROL_TYPE_WHEN; }
292
+ if (has_in_node(ruby) && !has_case_match_node(ruby)) { return CONTROL_TYPE_IN; }
279
293
  if (has_rescue_node(ruby)) { return CONTROL_TYPE_RESCUE; }
280
294
  if (has_ensure_node(ruby)) { return CONTROL_TYPE_ENSURE; }
281
295
  if (has_block_closing(ruby)) { return CONTROL_TYPE_BLOCK_CLOSE; }
@@ -3,6 +3,7 @@
3
3
  #include <string.h>
4
4
 
5
5
  #include "include/analyzed_ruby.h"
6
+ #include "include/util/string.h"
6
7
 
7
8
  bool has_if_node(analyzed_ruby_T* analyzed) {
8
9
  return analyzed->if_node_count > 0;
@@ -83,7 +84,7 @@ bool has_then_keyword(analyzed_ruby_T* analyzed) {
83
84
  bool has_error_message(analyzed_ruby_T* anlayzed, const char* message) {
84
85
  for (const pm_diagnostic_t* error = (const pm_diagnostic_t*) anlayzed->parser.error_list.head; error != NULL;
85
86
  error = (const pm_diagnostic_t*) error->node.next) {
86
- if (strcmp(error->message, message) == 0) { return true; }
87
+ if (string_equals(error->message, message)) { return true; }
87
88
  }
88
89
 
89
90
  return false;
@@ -242,7 +243,28 @@ bool search_unless_nodes(const pm_node_t* node, void* data) {
242
243
  return false;
243
244
  }
244
245
 
245
- bool search_elsif_nodes(analyzed_ruby_T* analyzed) {
246
+ bool search_when_nodes(const pm_node_t* node, void* data) {
247
+ analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
248
+
249
+ if (node->type == PM_WHEN_NODE) { analyzed->when_node_count++; }
250
+
251
+ pm_visit_child_nodes(node, search_when_nodes, analyzed);
252
+
253
+ return false;
254
+ }
255
+
256
+ bool search_in_nodes(const pm_node_t* node, void* data) {
257
+ analyzed_ruby_T* analyzed = (analyzed_ruby_T*) data;
258
+
259
+ if (node->type == PM_IN_NODE) { analyzed->in_node_count++; }
260
+ if (node->type == PM_MATCH_PREDICATE_NODE) { analyzed->in_node_count++; }
261
+
262
+ pm_visit_child_nodes(node, search_in_nodes, analyzed);
263
+
264
+ return false;
265
+ }
266
+
267
+ bool search_unexpected_elsif_nodes(analyzed_ruby_T* analyzed) {
246
268
  if (has_error_message(analyzed, "unexpected 'elsif', ignoring it")) {
247
269
  analyzed->elsif_node_count++;
248
270
  return true;
@@ -251,7 +273,7 @@ bool search_elsif_nodes(analyzed_ruby_T* analyzed) {
251
273
  return false;
252
274
  }
253
275
 
254
- bool search_else_nodes(analyzed_ruby_T* analyzed) {
276
+ bool search_unexpected_else_nodes(analyzed_ruby_T* analyzed) {
255
277
  if (has_error_message(analyzed, "unexpected 'else', ignoring it")) {
256
278
  analyzed->else_node_count++;
257
279
  return true;
@@ -260,7 +282,7 @@ bool search_else_nodes(analyzed_ruby_T* analyzed) {
260
282
  return false;
261
283
  }
262
284
 
263
- bool search_end_nodes(analyzed_ruby_T* analyzed) {
285
+ bool search_unexpected_end_nodes(analyzed_ruby_T* analyzed) {
264
286
  if (has_error_message(analyzed, "unexpected 'end', ignoring it")) {
265
287
  if (has_error_message(analyzed, "unexpected '=', ignoring it")) {
266
288
  // `=end`
@@ -274,7 +296,7 @@ bool search_end_nodes(analyzed_ruby_T* analyzed) {
274
296
  return false;
275
297
  }
276
298
 
277
- bool search_block_closing_nodes(analyzed_ruby_T* analyzed) {
299
+ bool search_unexpected_block_closing_nodes(analyzed_ruby_T* analyzed) {
278
300
  if (has_error_message(analyzed, "unexpected '}', ignoring it")) {
279
301
  analyzed->block_closing_count++;
280
302
  return true;
@@ -283,7 +305,7 @@ bool search_block_closing_nodes(analyzed_ruby_T* analyzed) {
283
305
  return false;
284
306
  }
285
307
 
286
- bool search_when_nodes(analyzed_ruby_T* analyzed) {
308
+ bool search_unexpected_when_nodes(analyzed_ruby_T* analyzed) {
287
309
  if (has_error_message(analyzed, "unexpected 'when', ignoring it")) {
288
310
  analyzed->when_node_count++;
289
311
  return true;
@@ -292,7 +314,7 @@ bool search_when_nodes(analyzed_ruby_T* analyzed) {
292
314
  return false;
293
315
  }
294
316
 
295
- bool search_in_nodes(analyzed_ruby_T* analyzed) {
317
+ bool search_unexpected_in_nodes(analyzed_ruby_T* analyzed) {
296
318
  if (has_error_message(analyzed, "unexpected 'in', ignoring it")) {
297
319
  analyzed->in_node_count++;
298
320
  return true;
@@ -301,7 +323,7 @@ bool search_in_nodes(analyzed_ruby_T* analyzed) {
301
323
  return false;
302
324
  }
303
325
 
304
- bool search_rescue_nodes(analyzed_ruby_T* analyzed) {
326
+ bool search_unexpected_rescue_nodes(analyzed_ruby_T* analyzed) {
305
327
  if (has_error_message(analyzed, "unexpected 'rescue', ignoring it")) {
306
328
  analyzed->rescue_node_count++;
307
329
  return true;
@@ -310,7 +332,7 @@ bool search_rescue_nodes(analyzed_ruby_T* analyzed) {
310
332
  return false;
311
333
  }
312
334
 
313
- bool search_ensure_nodes(analyzed_ruby_T* analyzed) {
335
+ bool search_unexpected_ensure_nodes(analyzed_ruby_T* analyzed) {
314
336
  if (has_error_message(analyzed, "unexpected 'ensure', ignoring it")) {
315
337
  analyzed->ensure_node_count++;
316
338
  return true;
@@ -468,6 +490,7 @@ bool search_unclosed_control_flows(const pm_node_t* node, void* data) {
468
490
  if (has_opening && !has_valid_block_closing(block_node->opening_loc, block_node->closing_loc)) {
469
491
  analyzed->unclosed_control_flow_count++;
470
492
  }
493
+
471
494
  break;
472
495
  }
473
496
 
data/src/errors.c CHANGED
@@ -511,6 +511,20 @@ void append_erb_multiple_blocks_in_tag_error(position_T start, position_T end, h
511
511
  hb_array_append(errors, erb_multiple_blocks_in_tag_error_init(start, end));
512
512
  }
513
513
 
514
+ ERB_CASE_WITH_CONDITIONS_ERROR_T* erb_case_with_conditions_error_init(position_T start, position_T end) {
515
+ ERB_CASE_WITH_CONDITIONS_ERROR_T* erb_case_with_conditions_error = malloc(sizeof(ERB_CASE_WITH_CONDITIONS_ERROR_T));
516
+
517
+ error_init(&erb_case_with_conditions_error->base, ERB_CASE_WITH_CONDITIONS_ERROR, start, end);
518
+
519
+ erb_case_with_conditions_error->base.message = herb_strdup("A `case` statement with `when`/`in` in a single ERB tag cannot be formatted. Use separate tags for `case` and its conditions.");
520
+
521
+ return erb_case_with_conditions_error;
522
+ }
523
+
524
+ void append_erb_case_with_conditions_error(position_T start, position_T end, hb_array_T* errors) {
525
+ hb_array_append(errors, erb_case_with_conditions_error_init(start, end));
526
+ }
527
+
514
528
  const char* error_type_to_string(ERROR_T* error) {
515
529
  switch (error->type) {
516
530
  case UNEXPECTED_ERROR: return "UNEXPECTED_ERROR";
@@ -525,6 +539,7 @@ const char* error_type_to_string(ERROR_T* error) {
525
539
  case ERB_CONTROL_FLOW_SCOPE_ERROR: return "ERB_CONTROL_FLOW_SCOPE_ERROR";
526
540
  case MISSINGERB_END_TAG_ERROR: return "MISSINGERB_END_TAG_ERROR";
527
541
  case ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR: return "ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR";
542
+ case ERB_CASE_WITH_CONDITIONS_ERROR: return "ERB_CASE_WITH_CONDITIONS_ERROR";
528
543
  }
529
544
 
530
545
  return "Unknown error_type_T";
@@ -544,6 +559,7 @@ const char* error_human_type(ERROR_T* error) {
544
559
  case ERB_CONTROL_FLOW_SCOPE_ERROR: return "ERBControlFlowScopeError";
545
560
  case MISSINGERB_END_TAG_ERROR: return "MissingERBEndTagError";
546
561
  case ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR: return "ERBMultipleBlocksInTagError";
562
+ case ERB_CASE_WITH_CONDITIONS_ERROR: return "ERBCaseWithConditionsError";
547
563
  }
548
564
 
549
565
  return "Unknown error_type_T";
@@ -638,6 +654,12 @@ static void error_free_erb_multiple_blocks_in_tag_error(ERB_MULTIPLE_BLOCKS_IN_T
638
654
  error_free_base_error(&erb_multiple_blocks_in_tag_error->base);
639
655
  }
640
656
 
657
+ static void error_free_erb_case_with_conditions_error(ERB_CASE_WITH_CONDITIONS_ERROR_T* erb_case_with_conditions_error) {
658
+ /* no ERB_CASE_WITH_CONDITIONS_ERROR_T specific fields to free up */
659
+
660
+ error_free_base_error(&erb_case_with_conditions_error->base);
661
+ }
662
+
641
663
  void error_free(ERROR_T* error) {
642
664
  if (!error) { return; }
643
665
 
@@ -654,6 +676,7 @@ void error_free(ERROR_T* error) {
654
676
  case ERB_CONTROL_FLOW_SCOPE_ERROR: error_free_erb_control_flow_scope_error((ERB_CONTROL_FLOW_SCOPE_ERROR_T*) error); break;
655
677
  case MISSINGERB_END_TAG_ERROR: error_free_missingerb_end_tag_error((MISSINGERB_END_TAG_ERROR_T*) error); break;
656
678
  case ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR: error_free_erb_multiple_blocks_in_tag_error((ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR_T*) error); break;
679
+ case ERB_CASE_WITH_CONDITIONS_ERROR: error_free_erb_case_with_conditions_error((ERB_CASE_WITH_CONDITIONS_ERROR_T*) error); break;
657
680
  }
658
681
  }
659
682
 
@@ -877,6 +900,19 @@ static void error_pretty_print_erb_multiple_blocks_in_tag_error(ERB_MULTIPLE_BLO
877
900
  pretty_print_quoted_property(hb_string("message"), hb_string(error->base.message), indent, relative_indent, true, buffer);
878
901
  }
879
902
 
903
+ static void error_pretty_print_erb_case_with_conditions_error(ERB_CASE_WITH_CONDITIONS_ERROR_T* error, const size_t indent, const size_t relative_indent, hb_buffer_T* buffer) {
904
+ if (!error) { return; }
905
+
906
+ hb_buffer_append(buffer, "@ ");
907
+ hb_buffer_append(buffer, error_human_type((ERROR_T*) error));
908
+ hb_buffer_append(buffer, " ");
909
+
910
+ pretty_print_location(error->base.location, buffer);
911
+ hb_buffer_append(buffer, "\n");
912
+
913
+ pretty_print_quoted_property(hb_string("message"), hb_string(error->base.message), indent, relative_indent, true, buffer);
914
+ }
915
+
880
916
  void error_pretty_print(ERROR_T* error, const size_t indent, const size_t relative_indent, hb_buffer_T* buffer) {
881
917
  if (!error) { return; }
882
918
 
@@ -893,5 +929,6 @@ void error_pretty_print(ERROR_T* error, const size_t indent, const size_t relati
893
929
  case ERB_CONTROL_FLOW_SCOPE_ERROR: error_pretty_print_erb_control_flow_scope_error((ERB_CONTROL_FLOW_SCOPE_ERROR_T*) error, indent, relative_indent, buffer); break;
894
930
  case MISSINGERB_END_TAG_ERROR: error_pretty_print_missingerb_end_tag_error((MISSINGERB_END_TAG_ERROR_T*) error, indent, relative_indent, buffer); break;
895
931
  case ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR: error_pretty_print_erb_multiple_blocks_in_tag_error((ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR_T*) error, indent, relative_indent, buffer); break;
932
+ case ERB_CASE_WITH_CONDITIONS_ERROR: error_pretty_print_erb_case_with_conditions_error((ERB_CASE_WITH_CONDITIONS_ERROR_T*) error, indent, relative_indent, buffer); break;
896
933
  }
897
934
  }
data/src/extract.c CHANGED
@@ -3,7 +3,9 @@
3
3
  #include "include/lexer.h"
4
4
  #include "include/util/hb_array.h"
5
5
  #include "include/util/hb_buffer.h"
6
+ #include "include/util/string.h"
6
7
 
8
+ #include <assert.h>
7
9
  #include <stdlib.h>
8
10
  #include <string.h>
9
11
 
@@ -22,11 +24,11 @@ void herb_extract_ruby_to_buffer(const char* source, hb_buffer_T* output) {
22
24
  }
23
25
 
24
26
  case TOKEN_ERB_START: {
25
- if (strcmp(token->value, "<%#") == 0) {
27
+ if (string_equals(token->value, "<%#")) {
26
28
  skip_erb_content = true;
27
29
  is_comment_tag = true;
28
- } else if (strcmp(token->value, "<%%") == 0 || strcmp(token->value, "<%%=") == 0
29
- || strcmp(token->value, "<%graphql") == 0) {
30
+ } else if (string_equals(token->value, "<%%") || string_equals(token->value, "<%%=")
31
+ || string_equals(token->value, "<%graphql")) {
30
32
  skip_erb_content = true;
31
33
  is_comment_tag = false;
32
34
  } else {
@@ -129,6 +131,7 @@ char* herb_extract(const char* source, const herb_extract_language_T language) {
129
131
  switch (language) {
130
132
  case HERB_EXTRACT_LANGUAGE_RUBY: herb_extract_ruby_to_buffer(source, &output); break;
131
133
  case HERB_EXTRACT_LANGUAGE_HTML: herb_extract_html_to_buffer(source, &output); break;
134
+ default: assert(0 && "invalid extract language");
132
135
  }
133
136
 
134
137
  return output.value;
@@ -34,26 +34,29 @@ bool is_brace_block(pm_location_t opening_location);
34
34
  bool is_closing_brace(pm_location_t location);
35
35
  bool has_valid_block_closing(pm_location_t opening_loc, pm_location_t closing_loc);
36
36
 
37
- bool search_if_nodes(const pm_node_t* node, void* data);
37
+ bool search_begin_nodes(const pm_node_t* node, void* data);
38
38
  bool search_block_nodes(const pm_node_t* node, void* data);
39
- bool search_case_nodes(const pm_node_t* node, void* data);
40
39
  bool search_case_match_nodes(const pm_node_t* node, void* data);
41
- bool search_while_nodes(const pm_node_t* node, void* data);
40
+ bool search_case_nodes(const pm_node_t* node, void* data);
42
41
  bool search_for_nodes(const pm_node_t* node, void* data);
43
- bool search_until_nodes(const pm_node_t* node, void* data);
44
- bool search_begin_nodes(const pm_node_t* node, void* data);
45
- bool search_unless_nodes(const pm_node_t* node, void* data);
46
- bool search_elsif_nodes(analyzed_ruby_T* analyzed);
47
- bool search_else_nodes(analyzed_ruby_T* analyzed);
48
- bool search_end_nodes(analyzed_ruby_T* analyzed);
49
- bool search_block_closing_nodes(analyzed_ruby_T* analyzed);
50
- bool search_when_nodes(analyzed_ruby_T* analyzed);
51
- bool search_in_nodes(analyzed_ruby_T* analyzed);
52
- bool search_rescue_nodes(analyzed_ruby_T* analyzed);
53
- bool search_ensure_nodes(analyzed_ruby_T* analyzed);
54
- bool search_yield_nodes(const pm_node_t* node, void* data);
42
+ bool search_if_nodes(const pm_node_t* node, void* data);
43
+ bool search_in_nodes(const pm_node_t* node, void* data);
55
44
  bool search_then_keywords(const pm_node_t* node, void* data);
56
45
  bool search_unclosed_control_flows(const pm_node_t* node, void* data);
46
+ bool search_unless_nodes(const pm_node_t* node, void* data);
47
+ bool search_until_nodes(const pm_node_t* node, void* data);
48
+ bool search_when_nodes(const pm_node_t* node, void* data);
49
+ bool search_while_nodes(const pm_node_t* node, void* data);
50
+ bool search_yield_nodes(const pm_node_t* node, void* data);
51
+
52
+ bool search_unexpected_block_closing_nodes(analyzed_ruby_T* analyzed);
53
+ bool search_unexpected_else_nodes(analyzed_ruby_T* analyzed);
54
+ bool search_unexpected_elsif_nodes(analyzed_ruby_T* analyzed);
55
+ bool search_unexpected_end_nodes(analyzed_ruby_T* analyzed);
56
+ bool search_unexpected_ensure_nodes(analyzed_ruby_T* analyzed);
57
+ bool search_unexpected_in_nodes(analyzed_ruby_T* analyzed);
58
+ bool search_unexpected_rescue_nodes(analyzed_ruby_T* analyzed);
59
+ bool search_unexpected_when_nodes(analyzed_ruby_T* analyzed);
57
60
 
58
61
  void check_erb_node_for_missing_end(const AST_NODE_T* node);
59
62
 
data/src/include/errors.h CHANGED
@@ -24,6 +24,7 @@ typedef enum {
24
24
  ERB_CONTROL_FLOW_SCOPE_ERROR,
25
25
  MISSINGERB_END_TAG_ERROR,
26
26
  ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR,
27
+ ERB_CASE_WITH_CONDITIONS_ERROR,
27
28
  } error_type_T;
28
29
 
29
30
  typedef struct ERROR_STRUCT {
@@ -102,6 +103,11 @@ typedef struct {
102
103
  /* no additional fields */
103
104
  } ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR_T;
104
105
 
106
+ typedef struct {
107
+ ERROR_T base;
108
+ /* no additional fields */
109
+ } ERB_CASE_WITH_CONDITIONS_ERROR_T;
110
+
105
111
  UNEXPECTED_ERROR_T* unexpected_error_init(const char* description, const char* expected, const char* found, position_T start, position_T end);
106
112
  void append_unexpected_error(const char* description, const char* expected, const char* found, position_T start, position_T end, hb_array_T* errors);
107
113
  UNEXPECTED_TOKEN_ERROR_T* unexpected_token_error_init(token_type_T expected_type, token_T* found, position_T start, position_T end);
@@ -126,6 +132,8 @@ MISSINGERB_END_TAG_ERROR_T* missingerb_end_tag_error_init(const char* keyword, p
126
132
  void append_missingerb_end_tag_error(const char* keyword, position_T start, position_T end, hb_array_T* errors);
127
133
  ERB_MULTIPLE_BLOCKS_IN_TAG_ERROR_T* erb_multiple_blocks_in_tag_error_init(position_T start, position_T end);
128
134
  void append_erb_multiple_blocks_in_tag_error(position_T start, position_T end, hb_array_T* errors);
135
+ ERB_CASE_WITH_CONDITIONS_ERROR_T* erb_case_with_conditions_error_init(position_T start, position_T end);
136
+ void append_erb_case_with_conditions_error(position_T start, position_T end, hb_array_T* errors);
129
137
 
130
138
  void error_init(ERROR_T* error, error_type_T type, position_T start, position_T end);
131
139
 
@@ -0,0 +1,11 @@
1
+ #ifndef HERB_UTILS_STRING_H
2
+ #define HERB_UTILS_STRING_H
3
+
4
+ #include <stdbool.h>
5
+ #include <string.h>
6
+
7
+ static inline bool string_equals(const char* a, const char* b) {
8
+ return strcmp(a, b) == 0;
9
+ }
10
+
11
+ #endif
@@ -1,6 +1,6 @@
1
1
  #ifndef HERB_VERSION_H
2
2
  #define HERB_VERSION_H
3
3
 
4
- #define HERB_VERSION "0.8.9"
4
+ #define HERB_VERSION "0.8.10"
5
5
 
6
6
  #endif
data/src/main.c CHANGED
@@ -9,8 +9,10 @@
9
9
  #include "include/io.h"
10
10
  #include "include/ruby_parser.h"
11
11
  #include "include/util/hb_buffer.h"
12
+ #include "include/util/string.h"
12
13
 
13
14
  #include <stdio.h>
15
+ #include <stdlib.h>
14
16
  #include <string.h>
15
17
  #include <time.h>
16
18
 
@@ -34,22 +36,22 @@ void print_time_diff(const struct timespec start, const struct timespec end, con
34
36
 
35
37
  int main(const int argc, char* argv[]) {
36
38
  if (argc < 2) {
37
- printf("./herb [command] [options]\n\n");
39
+ puts("./herb [command] [options]\n");
38
40
 
39
- printf("Herb 🌿 Powerful and seamless HTML-aware ERB parsing and tooling.\n\n");
41
+ puts("Herb 🌿 Powerful and seamless HTML-aware ERB parsing and tooling.\n");
40
42
 
41
- printf("./herb lex [file] - Lex a file\n");
42
- printf("./herb parse [file] - Parse a file\n");
43
- printf("./herb ruby [file] - Extract Ruby from a file\n");
44
- printf("./herb html [file] - Extract HTML from a file\n");
45
- printf("./herb prism [file] - Extract Ruby from a file and parse the Ruby source with Prism\n");
43
+ puts("./herb lex [file] - Lex a file");
44
+ puts("./herb parse [file] - Parse a file");
45
+ puts("./herb ruby [file] - Extract Ruby from a file");
46
+ puts("./herb html [file] - Extract HTML from a file");
47
+ puts("./herb prism [file] - Extract Ruby from a file and parse the Ruby source with Prism");
46
48
 
47
- return 1;
49
+ return EXIT_FAILURE;
48
50
  }
49
51
 
50
52
  if (argc < 3) {
51
- printf("Please specify input file.\n");
52
- return 1;
53
+ puts("Please specify input file.");
54
+ return EXIT_FAILURE;
53
55
  }
54
56
 
55
57
  hb_buffer_T output;
@@ -61,38 +63,20 @@ int main(const int argc, char* argv[]) {
61
63
  struct timespec start, end;
62
64
  clock_gettime(CLOCK_MONOTONIC, &start);
63
65
 
64
- if (strcmp(argv[1], "visit") == 0) {
65
- AST_DOCUMENT_NODE_T* root = herb_parse(source, NULL);
66
- clock_gettime(CLOCK_MONOTONIC, &end);
67
-
68
- herb_analyze_parse_tree(root, source);
69
-
70
- ast_pretty_print_node((AST_NODE_T*) root, 0, 0, &output);
71
- printf("%s\n", output.value);
72
-
73
- print_time_diff(start, end, "visiting");
74
-
75
- ast_node_free((AST_NODE_T*) root);
76
- free(output.value);
77
- free(source);
78
-
79
- return 0;
80
- }
81
-
82
- if (strcmp(argv[1], "lex") == 0) {
66
+ if (string_equals(argv[1], "lex")) {
83
67
  herb_lex_to_buffer(source, &output);
84
68
  clock_gettime(CLOCK_MONOTONIC, &end);
85
69
 
86
- printf("%s\n", output.value);
70
+ puts(output.value);
87
71
  print_time_diff(start, end, "lexing");
88
72
 
89
73
  free(output.value);
90
74
  free(source);
91
75
 
92
- return 0;
76
+ return EXIT_SUCCESS;
93
77
  }
94
78
 
95
- if (strcmp(argv[1], "parse") == 0) {
79
+ if (string_equals(argv[1], "parse")) {
96
80
  AST_DOCUMENT_NODE_T* root = herb_parse(source, NULL);
97
81
 
98
82
  herb_analyze_parse_tree(root, source);
@@ -100,11 +84,11 @@ int main(const int argc, char* argv[]) {
100
84
  clock_gettime(CLOCK_MONOTONIC, &end);
101
85
 
102
86
  int silent = 0;
103
- if (argc > 3 && strcmp(argv[3], "--silent") == 0) { silent = 1; }
87
+ if (argc > 3 && string_equals(argv[3], "--silent")) { silent = 1; }
104
88
 
105
89
  if (!silent) {
106
90
  ast_pretty_print_node((AST_NODE_T*) root, 0, 0, &output);
107
- printf("%s\n", output.value);
91
+ puts(output.value);
108
92
 
109
93
  print_time_diff(start, end, "parsing");
110
94
  }
@@ -113,36 +97,36 @@ int main(const int argc, char* argv[]) {
113
97
  free(output.value);
114
98
  free(source);
115
99
 
116
- return 0;
100
+ return EXIT_SUCCESS;
117
101
  }
118
102
 
119
- if (strcmp(argv[1], "ruby") == 0) {
103
+ if (string_equals(argv[1], "ruby")) {
120
104
  herb_extract_ruby_to_buffer(source, &output);
121
105
  clock_gettime(CLOCK_MONOTONIC, &end);
122
106
 
123
- printf("%s\n", output.value);
107
+ puts(output.value);
124
108
  print_time_diff(start, end, "extracting Ruby");
125
109
 
126
110
  free(output.value);
127
111
  free(source);
128
112
 
129
- return 0;
113
+ return EXIT_SUCCESS;
130
114
  }
131
115
 
132
- if (strcmp(argv[1], "html") == 0) {
116
+ if (string_equals(argv[1], "html")) {
133
117
  herb_extract_html_to_buffer(source, &output);
134
118
  clock_gettime(CLOCK_MONOTONIC, &end);
135
119
 
136
- printf("%s\n", output.value);
120
+ puts(output.value);
137
121
  print_time_diff(start, end, "extracting HTML");
138
122
 
139
123
  free(output.value);
140
124
  free(source);
141
125
 
142
- return 0;
126
+ return EXIT_SUCCESS;
143
127
  }
144
128
 
145
- if (strcmp(argv[1], "prism") == 0) {
129
+ if (string_equals(argv[1], "prism")) {
146
130
  printf("HTML+ERB File: \n%s\n", source);
147
131
 
148
132
  char* ruby_source = herb_extract(source, HERB_EXTRACT_LANGUAGE_RUBY);
@@ -150,9 +134,13 @@ int main(const int argc, char* argv[]) {
150
134
 
151
135
  herb_parse_ruby_to_stdout(ruby_source);
152
136
 
153
- return 0;
137
+ free(ruby_source);
138
+ free(output.value);
139
+ free(source);
140
+
141
+ return EXIT_SUCCESS;
154
142
  }
155
143
 
156
144
  printf("Unknown Command: %s\n", argv[1]);
157
- return 1;
145
+ return EXIT_FAILURE;
158
146
  }
data/src/parser.c CHANGED
@@ -12,6 +12,7 @@
12
12
  #include "include/util/hb_array.h"
13
13
  #include "include/util/hb_buffer.h"
14
14
  #include "include/util/hb_string.h"
15
+ #include "include/util/string.h"
15
16
  #include "include/visitor.h"
16
17
 
17
18
  #include <stdio.h>
@@ -346,7 +347,7 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
346
347
  while (!token_is(parser, TOKEN_EOF)
347
348
  && !(
348
349
  token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
349
- && strcmp(parser->current_token->value, opening_quote->value) == 0
350
+ && string_equals(parser->current_token->value, opening_quote->value)
350
351
  )) {
351
352
  if (token_is(parser, TOKEN_ERB_START)) {
352
353
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
@@ -364,7 +365,7 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
364
365
  token_T* next_token = lexer_next_token(parser->lexer);
365
366
 
366
367
  if (next_token && next_token->type == TOKEN_QUOTE && opening_quote != NULL
367
- && strcmp(next_token->value, opening_quote->value) == 0) {
368
+ && string_equals(next_token->value, opening_quote->value)) {
368
369
  hb_buffer_append(&buffer, parser->current_token->value);
369
370
  hb_buffer_append(&buffer, next_token->value);
370
371
 
@@ -387,7 +388,7 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
387
388
  }
388
389
 
389
390
  if (token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
390
- && strcmp(parser->current_token->value, opening_quote->value) == 0) {
391
+ && string_equals(parser->current_token->value, opening_quote->value)) {
391
392
  lexer_state_snapshot_T saved_state = lexer_save_state(parser->lexer);
392
393
 
393
394
  token_T* potential_closing = parser->current_token;
@@ -415,7 +416,7 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
415
416
  while (!token_is(parser, TOKEN_EOF)
416
417
  && !(
417
418
  token_is(parser, TOKEN_QUOTE) && opening_quote != NULL
418
- && strcmp(parser->current_token->value, opening_quote->value) == 0
419
+ && string_equals(parser->current_token->value, opening_quote->value)
419
420
  )) {
420
421
  if (token_is(parser, TOKEN_ERB_START)) {
421
422
  parser_append_literal_node_from_buffer(parser, &buffer, children, start);
@@ -445,7 +446,7 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_quoted_html_attribute_value
445
446
 
446
447
  token_T* closing_quote = parser_consume_expected(parser, TOKEN_QUOTE, errors);
447
448
 
448
- if (opening_quote != NULL && closing_quote != NULL && strcmp(opening_quote->value, closing_quote->value) != 0) {
449
+ if (opening_quote != NULL && closing_quote != NULL && !string_equals(opening_quote->value, closing_quote->value)) {
449
450
  append_quotes_mismatch_error(
450
451
  opening_quote,
451
452
  closing_quote,
@@ -1269,17 +1269,17 @@ nodes:
1269
1269
  - name: opening_loc
1270
1270
  type: location
1271
1271
  comment: |
1272
- Represents the location of the opening `|`.
1272
+ Represents the location of the opening `{` or `do`.
1273
1273
 
1274
1274
  [1, 2, 3].each { |i| puts x }
1275
- ^
1275
+ ^
1276
1276
  - name: closing_loc
1277
1277
  type: location
1278
1278
  comment: |
1279
- Represents the location of the closing `|`.
1279
+ Represents the location of the closing `}` or `end`.
1280
1280
 
1281
1281
  [1, 2, 3].each { |i| puts x }
1282
- ^
1282
+ ^
1283
1283
  comment: |
1284
1284
  Represents a block of ruby code.
1285
1285
 
@@ -1826,20 +1826,20 @@ typedef struct pm_block_node {
1826
1826
  /**
1827
1827
  * BlockNode#opening_loc
1828
1828
  *
1829
- * Represents the location of the opening `|`.
1829
+ * Represents the location of the opening `{` or `do`.
1830
1830
  *
1831
1831
  * [1, 2, 3].each { |i| puts x }
1832
- * ^
1832
+ * ^
1833
1833
  */
1834
1834
  pm_location_t opening_loc;
1835
1835
 
1836
1836
  /**
1837
1837
  * BlockNode#closing_loc
1838
1838
  *
1839
- * Represents the location of the closing `|`.
1839
+ * Represents the location of the closing `}` or `end`.
1840
1840
  *
1841
1841
  * [1, 2, 3].each { |i| puts x }
1842
- * ^
1842
+ * ^
1843
1843
  */
1844
1844
  pm_location_t closing_loc;
1845
1845
  } pm_block_node_t;
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * The minor version of the Prism library as an int.
16
16
  */
17
- #define PRISM_VERSION_MINOR 8
17
+ #define PRISM_VERSION_MINOR 9
18
18
 
19
19
  /**
20
20
  * The patch version of the Prism library as an int.
@@ -24,6 +24,6 @@
24
24
  /**
25
25
  * The version of the Prism library as a constant string.
26
26
  */
27
- #define PRISM_VERSION "1.8.0"
27
+ #define PRISM_VERSION "1.9.0"
28
28
 
29
29
  #endif
@@ -12438,7 +12438,7 @@ expect1_opening(pm_parser_t *parser, pm_token_type_t type, pm_diagnostic_id_t di
12438
12438
 
12439
12439
  pm_parser_err(parser, opening->start, opening->end, diag_id);
12440
12440
 
12441
- parser->previous.start = opening->end;
12441
+ parser->previous.start = parser->previous.end;
12442
12442
  parser->previous.type = PM_TOKEN_MISSING;
12443
12443
  }
12444
12444
 
@@ -101,7 +101,7 @@ public class Loader {
101
101
  expect((byte) 'M', "incorrect prism header");
102
102
 
103
103
  expect((byte) 1, "prism major version does not match");
104
- expect((byte) 8, "prism minor version does not match");
104
+ expect((byte) 9, "prism minor version does not match");
105
105
  expect((byte) 0, "prism patch version does not match");
106
106
 
107
107
  expect((byte) 1, "Loader.java requires no location fields in the serialized output");
@@ -1,7 +1,7 @@
1
1
  import * as nodes from "./nodes.js";
2
2
 
3
3
  const MAJOR_VERSION = 1;
4
- const MINOR_VERSION = 8;
4
+ const MINOR_VERSION = 9;
5
5
  const PATCH_VERSION = 0;
6
6
 
7
7
  // The DataView getFloat64 function takes an optional second argument that
@@ -183,25 +183,13 @@ module Prism
183
183
  def tunnel(line, column)
184
184
  queue = [self] #: Array[Prism::node]
185
185
  result = [] #: Array[Prism::node]
186
+ offset = source.byte_offset(line, column)
186
187
 
187
188
  while (node = queue.shift)
188
189
  result << node
189
190
 
190
191
  node.each_child_node do |child_node|
191
- child_location = child_node.location
192
-
193
- start_line = child_location.start_line
194
- end_line = child_location.end_line
195
-
196
- if start_line == end_line
197
- if line == start_line && column >= child_location.start_column && column < child_location.end_column
198
- queue << child_node
199
- break
200
- end
201
- elsif (line == start_line && column >= child_location.start_column) || (line == end_line && column < child_location.end_column)
202
- queue << child_node
203
- break
204
- elsif line > start_line && line < end_line
192
+ if child_node.start_offset <= offset && offset < child_node.end_offset
205
193
  queue << child_node
206
194
  break
207
195
  end
@@ -212,7 +200,7 @@ module Prism
212
200
  end
213
201
 
214
202
  # Returns the first node that matches the given block when visited in a
215
- # depth-first search. This is useful for finding a node that matches a
203
+ # breadth-first search. This is useful for finding a node that matches a
216
204
  # particular condition.
217
205
  #
218
206
  # node.breadth_first_search { |node| node.node_id == node_id }
@@ -227,6 +215,26 @@ module Prism
227
215
 
228
216
  nil
229
217
  end
218
+ alias find breadth_first_search
219
+
220
+ # Returns all of the nodes that match the given block when visited in a
221
+ # breadth-first search. This is useful for finding all nodes that match a
222
+ # particular condition.
223
+ #
224
+ # node.breadth_first_search_all { |node| node.is_a?(Prism::CallNode) }
225
+ #
226
+ def breadth_first_search_all(&block)
227
+ queue = [self] #: Array[Prism::node]
228
+ results = [] #: Array[Prism::node]
229
+
230
+ while (node = queue.shift)
231
+ results << node if yield node
232
+ queue.concat(node.compact_child_nodes)
233
+ end
234
+
235
+ results
236
+ end
237
+ alias find_all breadth_first_search_all
230
238
 
231
239
  # Returns a list of the fields that exist for this node class. Fields
232
240
  # describe the structure of the node. This kind of reflection is useful for
@@ -10,7 +10,7 @@ module Prism
10
10
 
11
11
  # The minor version of prism that we are expecting to find in the serialized
12
12
  # strings.
13
- MINOR_VERSION = 8
13
+ MINOR_VERSION = 9
14
14
 
15
15
  # The patch version of prism that we are expecting to find in the serialized
16
16
  # strings.
@@ -49,6 +49,9 @@ class Prism::Node
49
49
  sig { params(block: T.proc.params(node: Prism::Node).returns(T::Boolean)).returns(T.nilable(Prism::Node)) }
50
50
  def breadth_first_search(&block); end
51
51
 
52
+ sig { params(block: T.proc.params(node: Prism::Node).returns(T::Boolean)).returns(T::Array[Prism::Node]) }
53
+ def breadth_first_search_all(&block); end
54
+
52
55
  sig { abstract.params(visitor: Prism::Visitor).returns(T.untyped) }
53
56
  def accept(visitor); end
54
57
 
@@ -25,6 +25,9 @@ module Prism
25
25
  def to_dot: () -> String
26
26
  def tunnel: (Integer line, Integer column) -> Array[Prism::node]
27
27
  def breadth_first_search: () { (Prism::node) -> bool } -> Prism::node?
28
+ alias find breadth_first_search
29
+ def breadth_first_search_all: () { (Prism::node) -> bool } -> Array[Prism::node]
30
+ alias find_all breadth_first_search_all
28
31
  def newline!: (Array[untyped]) -> void
29
32
 
30
33
  def save: (_Repository repository) -> void
@@ -23,15 +23,16 @@ module Prism
23
23
 
24
24
  def self.<%= method %>: (
25
25
  String source,
26
+ ?command_line: String,
26
27
  ?encoding: Encoding | false,
27
28
  ?filepath: String,
28
29
  ?freeze: bool,
29
30
  ?frozen_string_literal: bool,
30
31
  ?line: Integer,
31
32
  ?main_script: bool,
32
- ?offset: Integer,
33
+ ?partial_script: bool,
33
34
  ?scopes: Array[Array[Symbol]],
34
- ?verbose: bool
35
+ ?version: String
35
36
  ) -> <%= return_type %>
36
37
  <%- end -%>
37
38
 
@@ -41,10 +42,6 @@ module Prism
41
42
  ?bool freeze
42
43
  ) -> ParseResult
43
44
 
44
- def self.lex_ripper: (
45
- String source
46
- ) -> Array[[[Integer, Integer], Symbol, String, untyped]]
47
-
48
45
  # Methods taking a path to a Ruby file:
49
46
  <%-
50
47
  {
@@ -61,14 +58,15 @@ module Prism
61
58
 
62
59
  def self.<%= method %>: (
63
60
  String filepath,
61
+ ?command_line: String,
64
62
  ?encoding: Encoding | false,
65
63
  ?freeze: bool,
66
64
  ?frozen_string_literal: bool,
67
65
  ?line: Integer,
68
66
  ?main_script: bool,
69
- ?offset: Integer,
67
+ ?partial_script: bool,
70
68
  ?scopes: Array[Array[Symbol]],
71
- ?verbose: bool
69
+ ?version: String
72
70
  ) -> <%= return_type %>
73
71
  <%- end -%>
74
72
 
@@ -78,15 +76,16 @@ module Prism
78
76
 
79
77
  def self.parse_stream: (
80
78
  _Stream stream,
79
+ ?command_line: String,
81
80
  ?encoding: Encoding | false,
82
81
  ?filepath: String,
83
82
  ?freeze: bool,
84
83
  ?frozen_string_literal: bool,
85
84
  ?line: Integer,
86
85
  ?main_script: bool,
87
- ?offset: Integer,
86
+ ?partial_script: bool,
88
87
  ?scopes: Array[Array[Symbol]],
89
- ?verbose: bool
88
+ ?version: String
90
89
  ) -> ParseResult
91
90
 
92
91
  def self.scope: (?locals: Array[Symbol], ?forwarding: Array[Symbol]) -> Scope
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: herb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.9
4
+ version: 0.8.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Roth
@@ -148,6 +148,7 @@ files:
148
148
  - src/include/util/hb_narray.h
149
149
  - src/include/util/hb_string.h
150
150
  - src/include/util/hb_system.h
151
+ - src/include/util/string.h
151
152
  - src/include/version.h
152
153
  - src/include/visitor.h
153
154
  - src/io.c