herbst 0.0.1 → 0.1.0

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.
@@ -0,0 +1,452 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi/escape"
4
+ require "herb"
5
+ require "herb/html/util"
6
+ require "temple"
7
+
8
+ module Herbst
9
+ module Slim
10
+ class Converter < Herbst::Converter
11
+ HYPHEN_ATTRIBUTES = ["data", "aria"].freeze #: Array[String]
12
+
13
+ # @rbs @options: Hash[Symbol, untyped]
14
+
15
+ #: (?Hash[Symbol, untyped]) -> void
16
+ def initialize(options = {})
17
+ super()
18
+
19
+ @options = { default_tag: "div" }.merge(options)
20
+ end
21
+
22
+ #: (String) -> Herb::AST::DocumentNode
23
+ def convert(source)
24
+ expression = ::Slim::Parser.new(@options).call(source)
25
+ children = join(prepare([expression]), 0)
26
+
27
+ finish(document(children))
28
+ rescue ::Slim::Parser::SyntaxError => e
29
+ raise Herbst::Error, e.message
30
+ end
31
+
32
+ private
33
+
34
+ #: (Array[untyped]) -> Array[untyped]
35
+ def prepare(expressions)
36
+ items = expressions.flat_map { |expression| flatten(expression) }
37
+ grouped = [] #: Array[untyped]
38
+
39
+ items.each do |item|
40
+ if control?(item) && item[2].match?(::Slim::EndInserter::END_RE)
41
+ warn(nil, "`- #{item[2].strip}` was dropped, since Slim closes blocks itself")
42
+ elsif clause?(item) && grouped.last&.first == :herbst_control
43
+ grouped.last[3] << item
44
+ elsif control?(item)
45
+ clauses = [] #: Array[untyped]
46
+ grouped << [:herbst_control, item[2], item[3], clauses]
47
+ else
48
+ grouped << item
49
+ end
50
+ end
51
+
52
+ grouped
53
+ end
54
+
55
+ #: (untyped) -> Array[untyped]
56
+ def flatten(expression)
57
+ return [] if expression.nil? || expression == [:newline]
58
+ return expression.drop(1).flat_map { |child| flatten(child) } if expression.first == :multi
59
+
60
+ [expression]
61
+ end
62
+
63
+ #: (untyped) -> bool
64
+ def control?(item)
65
+ item[0] == :slim && item[1] == :control
66
+ end
67
+
68
+ #: (untyped) -> bool
69
+ def clause?(item)
70
+ control?(item) && item[2].match?(::Slim::EndInserter::ELSE_RE)
71
+ end
72
+
73
+ #: (Array[untyped], Integer) -> Array[Herb::AST::Node]
74
+ def join(items, depth)
75
+ result = [] #: Array[Herb::AST::Node]
76
+ previous = nil #: untyped
77
+
78
+ items.each do |item|
79
+ converted = convert_item(item, depth)
80
+ next if converted.empty?
81
+
82
+ result << text(newline(depth)) if previous && (block?(previous) || block?(item))
83
+ result.concat(converted)
84
+ previous = item
85
+ end
86
+
87
+ result
88
+ end
89
+
90
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
91
+ def body(expression, depth)
92
+ items = prepare([expression])
93
+ children = join(items, depth + 1)
94
+ return children if children.empty?
95
+
96
+ children.unshift(text(newline(depth + 1))) if block?(items.first)
97
+ children.push(text(newline(depth))) if block?(items.last)
98
+
99
+ children
100
+ end
101
+
102
+ #: (untyped) -> bool
103
+ def block?(item)
104
+ return false unless item[0] == :html
105
+
106
+ case item[1]
107
+ when :tag
108
+ !Herb::HTML::Elements::INLINE_ELEMENTS.include?(item[2].to_s)
109
+ when :doctype
110
+ true
111
+ else
112
+ false
113
+ end
114
+ end
115
+
116
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
117
+ def convert_item(item, depth)
118
+ case item[0]
119
+ when :static
120
+ text_nodes(item[1])
121
+ when :html
122
+ convert_html(item, depth)
123
+ when :slim
124
+ convert_slim(item, depth)
125
+ when :herbst_control
126
+ convert_control(item[1], item[2], item[3], depth)
127
+ else
128
+ unexpected(item)
129
+ end
130
+ end
131
+
132
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
133
+ def convert_html(item, depth)
134
+ case item[1]
135
+ when :tag
136
+ convert_tag(item, depth)
137
+ when :doctype
138
+ convert_doctype(item[2])
139
+ when :comment
140
+ [comment(convert_text(item[2]))]
141
+ when :condcomment
142
+ [comment([literal("[#{item[2]}]>"), *body(item[3], depth), literal("<![endif]")])]
143
+ else
144
+ unexpected(item)
145
+ end
146
+ end
147
+
148
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
149
+ def convert_slim(item, depth)
150
+ case item[1]
151
+ when :text
152
+ convert_text(item[3])
153
+ when :interpolate
154
+ interpolate(item[2])
155
+ when :output
156
+ convert_output(item, depth)
157
+ when :embedded
158
+ convert_embedded(item, depth)
159
+ else
160
+ unexpected(item)
161
+ end
162
+ end
163
+
164
+ #: (untyped) -> bot
165
+ def unexpected(item)
166
+ raise Herbst::Error, "Unexpected Slim expression: #{item.inspect}"
167
+ end
168
+
169
+ #: (untyped) -> Array[Herb::AST::Node]
170
+ def convert_text(expression)
171
+ flatten(expression).flat_map do |item|
172
+ item[0] == :slim && item[1] == :interpolate ? interpolate(item[2]) : convert_item(item, 0)
173
+ end
174
+ end
175
+
176
+ #: (String, ?escape_static: bool) -> Array[Herb::AST::Node]
177
+ def interpolate(string, escape_static: false)
178
+ parts = ::Slim::Interpolation.new.call([:slim, :interpolate, string])
179
+
180
+ flatten(parts).flat_map do |part|
181
+ if part[0] == :static
182
+ text_nodes(escape_static ? CGI.escapeHTML(part[1]) : part[1])
183
+ else
184
+ [erb(part[3], opening: part[2] ? "<%=" : "<%==")]
185
+ end
186
+ end
187
+ end
188
+
189
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
190
+ def convert_tag(item, depth)
191
+ _html, _tag, name, attributes, content = item
192
+ raise Herbst::UnsupportedError, "a splat that sets the tag name, as in `*{tag: ...}`, is not supported" if name == "*"
193
+
194
+ attributes = convert_attributes(attributes)
195
+
196
+ if Herb::HTML::Util.void_element?(name.to_s)
197
+ return [element(name.to_s, attributes: attributes), *join(prepare([content]), depth)]
198
+ end
199
+
200
+ children = content.nil? ? [] : body(content, depth) #: Array[Herb::AST::Node]
201
+
202
+ [element(name.to_s, attributes: attributes, body: children, self_closing: content.nil?)]
203
+ end
204
+
205
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
206
+ def convert_output(item, depth)
207
+ _slim, _output, escape, code, content = item
208
+ code, comment = split_comment(code)
209
+ opening = escape ? "<%=" : "<%=="
210
+ children = body(content, depth)
211
+
212
+ return [*comment_nodes(comment), erb(code, opening: opening)] if children.empty? && !opens_block?(code)
213
+
214
+ [*comment_nodes(comment), erb_block(block_code(code), children, opening: opening)]
215
+ end
216
+
217
+ #: (String, untyped, Array[untyped], Integer) -> Array[Herb::AST::Node]
218
+ def convert_control(code, content, clause_items, depth)
219
+ code, comment = split_comment(code)
220
+ return comment_nodes(comment) if code.empty?
221
+
222
+ content, clause_items = nested_clauses(content) if keyword(code) == "case" && clause_items.empty?
223
+
224
+ children = body(content, depth)
225
+ return [*comment_nodes(comment), erb(code, opening: "<%")] if children.empty? && clause_items.empty? && !code.match?(::Slim::EndInserter::IF_RE)
226
+
227
+ clauses = clause_items.map do |clause|
228
+ clause_code, clause_comment = split_comment(clause[2])
229
+ [clause_code, keyword(clause_code), [*comment_nodes(clause_comment), *body(clause[3], depth)], nil] #: Herbst::Converter::clause
230
+ end
231
+
232
+ [*comment_nodes(comment), control(block_code(code), keyword(code), children, clauses, depth)]
233
+ end
234
+
235
+ # Slim also accepts the `- when` lines of a `- case` indented under it.
236
+ #
237
+ #: (untyped) -> [untyped, Array[untyped]]
238
+ def nested_clauses(content)
239
+ items = prepare([content])
240
+ first = items.first
241
+ return [content, []] unless items.size == 1 && first[0] == :herbst_control && ["when", "in"].include?(keyword(first[1]))
242
+
243
+ [[:multi], [[:slim, :control, first[1], first[2]], *first[3]]]
244
+ end
245
+
246
+ # Slim compiles each line of code onto its own line of Ruby, but in ERB the next tag follows on
247
+ # the same line, where a trailing comment would swallow it. The comment moves into an ERB comment.
248
+ #
249
+ #: (String) -> [String, String?]
250
+ def split_comment(code)
251
+ comment = Prism.parse(code).comments.last
252
+ return [code, nil] unless comment && code.byteslice(comment.location.end_offset, code.bytesize).to_s.strip.empty?
253
+
254
+ [code.byteslice(0, comment.location.start_offset).to_s.rstrip, comment.slice.delete_prefix("#").strip]
255
+ end
256
+
257
+ #: (String?) -> Array[Herb::AST::Node]
258
+ def comment_nodes(comment)
259
+ comment.nil? || comment.empty? ? [] : [erb_comment(comment.gsub("%>", "% >"))]
260
+ end
261
+
262
+ #: (String) -> bool
263
+ def opens_block?(code)
264
+ code.match?(/\bdo\s*(\|[^|]*\|)?\s*\z/)
265
+ end
266
+
267
+ #: (String) -> String
268
+ def block_code(code)
269
+ code.match?(::Slim::DoInserter::BLOCK_REGEX) ? code : "#{code} do"
270
+ end
271
+
272
+ #: (String) -> Array[Herb::AST::Node]
273
+ def convert_doctype(type)
274
+ html = Temple::HTML::Fast.new(format: :html).call([:html, :doctype, type])[1]
275
+
276
+ Herb.parse(html, track_whitespace: true).value.children
277
+ rescue Temple::FilterError => e
278
+ raise Herbst::UnsupportedError, "`doctype #{type}` is not supported: #{e.message}"
279
+ end
280
+
281
+ #: (untyped, Integer) -> Array[Herb::AST::Node]
282
+ def convert_embedded(item, depth)
283
+ _slim, _embedded, engine, content, attributes = item
284
+ source = flatten(content).filter_map { |part| part[2] if part[1] == :interpolate }.join
285
+
286
+ case engine
287
+ when "javascript"
288
+ [element("script", attributes: convert_attributes(attributes), body: embedded_body(source, depth))]
289
+ when "css"
290
+ [element("style", attributes: convert_attributes(attributes), body: embedded_body(source, depth))]
291
+ when "ruby"
292
+ [erb(source, opening: "<%")]
293
+ else
294
+ raise Herbst::UnsupportedError, "the `#{engine}:` embedded engine is not supported"
295
+ end
296
+ end
297
+
298
+ #: (String, Integer) -> Array[Herb::AST::Node]
299
+ def embedded_body(source, _depth)
300
+ interpolate(source)
301
+ end
302
+
303
+ #: (untyped) -> Array[Herb::AST::Node]
304
+ def convert_attributes(expression)
305
+ values = Hash.new { |hash, key| hash[key] = [] } #: Hash[String, Array[untyped]]
306
+ splats = [] #: Array[String]
307
+
308
+ expression.drop(2).each do |attribute|
309
+ if attribute[1] == :splat
310
+ splats << attribute[2]
311
+ else
312
+ values[attribute[2].to_s] << attribute[3]
313
+ end
314
+ end
315
+
316
+ nodes = values.keys.sort.flat_map do |name|
317
+ name == "class" ? convert_class(values.fetch(name)) : convert_attribute(name, values.fetch(name).fetch(-1))
318
+ end
319
+
320
+ nodes + splats.map do |code|
321
+ warn(nil, "`*#{code}` was converted to `tag.attributes`, which requires Rails 7.1+")
322
+ erb("tag.attributes(**#{code})")
323
+ end
324
+ end
325
+
326
+ #: (Array[untyped]) -> Array[Herb::AST::Node]
327
+ def convert_class(values)
328
+ parts = values.filter_map do |value|
329
+ nodes = class_value(value)
330
+ nodes unless nodes.empty?
331
+ end
332
+
333
+ return [] if parts.empty?
334
+
335
+ [attribute("class", parts.flat_map.with_index { |nodes, index| index.zero? ? nodes : [literal(" "), *nodes] })]
336
+ end
337
+
338
+ #: (untyped) -> Array[Herb::AST::Node]
339
+ def class_value(value)
340
+ case value[0]
341
+ when :static
342
+ value[1].to_s.empty? ? [] : [literal(value[1].to_s)]
343
+ when :escape
344
+ attribute_value(value[2][2], value[1])
345
+ when :slim
346
+ _slim, _attrvalue, escape, code = value
347
+ return [literal(static_class(code))] if Temple::StaticAnalyzer.static?(code)
348
+
349
+ code = "#{code}.flatten.map(&:to_s).reject(&:empty?).join(\" \")" if code.strip.start_with?("[")
350
+ [erb(code, opening: escape ? "<%=" : "<%==")]
351
+ else
352
+ []
353
+ end
354
+ end
355
+
356
+ #: (String, bool) -> Array[Herb::AST::Node]
357
+ def attribute_value(string, escape)
358
+ interpolate(string, escape_static: escape).map do |node|
359
+ node.is_a?(Herb::AST::HTMLTextNode) ? literal(node.content.to_s) : node
360
+ end
361
+ end
362
+
363
+ #: (String) -> String
364
+ def static_class(code)
365
+ value = eval(code) # rubocop:disable Security/Eval
366
+ value = Array(value).flatten.map(&:to_s).reject(&:empty?).join(" ") if value.is_a?(Array)
367
+
368
+ CGI.escapeHTML(value.to_s)
369
+ end
370
+
371
+ #: (String, untyped) -> Array[Herb::AST::Node]
372
+ def convert_attribute(name, value)
373
+ case value[0]
374
+ when :static
375
+ [attribute(name, [literal(value[1].to_s)])]
376
+ when :multi
377
+ [attribute(name)]
378
+ when :escape
379
+ [attribute(name, attribute_value(value[2][2], value[1]))]
380
+ when :slim
381
+ if HYPHEN_ATTRIBUTES.include?(name) && value[3].strip.start_with?("{")
382
+ convert_hash_attribute(name, value[2], value[3])
383
+ else
384
+ convert_code_attribute(name, value[2], value[3])
385
+ end
386
+ else
387
+ unexpected(value)
388
+ end
389
+ end
390
+
391
+ #: (String, bool, String) -> Array[Herb::AST::Node]
392
+ def convert_hash_attribute(name, escape, code)
393
+ entries = hash_entries(code)
394
+
395
+ unless entries
396
+ warn(nil, "`#{name}=#{code}` was converted to `tag.attributes`, which requires Rails 7.1+")
397
+ return [erb("tag.attributes(#{name}: #{code})")]
398
+ end
399
+
400
+ entries.flat_map do |key, value|
401
+ if value.strip.start_with?("{")
402
+ convert_hash_attribute("#{name}-#{key}", escape, value)
403
+ else
404
+ convert_code_attribute("#{name}-#{key}", escape, value)
405
+ end
406
+ end
407
+ end
408
+
409
+ #: (String) -> Array[[String, String]]?
410
+ def hash_entries(code)
411
+ hash = Prism.parse(code).value.statements.body.first
412
+ return nil unless hash.is_a?(Prism::HashNode)
413
+
414
+ entries = [] #: Array[[String, String]]
415
+
416
+ hash.elements.each do |element|
417
+ return nil unless element.is_a?(Prism::AssocNode)
418
+
419
+ key = element.key
420
+ return nil unless key.is_a?(Prism::SymbolNode) || key.is_a?(Prism::StringNode)
421
+
422
+ entries << [key.unescaped, element.value.slice]
423
+ end
424
+
425
+ entries
426
+ end
427
+
428
+ #: (String, bool, String) -> Array[Herb::AST::Node]
429
+ def convert_code_attribute(name, escape, code)
430
+ case code.strip
431
+ when "true"
432
+ [attribute(name)]
433
+ when "false", "nil"
434
+ []
435
+ else
436
+ if Temple::StaticAnalyzer.static?(code)
437
+ value = eval(code) # rubocop:disable Security/Eval
438
+ return convert_code_attribute(name, escape, value.inspect) if [true, false, nil].include?(value)
439
+
440
+ return [attribute(name, [literal(escape ? CGI.escapeHTML(value.to_s) : value.to_s)])]
441
+ end
442
+
443
+ if Herb::HTML::Util.boolean_attribute?(name)
444
+ [erb("#{name.inspect} if #{code}")]
445
+ else
446
+ [attribute(name, [erb(code, opening: escape ? "<%=" : "<%==")])]
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "slim"
5
+ rescue LoadError
6
+ raise Herbst::Error, "Converting Slim templates requires the slim gem. Add `gem \"slim\"` to your Gemfile."
7
+ end
8
+
9
+ module Herbst
10
+ # Converts Slim templates into Herb's syntax tree and ERB.
11
+ #
12
+ # Herbst::Slim.parse("p = greeting") # => Herb::AST::DocumentNode
13
+ # Herbst::Slim.to_erb("p = greeting") # => "<p><%= greeting %></p>"
14
+ #
15
+ # The slim gem is only needed when converting Slim, so it isn't a dependency of Herbst.
16
+ #
17
+ module Slim
18
+ #: (String, **untyped) -> Herb::AST::DocumentNode
19
+ def self.parse(source, **options)
20
+ Converter.new(options).convert(source)
21
+ end
22
+
23
+ #: (String, ?format: bool, **untyped) -> String
24
+ def self.to_erb(source, format: false, **)
25
+ Printer.print(parse(source, **), format: format)
26
+ end
27
+ end
28
+ end
29
+
30
+ require_relative "slim/converter"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Herbst
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/herbst.rb CHANGED
@@ -3,4 +3,26 @@
3
3
  require_relative "herbst/version"
