Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ To register plugins, define a file somewhere in your load path named `syntax_tre
* `plugin/single_quotes` - This will change all of your string literals to use single quotes instead of the default double quotes.
* `plugin/trailing_comma` - This will put trailing commas into multiline array literals, hash literals, and method calls that can support trailing commas.
* `plugin/disable_auto_ternary` - This will prevent the automatic conversion of `if ... else` to ternary expressions.
* `plugin/compact_empty_hash` - This will prevent the splitting of empty hashes `{}` over multiple lines.

If you're using Syntax Tree as a library, you can require those files directly or manually pass those options to the formatter initializer through the `SyntaxTree::Formatter::Options` class.

Expand Down
16 changes: 16 additions & 0 deletions lib/syntax_tree/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ class Options
attr_reader :quote,
:trailing_comma,
:disable_auto_ternary,
:compact_empty_hash,
:target_ruby_version

def initialize(
quote: :default,
trailing_comma: :default,
disable_auto_ternary: :default,
compact_empty_hash: :default,
target_ruby_version: :default
)
@quote =
Expand Down Expand Up @@ -65,6 +67,17 @@ def initialize(
disable_auto_ternary
end

@compact_empty_hash =
if compact_empty_hash == :default
# We ship with a compact empty hash plugin that will define this
# constant. That constant is responsible for determining the default
# compact empty hash value. If it's defined, then we default to true.
# Otherwise we default to false.
defined?(COMPACT_EMPTY_HASH)
else
compact_empty_hash
end

@target_ruby_version =
if target_ruby_version == :default
# The default target Ruby version is the current version of Ruby.
Expand All @@ -87,10 +100,12 @@ def initialize(
attr_reader :quote,
:trailing_comma,
:disable_auto_ternary,
:compact_empty_hash,
:target_ruby_version

alias trailing_comma? trailing_comma
alias disable_auto_ternary? disable_auto_ternary
alias compact_empty_hash? compact_empty_hash

def initialize(source, *args, options: Options.new)
super(*args)
Expand All @@ -102,6 +117,7 @@ def initialize(source, *args, options: Options.new)
@quote = options.quote
@trailing_comma = options.trailing_comma
@disable_auto_ternary = options.disable_auto_ternary
@compact_empty_hash = options.compact_empty_hash
@target_ruby_version = options.target_ruby_version
end

Expand Down
2 changes: 1 addition & 1 deletion lib/syntax_tree/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5766,7 +5766,7 @@ def format_contents(q)
q.format(lbrace)

if assocs.empty?
q.breakable_empty
q.breakable_empty unless q.compact_empty_hash?
else
q.indent do
q.breakable_space
Expand Down
7 changes: 7 additions & 0 deletions lib/syntax_tree/plugin/compact_empty_hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module SyntaxTree
class Formatter
COMPACT_EMPTY_HASH = true
end
end
79 changes: 79 additions & 0 deletions test/plugin/compact_empty_hash_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

require_relative "../test_helper"

module SyntaxTree
class CompactEmptyHashTest < Minitest::Test
def test_empty_hash
assert_format("{}\n", "{}")
end

def test_empty_hash_with_spaces
assert_format("{}\n", "{ }")
end

def test_empty_hash_with_newlines
assert_format("{}\n", "{\n}")
end

def test_empty_hash_in_assignment
assert_format("x = {}\n", "x = {}")
end

def test_empty_hash_in_method_call
assert_format("method({})\n", "method({})")
end

def test_empty_hash_in_array
assert_format("[{}]\n", "[{}]")
end

def test_long_assignment_with_empty_hash
source = "this_is_a_very_long_variable_name_that_might_cause_line_breaks_when_assigned_an_empty_hash = {}"
expected = "this_is_a_very_long_variable_name_that_might_cause_line_breaks_when_assigned_an_empty_hash = {}\n"
assert_format(expected, source)
end

def test_empty_hash_values_in_multiline_hash
source = "{ very_long_key_name_that_might_cause_issues: {}, another_very_long_key_name: {}, yet_another_key: {} }"
expected = <<~RUBY
{
very_long_key_name_that_might_cause_issues: {},
another_very_long_key_name: {},
yet_another_key: {}
}
RUBY
assert_format(expected, source)
end

def test_non_empty_hash_still_works
source = "{ key: value }"
expected = "{ key: value }\n"
assert_format(expected, source)
end

def test_without_plugin_allows_multiline_empty_hash
source = "this_is_a_very_long_variable_name_that_might_cause_line_breaks_when_assigned_an_empty_hash = {}"

# Format without the compact_empty_hash option
options = Formatter::Options.new(compact_empty_hash: false)
formatter = Formatter.new(source, [], options: options)
SyntaxTree.parse(source).format(formatter)
formatter.flush
result = formatter.output.join

# Should allow the hash to break across lines
assert(result.include?("= {\n}"), "Expected empty hash to break across lines when plugin is disabled")
end

private

def assert_format(expected, source = expected.chomp)
options = Formatter::Options.new(compact_empty_hash: true)
formatter = Formatter.new(source, [], options: options)
SyntaxTree.parse(source).format(formatter)
formatter.flush
assert_equal(expected, formatter.output.join)
end
end
end