diff --git a/lib/erb/formatter.rb b/lib/erb/formatter.rb
index 3e1db4f..9475d25 100644
--- a/lib/erb/formatter.rb
+++ b/lib/erb/formatter.rb
@@ -39,7 +39,7 @@ class Error < StandardError; end
ATTR = Regexp.union(SINGLE_QUOTE_ATTR, DOUBLE_QUOTE_ATTR, UNQUOTED_ATTR, UNQUOTED_VALUE)
MULTILINE_ATTR_NAMES = %w[class data-action]
- ERB_TAG = %r{(<%(?:==|=|-|))\s*(.*?)\s*(-?%>)}m
+ ERB_TAG = %r{(<%(?:=|-|#)*)(?:(?!\n)\s)*(.*?)\s*(-?%>)}m
ERB_PLACEHOLDER = %r{erb[a-z0-9]+tag}
TAG_NAME = /[a-z0-9_:-]+/
@@ -267,10 +267,14 @@ def format_ruby(code, autoclose: false)
end
p RUBY_IN_: code if @debug
- SyntaxTree::Command.prepend SyntaxTreeCommandPatch
+ # SyntaxTree::Command.prepend SyntaxTreeCommandPatch
code = begin
- SyntaxTree.format(code, @line_width)
+ # TODO: For single-lines, 7 should be subtracted instead of 2: 3 for opening, 2 for closing and 2 surrounding spaces
+ # Subtract 2 for multiline indentation or for the surrounding tags
+ # Then subtract twice the tag_stack size to respect indentation
+ width = @line_width - 2 - tag_stack.size * 2
+ SyntaxTree.format(code, width)
rescue SyntaxTree::Parser::ParseError => error
p RUBY_PARSE_ERROR: error if @debug
code
@@ -278,7 +282,7 @@ def format_ruby(code, autoclose: false)
lines = code.strip.lines
lines = lines[0...-1] if autoclose
- code = lines.map { |l| indented(l.chomp("\n"), strip: false) }.join.strip
+ code = lines.map { |l| indented(l.chomp("\n"), strip: false) }.join
p RUBY_OUT: code if @debug
code
end
@@ -304,31 +308,59 @@ def format_erb_tags(string)
format_text(erb_pre_match)
erb_open, ruby_code, erb_close = ERB_TAG.match(erb_code).captures
- erb_open << ' ' unless ruby_code.start_with?('#')
+ ruby_code.strip!
+
+ block_type =
+ if erb_open.include?('#')
+ :comment
+ else
+ case ruby_code
+ when RUBY_STANDALONE_BLOCK then :standalone
+ when RUBY_CLOSE_BLOCK then :close
+ when RUBY_REOPEN_BLOCK then :reopen
+ when RUBY_OPEN_BLOCK then :open
+ else :other
+ end
+ end
- case ruby_code
- when RUBY_STANDALONE_BLOCK
+ # Format Ruby code, and indent if it's multiline
+ if block_type == :open
+ # Block openers aren't complete ruby scripts, so syntax_tree won't like them.
+ # These are two workarounds to help make most of them valid, so we can format them anyway.
+ if (match = ruby_code.match(/(?:\s+do|\s*\{)(?:\s*\|\s*\w+\s*(?:,\s*\w+\s*)*\|)?\s*\z/))
+ # If this is a block starter (ends with "do" or "{" followed by optional block parameters), it usually is a
+ # valid statement without the suffix
+ suffix = match[0]
+ ruby_code = "#{format_ruby(ruby_code.chomp(suffix), autoclose: false)} #{suffix.strip}"
+ elsif ruby_code.start_with?('if ', 'unless ', 'while ', 'until ')
+ # If this is a condition or loop, it may be a valid expression without first word
+ keyword, rest = ruby_code.split(/\s+/, 2)
+ ruby_code = format_ruby(rest, autoclose: false).sub(/^(\s*)/, "\\1#{keyword} " )
+ end
+ ruby_code.gsub!(/^/, ' ') if ruby_code.strip.include?("\n")
+ elsif block_type == :standalone || block_type == :other
ruby_code = format_ruby(ruby_code, autoclose: false)
- full_erb_tag = "#{erb_open}#{ruby_code} #{erb_close}"
- html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
- when RUBY_CLOSE_BLOCK
- full_erb_tag = "#{erb_open}#{ruby_code} #{erb_close}"
- tag_stack_pop('%erb%', ruby_code)
- html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
- when RUBY_REOPEN_BLOCK
- full_erb_tag = "#{erb_open}#{ruby_code} #{erb_close}"
- tag_stack_pop('%erb%', ruby_code)
- html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
- tag_stack_push('%erb%', ruby_code)
- when RUBY_OPEN_BLOCK
- full_erb_tag = "#{erb_open}#{ruby_code} #{erb_close}"
- html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
- tag_stack_push('%erb%', ruby_code)
+ ruby_code.gsub!(/^/, ' ') if ruby_code.strip.include?("\n")
+ end
+
+ # Remove the first line if it only has whitespace
+ ruby_code.sub!(/\A((?!\n)\s)*\n/, '')
+
+ # Reset "common" indentation of multi-line comments
+ if block_type == :comment && ruby_code.strip.include?("\n")
+ # Leave comments intact, but if they're multiline, replace common indentation
+ ruby_code.gsub!(/^#{ruby_code.scan(/^ */).min_by(&:length)}/, ' ')
+ end
+
+ if ruby_code.strip.include?("\n")
+ full_erb_tag = "#{erb_open}\n#{ruby_code}#{indented(erb_close)}"
else
- ruby_code = format_ruby(ruby_code, autoclose: false)
- full_erb_tag = "#{erb_open}#{ruby_code} #{erb_close}"
- html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
+ full_erb_tag = "#{erb_open} #{ruby_code.strip} #{erb_close}"
end
+
+ tag_stack_pop('%erb%', ruby_code) if %i[close reopen].include? block_type
+ html << (erb_pre_match.match?(/\s+\z/) ? indented(full_erb_tag) : full_erb_tag)
+ tag_stack_push('%erb%', ruby_code) if %i[reopen open].include? block_type
else
p ERB_REST: erb_scanner.rest if @debug
rest = erb_scanner.rest.to_s
diff --git a/test/erb/test_formatter.rb b/test/erb/test_formatter.rb
index 008f335..c6ac544 100644
--- a/test/erb/test_formatter.rb
+++ b/test/erb/test_formatter.rb
@@ -146,12 +146,16 @@ def test_tagnames_with_dashes
def test_format_ruby
assert_equal(
- "
\n" \
- " <%= render MyComponent.new(\n" \
- " foo: barbarbarbarbarbarbarbar,\n" \
- " bar: bazbazbazbazbazbazbazbaz,\n" \
- " ) %>\n" \
- "
\n",
+ <<~ERB,
+
+ <%=
+ render MyComponent.new(
+ foo: barbarbarbarbarbarbarbar,
+ bar: bazbazbazbazbazbazbazbaz,
+ )
+ %>
+
+ ERB
ERB::Formatter.format(" <%=render MyComponent.new(foo:barbarbarbarbarbarbarbar,bar:bazbazbazbazbazbazbazbaz)%>
"),
)
end
diff --git a/test/fixtures/comments-2.html.erb b/test/fixtures/comments-2.html.erb
index 54c6d14..bf133c8 100644
--- a/test/fixtures/comments-2.html.erb
+++ b/test/fixtures/comments-2.html.erb
@@ -4,5 +4,5 @@
<%-# link_to 'Approve', some_path, class: 'something', disabled: disabled %>
<%# if smth %>
-<%#else %>
+<%# else %>
<%# end %>
\ No newline at end of file
diff --git a/test/fixtures/comments-2.html.expected.erb b/test/fixtures/comments-2.html.expected.erb
index 441c788..84f0ce5 100644
--- a/test/fixtures/comments-2.html.expected.erb
+++ b/test/fixtures/comments-2.html.expected.erb
@@ -4,5 +4,5 @@
<%-# link_to 'Approve', some_path, class: 'something', disabled: disabled %>
<%# if smth %>
-<%#else %>
+<%# else %>
<%# end %>
diff --git a/test/fixtures/comments.html.erb b/test/fixtures/comments.html.erb
index 3036cd6..4173190 100644
--- a/test/fixtures/comments.html.erb
+++ b/test/fixtures/comments.html.erb
@@ -72,4 +72,4 @@ This fails
<%#
hey
-%>
\ No newline at end of file
+%>
diff --git a/test/fixtures/comments.html.expected.erb b/test/fixtures/comments.html.expected.erb
index a4b7c62..a0b914d 100644
--- a/test/fixtures/comments.html.expected.erb
+++ b/test/fixtures/comments.html.expected.erb
@@ -1,64 +1,74 @@
<%#
-This fails
-hey
-hey
-hey
-hey %>
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
<%#
-This fails
-hey
-hey
-hey
-hey %>
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
<%#
-This fails
-hey
-hey
-hey
-hey %>
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
-<%# This fails
-This fails
-hey
-hey
-hey
-hey %>
-
-<%# This fails
-This fails
-hey
-hey
-hey
-hey %>
-
-<%# This fails
-This fails
-hey
-hey
-hey
-hey %>
-
-<%#This fails
-This fails
-hey
-hey
-hey
-hey %>
+<%#
+ This fails
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
-<%# This fails
-This fails
-hey
-hey
-hey
-hey %>
+<%#
+ This fails
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
<%#
-hey %>
+ This fails
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
<%#
-hey %>
+ This fails
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
<%#
-hey %>
+ This fails
+ This fails
+ hey
+ hey
+ hey
+ hey
+%>
+
+<%# hey %>
+
+<%# hey %>
+
+<%# hey %>
diff --git a/test/fixtures/complex_case_when.html.expected.erb b/test/fixtures/complex_case_when.html.expected.erb
index 5a0e743..70f9a7e 100644
--- a/test/fixtures/complex_case_when.html.expected.erb
+++ b/test/fixtures/complex_case_when.html.expected.erb
@@ -14,11 +14,13 @@
<% when 'Foo::PaymentMethod' %>
<%= t(".payment.stripe_invoice") %>
<% else %>
- <% Rails.logger.error.report(
- StandardError.new(
- "No human readable name found for payment method #{payment_method.class}",
- ),
- ) %>
+ <%
+ Rails.logger.error.report(
+ StandardError.new(
+ "No human readable name found for payment method #{payment_method.class}",
+ ),
+ )
+ %>
<% end %>
<% else %>
<%= t(".payment.no_payment_method_found") %>
diff --git a/test/fixtures/formatted-2.html.expected.erb b/test/fixtures/formatted-2.html.expected.erb
index 169ba7f..56b7f30 100644
--- a/test/fixtures/formatted-2.html.expected.erb
+++ b/test/fixtures/formatted-2.html.expected.erb
@@ -5,12 +5,14 @@
<%= react_component("HelloWorld", { greeting: "Hello from react-rails." }) %>
<%= react_component("HelloWorld", { greeting: "Hello from react-rails." }) %>
- <%= react_component(
- "HelloWorld",
- { greeting: "Hello from react-rails." },
- { greeting: "Hello from react-rails." },
- { greeting: "Hello from react-rails." },
- { greeting: "Hello from react-rails." },
- ) %>
+ <%=
+ react_component(
+ "HelloWorld",
+ { greeting: "Hello from react-rails." },
+ { greeting: "Hello from react-rails." },
+ { greeting: "Hello from react-rails." },
+ { greeting: "Hello from react-rails." },
+ )
+ %>
diff --git a/test/fixtures/formatted.html.expected.erb b/test/fixtures/formatted.html.expected.erb
index 2adfd9c..ced7d10 100644
--- a/test/fixtures/formatted.html.expected.erb
+++ b/test/fixtures/formatted.html.expected.erb
@@ -1,6 +1,8 @@
-<% link_to "Very long string here and there",
-very_very_very_long_long_long_pathhhhhh_here,
-opt: "212",
-options: "222sdasdasd",
-class: " 322 ",
-dis: diss %>
+<%
+ link_to "Very long string here and there",
+ very_very_very_long_long_long_pathhhhhh_here,
+ opt: "212",
+ options: "222sdasdasd",
+ class: " 322 ",
+ dis: diss
+%>
diff --git a/test/fixtures/if_then_else.html.expected.erb b/test/fixtures/if_then_else.html.expected.erb
index f26f63f..5596316 100644
--- a/test/fixtures/if_then_else.html.expected.erb
+++ b/test/fixtures/if_then_else.html.expected.erb
@@ -1,17 +1,21 @@
<% eeee ? "b" : c %>
<% eeee ? a : c %>
-<% if longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
- a
-else
- c
-end %>
+<%
+ if longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
+ a
+ else
+ c
+ end
+%>
-<% if longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
- "a"
-else
- "c"
-end %>
+<%
+ if longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong
+ "a"
+ else
+ "c"
+ end
+%>
>
>
diff --git a/test/fixtures/long_deep_nested-2.html.expected.erb b/test/fixtures/long_deep_nested-2.html.expected.erb
index 216894f..e548c13 100644
--- a/test/fixtures/long_deep_nested-2.html.expected.erb
+++ b/test/fixtures/long_deep_nested-2.html.expected.erb
@@ -16,19 +16,21 @@
- <%= react_component(
- "HelloWorld",
- {
- greeting:
- "Hello from react-rails react-rails react-rails react-rails react-rails react-rails react-rails.",
- },
- {
- greeting:
- "Hello from react-rails react-rails react-rails react-rails react-rails react-rails react-rails.",
- },
- { greeting: "Hello from react-rails." },
- { greeting: "Hello from react-rails." },
- ) %>
+ <%=
+ react_component(
+ "HelloWorld",
+ {
+ greeting:
+ "Hello from react-rails react-rails react-rails react-rails react-rails react-rails react-rails.",
+ },
+ {
+ greeting:
+ "Hello from react-rails react-rails react-rails react-rails react-rails react-rails react-rails.",
+ },
+ { greeting: "Hello from react-rails." },
+ { greeting: "Hello from react-rails." },
+ )
+ %>
diff --git a/test/fixtures/long_deep_nested.html.expected.erb b/test/fixtures/long_deep_nested.html.expected.erb
index 17d219d..8d66b2f 100644
--- a/test/fixtures/long_deep_nested.html.expected.erb
+++ b/test/fixtures/long_deep_nested.html.expected.erb
@@ -16,20 +16,31 @@
- <% link_to "Very long long long long long long long long string here and there",
- very_very_very_long_long_long_pathhhhhh_here,
- opt: "212",
- options: "222sdasdasd",
- class: " 322 ",
- dis: diss %>
+ <%
+ link_to "Very long long long long long long long long string here and there",
+ very_very_very_long_long_long_pathhhhhh_here,
+ opt: "212",
+ options: "222sdasdasd",
+ class: " 322 ",
+ dis: diss
+ %>
- <% link_to "string", path, opt: "212", options: "222sdasdasd" %>
+ <%
+ link_to "string",
+ path,
+ opt: "212",
+ options: "222sdasdasd"
+ %>
- <%= react_component({ greeting: "react-rails." }) %>
+ <%=
+ react_component(
+ { greeting: "react-rails." },
+ )
+ %>
diff --git a/test/fixtures/trailing_comma.html.expected.erb b/test/fixtures/trailing_comma.html.expected.erb
index 206c678..96eb5ac 100644
--- a/test/fixtures/trailing_comma.html.expected.erb
+++ b/test/fixtures/trailing_comma.html.expected.erb
@@ -1,9 +1,11 @@
-<%= render component("ui/table").new(
- id: "option-types-list",
- data: {
- rows: @option_types,
- url: ->(option_type) { edit_admin_option_type_path(option_type) },
- columns: columns,
- batch_actions: batch_actions,
- },
-) %>
+<%=
+ render component("ui/table").new(
+ id: "option-types-list",
+ data: {
+ rows: @option_types,
+ url: ->(option_type) { edit_admin_option_type_path(option_type) },
+ columns: columns,
+ batch_actions: batch_actions,
+ },
+ )
+%>
diff --git a/test/fixtures/utf8.html.expected.erb b/test/fixtures/utf8.html.expected.erb
index 7e387df..c73e5c1 100644
--- a/test/fixtures/utf8.html.expected.erb
+++ b/test/fixtures/utf8.html.expected.erb
@@ -26,16 +26,18 @@
Dein PEND Team
<%= form_with model: model, url: path, method: :patch do |f| %>
- <%= f.select :type,
- [
- ["не выбрано", nil],
- %w[Тип1 type1],
- %w[Тип2 type2],
- %w[Тип3 type3],
- %w[Тип4 type4],
- %w[Тип5 type5],
- ],
- {},
- { class: "select" } %>
+ <%=
+ f.select :type,
+ [
+ ["не выбрано", nil],
+ %w[Тип1 type1],
+ %w[Тип2 type2],
+ %w[Тип3 type3],
+ %w[Тип4 type4],
+ %w[Тип5 type5],
+ ],
+ {},
+ { class: "select" }
+ %>
<%= f.submit "Сохранить" %>
<% end %>