4
4
 
5
5
  module Herbst
6
+ autoload :Haml, "#{__dir__}/herbst/haml"
7
+ autoload :Slim, "#{__dir__}/herbst/slim"
8
+
9
+ class Error < StandardError; end
10
+
11
+ class UnsupportedError < Error
12
+ attr_reader :line #: Integer?
13
+
14
+ #: (String, ?Integer?) -> void
15
+ def initialize(message, line = nil)
16
+ @line = line
17
+
18
+ super(line ? "line #{line}: #{message}" : message)
19
+ end
20
+ end
6
21
  end
22
+
23
+ require_relative "herbst/builder"
24
+ require_relative "herbst/identity_printer"
25
+ require_relative "herbst/printer"
26
+ require_relative "herbst/round_trip"
27
+ require_relative "herbst/converter"
28
+ require_relative "herbst/cli"
@@ -0,0 +1,110 @@
1
+ # Generated from lib/herbst/builder.rb with RBS::Inline
2
+
3
+ module Herbst
4
+ module Builder
5
+ # : (String, String) -> Herb::Token
6
+ def token: (String, String) -> Herb::Token
7
+
8
+ # : (Array[Herb::AST::Node]) -> Herb::AST::DocumentNode
9
+ def document: (Array[Herb::AST::Node]) -> Herb::AST::DocumentNode
10
+
11
+ # : (String) -> Herb::AST::HTMLTextNode
12
+ def text: (String) -> Herb::AST::HTMLTextNode
13
+
14
+ # : (String) -> Herb::AST::LiteralNode
15
+ def literal: (String) -> Herb::AST::LiteralNode
16
+
17
+ # : (?String) -> Herb::AST::WhitespaceNode
18
+ def whitespace: (?String) -> Herb::AST::WhitespaceNode
19
+
20
+ # : (String, ?opening: String) -> (Herb::AST::ERBContentNode | Herb::AST::ERBYieldNode)
21
+ def erb: (String, ?opening: String) -> (Herb::AST::ERBContentNode | Herb::AST::ERBYieldNode)
22
+
23
+ # : (String) -> bool
24
+ def yield?: (String) -> bool
25
+
26
+ # : (String) -> Herb::AST::ERBCommentNode
27
+ def erb_comment: (String) -> Herb::AST::ERBCommentNode
28
+
29
+ # : () -> Herb::AST::ERBEndNode
30
+ def erb_end: () -> Herb::AST::ERBEndNode
31
+
32
+ # : (String, Array[Herb::AST::Node], ?subsequent: (Herb::AST::ERBIfNode | Herb::AST::ERBElseNode)?, ?end_node: Herb::AST::ERBEndNode?) -> Herb::AST::ERBIfNode
33
+ def erb_if: (String, Array[Herb::AST::Node], ?subsequent: (Herb::AST::ERBIfNode | Herb::AST::ERBElseNode)?, ?end_node: Herb::AST::ERBEndNode?) -> Herb::AST::ERBIfNode
34
+
35
+ # : (String, Array[Herb::AST::Node], ?else_clause: Herb::AST::ERBElseNode?) -> Herb::AST::ERBUnlessNode
36
+ def erb_unless: (String, Array[Herb::AST::Node], ?else_clause: Herb::AST::ERBElseNode?) -> Herb::AST::ERBUnlessNode
37
+
38
+ # : (Array[Herb::AST::Node]) -> Herb::AST::ERBElseNode
39
+ def erb_else: (Array[Herb::AST::Node]) -> Herb::AST::ERBElseNode
40
+
41
+ # : (String, Array[Herb::AST::ERBWhenNode], ?else_clause: Herb::AST::ERBElseNode?) -> Herb::AST::ERBCaseNode
42
+ def erb_case: (String, Array[Herb::AST::ERBWhenNode], ?else_clause: Herb::AST::ERBElseNode?) -> Herb::AST::ERBCaseNode
43
+
44
+ # : (String, Array[Herb::AST::Node]) -> Herb::AST::ERBWhenNode
45
+ def erb_when: (String, Array[Herb::AST::Node]) -> Herb::AST::ERBWhenNode
46
+
47
+ # : (String, Array[Herb::AST::Node], ?rescue_clause: Herb::AST::ERBRescueNode?, ?else_clause: Herb::AST::ERBElseNode?, ?ensure_clause: Herb::AST::ERBEnsureNode?) -> Herb::AST::ERBBeginNode
48
+ def erb_begin: (String, Array[Herb::AST::Node], ?rescue_clause: Herb::AST::ERBRescueNode?, ?else_clause: Herb::AST::ERBElseNode?, ?ensure_clause: Herb::AST::ERBEnsureNode?) -> Herb::AST::ERBBeginNode
49
+
50
+ # : (String, Array[Herb::AST::Node], ?subsequent: Herb::AST::ERBRescueNode?) -> Herb::AST::ERBRescueNode
51
+ def erb_rescue: (String, Array[Herb::AST::Node], ?subsequent: Herb::AST::ERBRescueNode?) -> Herb::AST::ERBRescueNode
52
+
53
+ # : (Array[Herb::AST::Node]) -> Herb::AST::ERBEnsureNode
54
+ def erb_ensure: (Array[Herb::AST::Node]) -> Herb::AST::ERBEnsureNode
55
+
56
+ # : (String, Array[Herb::AST::Node]) -> Herb::AST::ERBWhileNode
57
+ def erb_while: (String, Array[Herb::AST::Node]) -> Herb::AST::ERBWhileNode
58
+
59
+ # : (String, Array[Herb::AST::Node]) -> Herb::AST::ERBUntilNode
60
+ def erb_until: (String, Array[Herb::AST::Node]) -> Herb::AST::ERBUntilNode
61
+
62
+ # : (String, Array[Herb::AST::Node]) -> Herb::AST::ERBForNode
63
+ def erb_for: (String, Array[Herb::AST::Node]) -> Herb::AST::ERBForNode
64
+
65
+ # : (String, Array[Herb::AST::Node], ?opening: String) -> Herb::AST::ERBBlockNode
66
+ def erb_block: (String, Array[Herb::AST::Node], ?opening: String) -> Herb::AST::ERBBlockNode
67
+
68
+ # : (String) -> Array[Herb::AST::RubyParameterNode]
69
+ def block_arguments: (String) -> Array[Herb::AST::RubyParameterNode]
70
+
71
+ # : (Prism::node) -> Herb::AST::RubyParameterNode?
72
+ def ruby_parameter: (Prism::node) -> Herb::AST::RubyParameterNode?
73
+
74
+ # : (Symbol?, String, bool, ?Prism::node?) -> Herb::AST::RubyParameterNode?
75
+ def ruby_parameter_node: (Symbol?, String, bool, ?Prism::node?) -> Herb::AST::RubyParameterNode?
76
+
77
+ # : (String, ?attributes: Array[Herb::AST::Node], ?body: Array[Herb::AST::Node], ?self_closing: bool) -> Herb::AST::HTMLElementNode
78
+ def element: (String, ?attributes: Array[Herb::AST::Node], ?body: Array[Herb::AST::Node], ?self_closing: bool) -> Herb::AST::HTMLElementNode
79
+
80
+ # : (String | Herb::AST::ERBContentNode, ?Array[Herb::AST::Node]?) -> Herb::AST::HTMLAttributeNode
81
+ def attribute: (String | Herb::AST::ERBContentNode, ?Array[Herb::AST::Node]?) -> Herb::AST::HTMLAttributeNode
82
+
83
+ # : (Array[Herb::AST::Node]) -> Herb::AST::HTMLCommentNode
84
+ def comment: (Array[Herb::AST::Node]) -> Herb::AST::HTMLCommentNode
85
+
86
+ # : (String) -> Herb::AST::HTMLDoctypeNode
87
+ def doctype: (String) -> Herb::AST::HTMLDoctypeNode
88
+
89
+ # : (String) -> Herb::AST::XMLDeclarationNode
90
+ def xml_declaration: (String) -> Herb::AST::XMLDeclarationNode
91
+
92
+ # : (Prism::node) -> Array[Prism::node]
93
+ def destructured: (Prism::node) -> Array[Prism::node]
94
+
95
+ # : (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
96
+ def open_tag_children: (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
97
+
98
+ # : [T < Herb::AST::Node] (T) -> T
99
+ def merge_texts!: [T < Herb::AST::Node] (T) -> T
100
+
101
+ # : (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
102
+ def merge_adjacent_texts: (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
103
+
104
+ # : (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
105
+ def merge_literals: (Array[Herb::AST::Node]) -> Array[Herb::AST::Node]
106
+
107
+ # : (String, String) -> { tag_opening: Herb::Token, content: Herb::Token, tag_closing: Herb::Token }
108
+ def erb_tag: (String, String) -> { tag_opening: Herb::Token, content: Herb::Token, tag_closing: Herb::Token }
109
+ end
110
+ end