diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5268526..25a890e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,7 @@ jobs: - "3.0" - "3.1" - "3.2" + - "3.3" name: CI runs-on: ubuntu-latest env: diff --git a/.tool-versions b/.tool-versions index c1c77c3..3294aed 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.2.3 +ruby 3.3.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index a2c32f8..cd356c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +- Support Ruby 3.3 by handling yield in ERB specifically + ## [0.11.0] - 2024-04-23 - ErbContent now has its value as child_nodes instead of empty array. diff --git a/lib/syntax_tree/erb/format.rb b/lib/syntax_tree/erb/format.rb index 3e2d7ac..56f27e1 100644 --- a/lib/syntax_tree/erb/format.rb +++ b/lib/syntax_tree/erb/format.rb @@ -131,6 +131,10 @@ def visit_erb_close(node) visit(node.closing) end + def visit_erb_yield(node) + q.text("yield") + end + # Visit an ErbEnd node. def visit_erb_end(node) visit(node.opening_tag) @@ -169,9 +173,14 @@ def format_statement(statement) formatter.format(statement) formatter.flush - rows = formatter.output.join.split("\n") - output_rows(rows) + formatted = + formatter.output.join.gsub( + SyntaxTree::ERB::ErbYield::PLACEHOLDER, + "yield" + ) + + output_rows(formatted.split("\n")) end def output_rows(rows) diff --git a/lib/syntax_tree/erb/nodes.rb b/lib/syntax_tree/erb/nodes.rb index a644843..66d1bd4 100644 --- a/lib/syntax_tree/erb/nodes.rb +++ b/lib/syntax_tree/erb/nodes.rb @@ -372,6 +372,7 @@ def prepare_content(content) # Try to add the keyword to see if it parses result = ErbContent.new(value: [keyword, *content]) @keyword = nil + result end end @@ -489,7 +490,17 @@ class ErbContent < Node def initialize(value:) if value.is_a?(Array) value = - value.map { |token| token.is_a?(Token) ? token.value : token }.join + value + .map do |token| + if token.is_a?(Token) + token.value + elsif token.is_a?(ErbYield) + ErbYield::PLACEHOLDER + else + token + end + end + .join end @value = SyntaxTree.parse(value.strip) end @@ -518,6 +529,21 @@ def deconstruct_keys(keys) end end + class ErbYield < Element + PLACEHOLDER = "qqqqy" + def initialize(new_line:, location:) + super(new_line: new_line, location: location) + end + + def accept(visitor) + visitor.visit_erb_yield(self) + end + + def child_nodes + [] + end + end + # An HtmlAttribute is a key-value pair within a tag. It contains the key, the # equals sign, and the value. class HtmlAttribute < Node diff --git a/lib/syntax_tree/erb/parser.rb b/lib/syntax_tree/erb/parser.rb index 48d43b2..7572aea 100644 --- a/lib/syntax_tree/erb/parser.rb +++ b/lib/syntax_tree/erb/parser.rb @@ -176,6 +176,8 @@ def make_tokens when /\A-?%>/ enum.yield :erb_close, $&, index, line state.pop + when /\Ayield\b/ + enum.yield :erb_yield, $&, index, line when /\A[\p{L}\w]*\b/ # Split by word boundary while parsing the code # This allows us to separate what_to_do vs do @@ -648,7 +650,7 @@ def parse_until_erb_close result = atleast do maybe { parse_erb_do_close } || maybe { parse_erb_close } || - maybe { consume(:erb_code) } + maybe { parse_erb_yield } || maybe { consume(:erb_code) } end items << result @@ -701,6 +703,14 @@ def parse_erb_do_close ) end + def parse_erb_yield + token = consume(:erb_yield) + + new_line = maybe { parse_new_line } + + ErbYield.new(location: token.location, new_line: new_line) + end + def parse_html_string opening = maybe { consume(:string_open_double_quote) } || diff --git a/lib/syntax_tree/erb/pretty_print.rb b/lib/syntax_tree/erb/pretty_print.rb index 869d1bc..0a67a91 100644 --- a/lib/syntax_tree/erb/pretty_print.rb +++ b/lib/syntax_tree/erb/pretty_print.rb @@ -143,6 +143,10 @@ def visit_erb_do_close(node) visit_node("erb_do_close", node) end + def visit_erb_yield(node) + visit_node("erb_yield", node) + end + # Visit a Doctype node. def visit_doctype(node) visit_node("doctype", node)