From 04c395f375f1d3a1416362b3ee499b891fed8f36 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 3 Dec 2021 09:53:35 -0500 Subject: [PATCH 01/67] Force break if block attached to command or command call --- CHANGELOG.md | 4 ++ exe/stree | 31 +++-------- lib/syntax_tree.rb | 95 +++++++++++++++++++++----------- test/fixtures/do_block.rb | 4 ++ test/fixtures/top_const_field.rb | 2 +- test/fixtures/top_const_ref.rb | 2 +- test/syntax_tree_test.rb | 5 ++ 7 files changed, 87 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e637bb64..c412cc82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Changed + +- Force a break if a block is attached to a `Command` or `CommandCall` node. + ## [0.1.0] - 2021-11-16 ### Added diff --git a/exe/stree b/exe/stree index 05b09494..34a73e4a 100755 --- a/exe/stree +++ b/exe/stree @@ -58,27 +58,14 @@ mode = exit(1) end -queue = Queue.new -ARGV.each { |pattern| Dir[pattern].each { |filepath| queue << filepath } } - -if queue.size <= 1 - filepath = queue.shift - mode.run(filepath) if File.file?(filepath) - return -end - -count = [8, queue.size].min -threads = - count.times.map do - Thread.new do - loop do - filepath = queue.shift - break if filepath == :exit - - mode.run(filepath) if File.file?(filepath) - end +ARGV.each do |pattern| + Dir.glob(pattern).each do |filepath| + begin + mode.run(filepath) if File.file?(filepath) + rescue => error + warn("!!! Failed on #{filepath}") + warn(error.message) + warn(error.backtrace) end end - -count.times { queue << :exit } -threads.each(&:join) +end diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 9c90b0d5..291a2da4 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7,7 +7,7 @@ require_relative "syntax_tree/version" -# If PrettyPrint::Assign isn't defined, then we haven't gotten the updated +# If PrettyPrint::Align isn't defined, then we haven't gotten the updated # version of prettyprint. In that case we'll define our own. This is going to # overwrite a bunch of methods, so silencing them as well. unless PrettyPrint.const_defined?(:Align) @@ -248,6 +248,12 @@ def initialize(source, *) last_index += line.size end + + # Make sure line counts is filled out with the first and last line at + # minimum so that it has something to compare against if the parser is in a + # lineno=2 state for an empty file. + @line_counts << SingleByteString.new(0) if @line_counts.empty? + @line_counts << SingleByteString.new(last_index) end def self.parse(source) @@ -2695,44 +2701,58 @@ def initialize(node, block_open, statements) end def format(q) - q.group do - q.text(" ") + receiver = q.parent.call - q.if_break do - q.format(BlockOpenFormatter.new("do", block_open)) + # If the parent node is a command node, then there are no parentheses + # around the arguments to that command, so we need to break the block. + if receiver.is_a?(Command) || receiver.is_a?(CommandCall) + q.break_parent + format_break(q) + return + end - if node.block_var - q.text(" ") - q.format(node.block_var) - end + q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } } + end - unless statements.empty? - q.indent do - q.breakable - q.format(statements) - end - end + private + def format_break(q) + q.text(" ") + q.format(BlockOpenFormatter.new("do", block_open)) + + if node.block_var + q.text(" ") + q.format(node.block_var) + end + + unless statements.empty? + q.indent do q.breakable - q.text("end") - end.if_flat do - q.format(BlockOpenFormatter.new("{", block_open)) + q.format(statements) + end + end - if node.block_var - q.breakable - q.format(node.block_var) - q.breakable - end + q.breakable + q.text("end") + end - unless statements.empty? - q.breakable unless node.block_var - q.format(statements) - q.breakable - end + def format_flat(q) + q.text(" ") + q.format(BlockOpenFormatter.new("{", block_open)) - q.text("}") - end + if node.block_var + q.breakable + q.format(node.block_var) + q.breakable + end + + unless statements.empty? + q.breakable unless node.block_var + q.format(statements) + q.breakable end + + q.text("}") end end @@ -2854,8 +2874,13 @@ def format(q) if arguments.parts.length == 1 && arguments.parts.first.is_a?(Paren) q.format(arguments) else - q.text(" ") - q.nest(keyword.length + 1) { q.format(arguments) } + q.if_break { q.text("(") } + q.indent do + q.breakable(" ") + q.format(arguments) + end + q.breakable("") + q.if_break { q.text(")") } end end end @@ -5009,6 +5034,7 @@ def on_else(statements) node = tokens[index] ending = node.value == "end" ? tokens.delete_at(index) : node + # ending = node statements.bind(beginning.location.end_char, ending.location.start_char) @@ -11258,6 +11284,11 @@ def child_nodes [constant] end + def format(q) + q.text("::") + q.format(constant) + end + def pretty_print(q) q.group(2, "(", ")") do q.text("top_const_field") diff --git a/test/fixtures/do_block.rb b/test/fixtures/do_block.rb index 60de0e12..016f27b2 100644 --- a/test/fixtures/do_block.rb +++ b/test/fixtures/do_block.rb @@ -10,3 +10,7 @@ % foo do # comment end +% +foo :bar do + baz +end diff --git a/test/fixtures/top_const_field.rb b/test/fixtures/top_const_field.rb index c00ad9a9..5e3985a2 100644 --- a/test/fixtures/top_const_field.rb +++ b/test/fixtures/top_const_field.rb @@ -1,2 +1,2 @@ % -::Foo::Bar = baz +::Foo = baz diff --git a/test/fixtures/top_const_ref.rb b/test/fixtures/top_const_ref.rb index bbc38580..b8989a59 100644 --- a/test/fixtures/top_const_ref.rb +++ b/test/fixtures/top_const_ref.rb @@ -1,2 +1,2 @@ % -::Foo::Bar +::Foo diff --git a/test/syntax_tree_test.rb b/test/syntax_tree_test.rb index ea706066..99f77cc6 100644 --- a/test/syntax_tree_test.rb +++ b/test/syntax_tree_test.rb @@ -16,6 +16,11 @@ class SyntaxTreeTest < Minitest::Test # Tests for behavior # -------------------------------------------------------------------------- + def test_empty + void_stmt = SyntaxTree.parse("").statements.body.first + assert_kind_of(VoidStmt, void_stmt) + end + def test_multibyte assign = SyntaxTree.parse("🎉 + 🎉").statements.body.first assert_equal(5, assign.location.end_char) From 29826bf2037183746f9bb6a109e323814cb29b6b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 09:46:45 -0500 Subject: [PATCH 02/67] The ability to "check" formatting by formatting the output of the first format. --- CHANGELOG.md | 4 ++++ exe/stree | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c412cc82..fc4abf4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- The ability to "check" formatting by formatting the output of the first format. + ### Changed - Force a break if a block is attached to a `Command` or `CommandCall` node. diff --git a/exe/stree b/exe/stree index 34a73e4a..b9daca65 100755 --- a/exe/stree +++ b/exe/stree @@ -6,7 +6,7 @@ require_relative File.expand_path("../lib/syntax_tree", __dir__) help = <<~EOF stree MDOE FILE - MODE: one of "a", "ast", "d", "doc", "f", "format", "w", or "write" + MODE: one of "a", "ast", "c", "check", "d", "doc", "f", "format", "w", or "write" FILE: one or more paths to files to parse EOF @@ -22,6 +22,13 @@ module SyntaxTree::CLI end end + class Check + def run(filepath) + formatted = SyntaxTree.format(File.read(filepath)) + raise if formatted != SyntaxTree.format(formatted) + end + end + class Doc def run(filepath) formatter = SyntaxTree::Formatter.new([]) @@ -47,6 +54,8 @@ mode = case ARGV.shift when "a", "ast" SyntaxTree::CLI::AST.new + when "c", "check" + SyntaxTree::CLI::Check.new when "d", "doc" SyntaxTree::CLI::Doc.new when "f", "format" @@ -58,6 +67,8 @@ mode = exit(1) end +errored = false + ARGV.each do |pattern| Dir.glob(pattern).each do |filepath| begin @@ -66,6 +77,9 @@ ARGV.each do |pattern| warn("!!! Failed on #{filepath}") warn(error.message) warn(error.backtrace) + errored = true end end end + +exit(errored ? 1 : 0) From 6870b052fa0930f47689b1af828d69f82fe20b17 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 09:58:32 -0500 Subject: [PATCH 03/67] Do not indent `CommandCall` arguments if they do not fit aligned. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc4abf4f..f1e47091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Changed - Force a break if a block is attached to a `Command` or `CommandCall` node. +- Don't indent `CommandCall` arguments if they don't fit aligned. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 291a2da4..db813d81 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3546,8 +3546,8 @@ def format(q) q.text(" ") width = doc_width(doc) - if width > (q.maxwidth / 2) || width < 2 - q.indent { q.format(arguments) } + if width > (q.maxwidth / 2) + q.format(arguments) else q.nest(width) { q.format(arguments) } end From 26da9a556fe64d5a60a39b6a126a5d1bacab3dff Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 10:10:28 -0500 Subject: [PATCH 04/67] Align command blocks --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e47091..a104a416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added - The ability to "check" formatting by formatting the output of the first format. +- `Command` node siblings to `DoBlock` and `BraceBlock` are now aligned. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index db813d81..c03d1eac 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7780,7 +7780,9 @@ def child_nodes def format(q) q.format(call) - q.format(block) + + width = call.is_a?(Command) ? call.message.value.length + 1 : 0 + q.nest(width) { q.format(block) } end def pretty_print(q) From 8a4d6b9800b2c8e6c98522b7ceba7bcbf3087bf8 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 10:20:04 -0500 Subject: [PATCH 05/67] Force a space in calls if there are comments on the receiver --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a104a416..7b4145d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Force a break if a block is attached to a `Command` or `CommandCall` node. - Don't indent `CommandCall` arguments if they don't fit aligned. +- Force a break in `Call` nodes if there are comments on the receiver. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index c03d1eac..068b1a0d 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3010,6 +3010,7 @@ def format(q) q.format(receiver) q.group do q.indent do + q.breakable(force: true) if receiver.comments.any? q.format(CallOperatorFormatter.new(operator)) q.format(message) if message != :call end From c37b6d74ff9d9a6c1357286f68a89fdc4c57df9c Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 10:22:48 -0500 Subject: [PATCH 06/67] Revert change to command nodes --- CHANGELOG.md | 1 - lib/syntax_tree.rb | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b4145d9..6852b779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added - The ability to "check" formatting by formatting the output of the first format. -- `Command` node siblings to `DoBlock` and `BraceBlock` are now aligned. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 068b1a0d..b8e0fbab 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7781,9 +7781,7 @@ def child_nodes def format(q) q.format(call) - - width = call.is_a?(Command) ? call.message.value.length + 1 : 0 - q.nest(width) { q.format(block) } + q.format(block) end def pretty_print(q) From 75a4afd97e68e7a166735a8d88cf285fdecf6bd9 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 11:36:26 -0500 Subject: [PATCH 07/67] Fix up tests --- test/fixtures/break.rb | 4 ++++ test/fixtures/next.rb | 4 ++++ test/fixtures/return.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/test/fixtures/break.rb b/test/fixtures/break.rb index 9193a1cd..4eebc07f 100644 --- a/test/fixtures/break.rb +++ b/test/fixtures/break.rb @@ -8,6 +8,10 @@ break(foo) % break fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +- +break( + fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +) % break(fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo) - diff --git a/test/fixtures/next.rb b/test/fixtures/next.rb index 13947746..ad5bac36 100644 --- a/test/fixtures/next.rb +++ b/test/fixtures/next.rb @@ -8,6 +8,10 @@ next(foo) % next fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +- +next( + fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +) % next(fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo) - diff --git a/test/fixtures/return.rb b/test/fixtures/return.rb index 390e5e2f..e1989cb4 100644 --- a/test/fixtures/return.rb +++ b/test/fixtures/return.rb @@ -8,6 +8,10 @@ return(foo) % return fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +- +return( + fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo +) % return(fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo) - From 704a0862ba1a139be4ae35b6b755821998735dda Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:22:18 -0500 Subject: [PATCH 08/67] Do not change block bounds inside a Command or CommandCall node --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 50 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6852b779..a0fa9637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Force a break if a block is attached to a `Command` or `CommandCall` node. - Don't indent `CommandCall` arguments if they don't fit aligned. - Force a break in `Call` nodes if there are comments on the receiver. +- Do not change block bounds if inside of a `Command` or `CommandCall` node. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index b8e0fbab..4c4e0e9d 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2691,34 +2691,50 @@ def format(q) # [LBrace | Keyword] the node that opens the block attr_reader :block_open + # [String] the string that closes the block + attr_reader :block_close + # [BodyStmt | Statements] the statements inside the block attr_reader :statements - def initialize(node, block_open, statements) + def initialize(node, block_open, block_close, statements) @node = node @block_open = block_open + @block_close = block_close @statements = statements end def format(q) receiver = q.parent.call + # If this is nested anywhere inside of a Command or CommandCall node, then + # we can't change which operators we're using for the bounds of the block. + break_opening, break_closing, flat_opening, flat_closing = + if q.parents.any? { |node| node.is_a?(Command) || node.is_a?(CommandCall) } + [block_open.value, block_close, block_open.value, block_close] + else + ["do", "end", "{", "}"] + end + # If the parent node is a command node, then there are no parentheses # around the arguments to that command, so we need to break the block. if receiver.is_a?(Command) || receiver.is_a?(CommandCall) q.break_parent - format_break(q) + format_break(q, break_opening, break_closing) return end - q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } } + q.group do + q.if_break { format_break(q, break_opening, break_closing) } + .if_flat { format_flat(q, flat_opening, flat_closing) } + end end private - def format_break(q) + def format_break(q, opening, closing) q.text(" ") - q.format(BlockOpenFormatter.new("do", block_open)) + q.format(BlockOpenFormatter.new(opening, block_open)) if node.block_var q.text(" ") @@ -2733,12 +2749,12 @@ def format_break(q) end q.breakable - q.text("end") + q.text(closing) end - def format_flat(q) + def format_flat(q, opening, closing) q.text(" ") - q.format(BlockOpenFormatter.new("{", block_open)) + q.format(BlockOpenFormatter.new(opening, block_open)) if node.block_var q.breakable @@ -2752,7 +2768,7 @@ def format_flat(q) q.breakable end - q.text("}") + q.text(closing) end end @@ -2790,7 +2806,7 @@ def child_nodes end def format(q) - BlockFormatter.new(self, lbrace, statements).format(q) + BlockFormatter.new(self, lbrace, "}", statements).format(q) end def pretty_print(q) @@ -3541,12 +3557,16 @@ def child_nodes def format(q) q.group do - doc = q.format(receiver) - q.format(CallOperatorFormatter.new(operator)) - q.format(message) - q.text(" ") + doc = + q.nest(0) do + q.format(receiver) + q.format(CallOperatorFormatter.new(operator)) + q.format(message) + q.text(" ") + end width = doc_width(doc) + if width > (q.maxwidth / 2) q.format(arguments) else @@ -4538,7 +4558,7 @@ def child_nodes end def format(q) - BlockFormatter.new(self, keyword, bodystmt).format(q) + BlockFormatter.new(self, keyword, "end", bodystmt).format(q) end def pretty_print(q) From 3b657407401f786856abb074646a0ebe616a3667 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:27:54 -0500 Subject: [PATCH 09/67] Handle empty parentheses inside method calls. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fa9637..820cc9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Don't indent `CommandCall` arguments if they don't fit aligned. - Force a break in `Call` nodes if there are comments on the receiver. - Do not change block bounds if inside of a `Command` or `CommandCall` node. +- Handle empty parentheses inside method calls. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 4c4e0e9d..5db66085 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -8763,7 +8763,7 @@ class Paren # [LParen] the left parenthesis that opened this statement attr_reader :lparen - # [untyped] the expression inside the parentheses + # [nil | untyped] the expression inside the parentheses attr_reader :contents # [Location] the location of this node @@ -8787,7 +8787,7 @@ def format(q) q.group do q.format(lparen) - if !contents.is_a?(Params) || !contents.empty? + if contents && (!contents.is_a?(Params) || !contents.empty?) q.indent do q.breakable("") q.format(contents) @@ -8852,7 +8852,7 @@ def on_paren(contents) Paren.new( lparen: lparen, - contents: contents, + contents: contents || nil, location: lparen.location.to(rparen.location) ) end From 2caae3a271ca702039d1d4535a376d2b24a164fe Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:31:08 -0500 Subject: [PATCH 10/67] Skip indentation for special array literals on assignment nodes. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820cc9e3..76bf4b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Force a break in `Call` nodes if there are comments on the receiver. - Do not change block bounds if inside of a `Command` or `CommandCall` node. - Handle empty parentheses inside method calls. +- Skip indentation for special array literals on assignment nodes. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 5db66085..0746ad82 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1684,7 +1684,7 @@ def format(q) q.format(target) q.text(" =") - if skip_indent? + if skip_indent_target? || skip_indent_value? q.text(" ") q.format(value) else @@ -1722,11 +1722,21 @@ def to_json(*opts) private - def skip_indent? - target.is_a?(ARefField) || value.is_a?(ArrayLiteral) || - value.is_a?(HashLiteral) || - value.is_a?(Heredoc) || - value.is_a?(Lambda) + def skip_indent_target? + target.is_a?(ARefField) + end + + def skip_indent_value? + [ + ArrayLiteral, + HashLiteral, + Heredoc, + Lambda, + QSymbols, + QWords, + Symbols, + Words + ].any? { |type| value.is_a?(type) } end end From 3f6f97225162241699d5fcceb778a857aca45ea9 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:36:45 -0500 Subject: [PATCH 11/67] Ensure a final breakable is inserted when converting an `ArrayLiteral` to a `QSymbols`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76bf4b94..1f65ab56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Do not change block bounds if inside of a `Command` or `CommandCall` node. - Handle empty parentheses inside method calls. - Skip indentation for special array literals on assignment nodes. +- Ensure a final breakable is inserted when converting an `ArrayLiteral` to a `QSymbols`. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 0746ad82..e9666b52 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1379,6 +1379,7 @@ def format(q) q.format(part.value) end end + q.breakable("") end end end From 892339020e1aabf2f34349b47388c2946b5e0101 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:49:03 -0500 Subject: [PATCH 12/67] Fix up the `doc_width` calculation for `CommandCall` nodes. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f65ab56..7fda8c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Handle empty parentheses inside method calls. - Skip indentation for special array literals on assignment nodes. - Ensure a final breakable is inserted when converting an `ArrayLiteral` to a `QSymbols`. +- Fix up the `doc_width` calculation for `CommandCall` nodes. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index e9666b52..93bf22df 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3634,11 +3634,11 @@ def doc_width(parent) when PrettyPrint::Text width += doc.width when PrettyPrint::Indent, PrettyPrint::Align, PrettyPrint::Group - queue += doc.contents.reverse + queue = doc.contents + queue when PrettyPrint::IfBreak - queue += doc.flat_contents.reverse + queue = doc.break_contents + queue when PrettyPrint::Breakable - width = doc.force? ? 0 : width + doc.width + width = 0 end end From d41ac4fde9a332063f86cd0a255b626940d6979a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:51:33 -0500 Subject: [PATCH 13/67] Ensure parameters inside a lambda literal when there are no parentheses are grouped. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fda8c62..fd6365f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Skip indentation for special array literals on assignment nodes. - Ensure a final breakable is inserted when converting an `ArrayLiteral` to a `QSymbols`. - Fix up the `doc_width` calculation for `CommandCall` nodes. +- Ensure parameters inside a lambda literal when there are no parentheses are grouped. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 93bf22df..01b75019 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7388,9 +7388,11 @@ def format(q) if params.is_a?(Paren) q.format(params) unless params.contents.empty? elsif !params.empty? - q.text("(") - q.format(params) - q.text(")") + q.group do + q.text("(") + q.format(params) + q.text(")") + end end q.text(" ") From 0ddcedb2dc3de6d5a919d8c12c90042e2062bb9b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 13:57:38 -0500 Subject: [PATCH 14/67] Ensure when converting an `ArrayLiteral` to a `QWords` that the strings do not contain `[`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd6365f2..e27b80f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure a final breakable is inserted when converting an `ArrayLiteral` to a `QSymbols`. - Fix up the `doc_width` calculation for `CommandCall` nodes. - Ensure parameters inside a lambda literal when there are no parentheses are grouped. +- Ensure when converting an `ArrayLiteral` to a `QWords` that the strings do not contain `[`. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 01b75019..42197079 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1453,7 +1453,7 @@ def qwords? part.is_a?(StringLiteral) && part.comments.empty? && part.parts.length == 1 && part.parts.first.is_a?(TStringContent) && - !part.parts.first.value.match?(/[\s\\\]]/) + !part.parts.first.value.match?(/[\s\[\]\\]/) end end From e06158f9097644fb6f47081d35f358a71cc970a7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 14:05:14 -0500 Subject: [PATCH 15/67] Stop looking for parent `Command` or `CommandCall` nodes in blocks once you hit `Statements`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27b80f4..c10e624f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Fix up the `doc_width` calculation for `CommandCall` nodes. - Ensure parameters inside a lambda literal when there are no parentheses are grouped. - Ensure when converting an `ArrayLiteral` to a `QWords` that the strings do not contain `[`. +- Stop looking for parent `Command` or `CommandCall` nodes in blocks once you hit `Statements`. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 42197079..35430665 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2718,10 +2718,22 @@ def initialize(node, block_open, block_close, statements) def format(q) receiver = q.parent.call + # If this is nested anywhere inside of a Command or CommandCall node, then + # we can't change which operators we're using for the bounds of the block. + found_command = + q.parents.any? do |parent| + # If we hit a statements, then we're safe to use whatever since we + # know for certain we're going to get split over multiple lines + # anyway. + break false if parent.is_a?(Statements) + + parent.is_a?(Command) || parent.is_a?(CommandCall) + end + # If this is nested anywhere inside of a Command or CommandCall node, then # we can't change which operators we're using for the bounds of the block. break_opening, break_closing, flat_opening, flat_closing = - if q.parents.any? { |node| node.is_a?(Command) || node.is_a?(CommandCall) } + if found_command [block_open.value, block_close, block_open.value, block_close] else ["do", "end", "{", "}"] From 5530cdf0ebf911164fa8444066de89383bb1972a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 14:22:29 -0500 Subject: [PATCH 16/67] Ensure nested `Lambda` nodes get their correct bounds. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 4 ++-- test/fixtures/lambda.rb | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c10e624f..f627e878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure parameters inside a lambda literal when there are no parentheses are grouped. - Ensure when converting an `ArrayLiteral` to a `QWords` that the strings do not contain `[`. - Stop looking for parent `Command` or `CommandCall` nodes in blocks once you hit `Statements`. +- Ensure nested `Lambda` nodes get their correct bounds. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 35430665..2c4e23be 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7463,8 +7463,8 @@ def to_json(*opts) def on_lambda(params, statements) beginning = find_token(TLambda) - if token = find_token(TLamBeg, consume: false) - opening = tokens.delete(token) + if tokens.any? { |token| token.is_a?(TLamBeg) && token.location.start_char > beginning.location.start_char } + opening = find_token(TLamBeg) closing = find_token(RBrace) else opening = find_token(Kw, "do") diff --git a/test/fixtures/lambda.rb b/test/fixtures/lambda.rb index 601c6d69..043ceb5a 100644 --- a/test/fixtures/lambda.rb +++ b/test/fixtures/lambda.rb @@ -36,3 +36,7 @@ - command.call foo, ->(bar) { barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr } +% +-> { -> foo do bar end.baz }.qux +- +-> { ->(foo) { bar }.baz }.qux From c9cae13e9a553f30bfe9cd25cf974b352daa54a7 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 14:36:52 -0500 Subject: [PATCH 17/67] Ensure we do not change block bounds within control flow constructs. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 47 ++++++++++++++++++++++++++---------------- test/fixtures/break.rb | 2 ++ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f627e878..6f3cbce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure when converting an `ArrayLiteral` to a `QWords` that the strings do not contain `[`. - Stop looking for parent `Command` or `CommandCall` nodes in blocks once you hit `Statements`. - Ensure nested `Lambda` nodes get their correct bounds. +- Ensure we do not change block bounds within control flow constructs. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 2c4e23be..2dfc5052 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2716,31 +2716,21 @@ def initialize(node, block_open, block_close, statements) end def format(q) - receiver = q.parent.call - - # If this is nested anywhere inside of a Command or CommandCall node, then - # we can't change which operators we're using for the bounds of the block. - found_command = - q.parents.any? do |parent| - # If we hit a statements, then we're safe to use whatever since we - # know for certain we're going to get split over multiple lines - # anyway. - break false if parent.is_a?(Statements) - - parent.is_a?(Command) || parent.is_a?(CommandCall) - end - # If this is nested anywhere inside of a Command or CommandCall node, then # we can't change which operators we're using for the bounds of the block. break_opening, break_closing, flat_opening, flat_closing = - if found_command + if unchangeable_bounds?(q) [block_open.value, block_close, block_open.value, block_close] + elsif forced_do_end_bounds?(q) + ["do", "end", "do", "end"] else ["do", "end", "{", "}"] end - # If the parent node is a command node, then there are no parentheses - # around the arguments to that command, so we need to break the block. + # If the receiver of this block a Command or CommandCall node, then there + # are no parentheses around the arguments to that command, so we need to + # break the block. + receiver = q.parent.call if receiver.is_a?(Command) || receiver.is_a?(CommandCall) q.break_parent format_break(q, break_opening, break_closing) @@ -2755,6 +2745,25 @@ def format(q) private + # If this is nested anywhere inside certain nodes, then we can't change + # which operators/keywords we're using for the bounds of the block. + def unchangeable_bounds?(q) + q.parents.any? do |parent| + # If we hit a statements, then we're safe to use whatever since we + # know for certain we're going to get split over multiple lines + # anyway. + break false if parent.is_a?(Statements) + + [Command, CommandCall].include?(parent.class) + end + end + + # If we're a sibling of a control-flow keyword, then we're going to have to + # use the do..end bounds. + def forced_do_end_bounds?(q) + [Break, Next, Return, Super].include?(q.parent.call.class) + end + def format_break(q, opening, closing) q.text(" ") q.format(BlockOpenFormatter.new(opening, block_open)) @@ -2785,7 +2794,9 @@ def format_flat(q, opening, closing) q.breakable end - unless statements.empty? + if statements.empty? + q.text(" ") if opening == "do" + else q.breakable unless node.block_var q.format(statements) q.breakable diff --git a/test/fixtures/break.rb b/test/fixtures/break.rb index 4eebc07f..a77c6b35 100644 --- a/test/fixtures/break.rb +++ b/test/fixtures/break.rb @@ -25,3 +25,5 @@ foo bar ) +% +break foo.bar :baz do |qux| qux end From c2dc3c9b5513957a20a066ee85cdae6433b6df75 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 15:36:07 -0500 Subject: [PATCH 18/67] The `AccessCtrl` node in favor of just formatting correctly when you hit a `Statements` node. --- CHANGELOG.md | 4 +++ lib/syntax_tree.rb | 71 ++++++---------------------------------------- 2 files changed, 12 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f3cbce3..580f6d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure nested `Lambda` nodes get their correct bounds. - Ensure we do not change block bounds within control flow constructs. +### Removed + +- The `AccessCtrl` node in favor of just formatting correctly when you hit a `Statements` node. + ## [0.1.0] - 2021-11-16 ### Added diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 2dfc5052..3f5edd33 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -10468,6 +10468,11 @@ def format(q) return end + access_controls = + Hash.new do |hash, node| + hash[node] = node.is_a?(VCall) && %w[private protected public].include?(node.value.value) + end + body.each_with_index do |statement, index| next if statement.is_a?(VoidStmt) @@ -10477,7 +10482,7 @@ def format(q) q.breakable(force: true) q.breakable(force: true) q.format(statement) - elsif statement.is_a?(AccessCtrl) || body[index - 1].is_a?(AccessCtrl) + elsif access_controls[statement] || access_controls[body[index - 1]] q.breakable(force: true) q.breakable(force: true) q.format(statement) @@ -12379,56 +12384,6 @@ def on_var_ref(value) VarRef.new(value: value, location: value.location) end - # AccessCtrl represents a call to a method visibility control, i.e., +public+, - # +protected+, or +private+. - # - # private - # - class AccessCtrl - # [Ident] the value of this expression - attr_reader :value - - # [Location] the location of this node - attr_reader :location - - # [Array[ Comment | EmbDoc ]] the comments attached to this node - attr_reader :comments - - def initialize(value:, location:, comments: []) - @value = value - @location = location - @comments = comments - end - - def child_nodes - [value] - end - - def format(q) - q.format(value) - end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("access_ctrl") - - q.breakable - q.pp(value) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :access_ctrl, - value: value, - loc: location, - cmts: comments - }.to_json(*opts) - end - end - # VCall represent any plain named object with Ruby that could be either a # local variable or a method call. # @@ -12477,19 +12432,9 @@ def to_json(*opts) end # :call-seq: - # on_vcall: (Ident ident) -> AccessCtrl | VCall + # on_vcall: (Ident ident) -> VCall def on_vcall(ident) - @controls ||= %w[private protected public].freeze - - if @controls.include?(ident.value) && ident.value == lines[lineno - 1].strip - # Access controls like private, protected, and public are reported as - # vcall nodes since they're technically method calls. We want to be able - # add new lines around them as necessary, so here we're going to - # explicitly track those as a different node type. - AccessCtrl.new(value: ident, location: ident.location) - else - VCall.new(value: ident, location: ident.location) - end + VCall.new(value: ident, location: ident.location) end # VoidStmt represents an empty lexical block of code. From 5476ef2a05a372f059cad36f63fd873f4cc0dc7f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 18:45:26 -0500 Subject: [PATCH 19/67] Ensure parentheses are added around keywords changing to their modifier forms. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 69 ++++++++++++++++++++++++++++++++++------ test/fixtures/if.rb | 4 +++ test/fixtures/unless.rb | 4 +++ test/fixtures/until.rb | 4 +++ test/fixtures/while.rb | 4 +++ test/syntax_tree_test.rb | 4 --- 7 files changed, 77 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580f6d57..bc2d7ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Stop looking for parent `Command` or `CommandCall` nodes in blocks once you hit `Statements`. - Ensure nested `Lambda` nodes get their correct bounds. - Ensure we do not change block bounds within control flow constructs. +- Ensure parentheses are added around keywords changing to their modifier forms. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 3f5edd33..ee9467e4 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -6509,6 +6509,51 @@ def on_ident(value) ) end + # If you have a modifier statement (for instance a modifier if statement or a + # modifier while loop) there are times when you need to wrap the entire + # statement in parentheses. This occurs when you have something like: + # + # foo[:foo] = + # if bar? + # baz + # end + # + # Normally we would shorten this to an inline version, which would result in: + # + # foo[:foo] = baz if bar? + # + # but this actually has different semantic meaning. The first example will + # result in a nil being inserted into the hash for the :foo key, whereas the + # second example will result in an empty hash because the if statement applies + # to the entire assignment. + # + # We can fix this in a couple of ways. We can use the then keyword, as in: + # + # foo[:foo] = if bar? then baz end + # + # But this isn't used very often. We can also just leave it as is with the + # multi-line version, but for a short predicate and short value it looks + # verbose. The last option and the one used here is to add parentheses on + # both sides of the expression, as in: + # + # foo[:foo] = (baz if bar?) + # + # This approach maintains the nice conciseness of the inline version, while + # keeping the correct semantic meaning. + module ModifierParentheses + def self.call(q) + parens = [Args, Assign, Assoc, Binary, Call, Defined, MAssign, OpAssign] + + if parens.include?(q.parent.class) + q.text("(") + yield + q.text(")") + else + yield + end + end + end + # Formats an If or Unless node. class ConditionalFormatter # [String] the keyword associated with this conditional @@ -6549,9 +6594,11 @@ def format(q) else q.group do q.if_break { break_format.call(force: false) }.if_flat do - q.format(node.statements) - q.text(" #{keyword} ") - q.format(node.predicate) + ModifierParentheses.call(q) do + q.format(node.statements) + q.text(" #{keyword} ") + q.format(node.predicate) + end end end end @@ -6784,9 +6831,11 @@ def format(q) q.breakable q.text("end") end.if_flat do - q.format(node.statement) - q.text(" #{keyword} ") - q.format(node.predicate) + ModifierParentheses.call(q) do + q.format(node.statement) + q.text(" #{keyword} ") + q.format(node.predicate) + end end end end @@ -12018,9 +12067,11 @@ def format(q) q.breakable("") q.text("end") end.if_flat do - q.format(statements) - q.text(" #{keyword} ") - q.format(node.predicate) + ModifierParentheses.call(q) do + q.format(statements) + q.text(" #{keyword} ") + q.format(node.predicate) + end end end end diff --git a/test/fixtures/if.rb b/test/fixtures/if.rb index 2e013a2c..d8c8bfb2 100644 --- a/test/fixtures/if.rb +++ b/test/fixtures/if.rb @@ -16,3 +16,7 @@ bar else end +% +foo = if bar then baz end +- +foo = (baz if bar) diff --git a/test/fixtures/unless.rb b/test/fixtures/unless.rb index 3041f849..f21ffdf2 100644 --- a/test/fixtures/unless.rb +++ b/test/fixtures/unless.rb @@ -16,3 +16,7 @@ bar else end +% +foo = unless bar then baz end +- +foo = (baz unless bar) diff --git a/test/fixtures/until.rb b/test/fixtures/until.rb index 0daa09ac..2b0352a9 100644 --- a/test/fixtures/until.rb +++ b/test/fixtures/until.rb @@ -11,3 +11,7 @@ until fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo bar end +% +foo = until bar do baz end +- +foo = (baz until bar) diff --git a/test/fixtures/while.rb b/test/fixtures/while.rb index d8a79f89..39eb1dfd 100644 --- a/test/fixtures/while.rb +++ b/test/fixtures/while.rb @@ -11,3 +11,7 @@ while fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo bar end +% +foo = while bar do baz end +- +foo = (baz while bar) diff --git a/test/syntax_tree_test.rb b/test/syntax_tree_test.rb index 99f77cc6..ae40f425 100644 --- a/test/syntax_tree_test.rb +++ b/test/syntax_tree_test.rb @@ -934,10 +934,6 @@ def test_var_ref assert_node(VarRef, "var_ref", "true") end - def test_access_ctrl - assert_node(AccessCtrl, "access_ctrl", "private") - end - def test_vcall assert_node(VCall, "vcall", "variable") end From 642736ae5e074e53a5b6a9dbbefb75f3ab9ed5d2 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:03:03 -0500 Subject: [PATCH 20/67] Allow conditionals to take modifier form if they are using the `then` keyword with a `VoidStmt`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 61 +++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc2d7ffb..4af90ffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure nested `Lambda` nodes get their correct bounds. - Ensure we do not change block bounds within control flow constructs. - Ensure parentheses are added around keywords changing to their modifier forms. +- Allow conditionals to take modifier form if they are using the `then` keyword with a `VoidStmt`. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index ee9467e4..3b1b730c 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -6568,32 +6568,11 @@ def initialize(keyword, node) end def format(q) - statements = node.statements - break_format = ->(force:) do - q.text("#{keyword} ") - q.nest(keyword.length + 1) { q.format(node.predicate) } - - unless statements.empty? - q.indent do - q.breakable(force: force) - q.format(statements) - end - end - - if node.consequent - q.breakable(force: force) - q.format(node.consequent) - end - - q.breakable(force: force) - q.text("end") - end - - if node.consequent || statements.empty? - q.group { break_format.call(force: true) } + if node.consequent || node.statements.empty? + q.group { format_break(q, force: true) } else q.group do - q.if_break { break_format.call(force: false) }.if_flat do + q.if_break { format_break(q, force: false) }.if_flat do ModifierParentheses.call(q) do q.format(node.statements) q.text(" #{keyword} ") @@ -6603,6 +6582,28 @@ def format(q) end end end + + private + + def format_break(q, force:) + q.text("#{keyword} ") + q.nest(keyword.length + 1) { q.format(node.predicate) } + + unless node.statements.empty? + q.indent do + q.breakable(force: force) + q.format(node.statements) + end + end + + if node.consequent + q.breakable(force: force) + q.format(node.consequent) + end + + q.breakable(force: force) + q.text("end") + end end # If represents the first clause in an +if+ chain. @@ -10511,10 +10512,14 @@ def format(q) # the only value is a comment. In that case a lot of nodes like # brace_block will attempt to format as a single line, but since that # wouldn't work with a comment, we intentionally break the parent group. - if body.length == 2 && body.first.is_a?(VoidStmt) - q.format(body.last) - q.break_parent - return + if body.length == 2 + void_stmt, comment = body + + if void_stmt.is_a?(VoidStmt) && comment.is_a?(Comment) + q.format(comment) + q.break_parent + return + end end access_controls = From 041b37d93c982f3b503a515b0fb8cd728b5baa35 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:07:19 -0500 Subject: [PATCH 21/67] Comments can now be attached to the `case` keyword. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 18 +++++++++++++++--- test/fixtures/case.rb | 5 +++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af90ffb..6d6f7bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added - The ability to "check" formatting by formatting the output of the first format. +- Comments can now be attached to the `case` keyword. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 3b1b730c..03c636f4 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3133,6 +3133,9 @@ def on_call(receiver, operator, message) # end # class Case + # [Kw] the keyword that opens this expression + attr_reader :keyword + # [nil | untyped] optional value being switched on attr_reader :value @@ -3145,7 +3148,8 @@ class Case # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(value:, consequent:, location:, comments: []) + def initialize(keyword:, value:, consequent:, location:, comments: []) + @keyword = keyword @value = value @consequent = consequent @location = location @@ -3153,11 +3157,13 @@ def initialize(value:, consequent:, location:, comments: []) end def child_nodes - [value, consequent] + [keyword, value, consequent] end def format(q) - q.group(0, "case", "end") do + q.group do + q.format(keyword) + if value q.text(" ") q.format(value) @@ -3166,6 +3172,8 @@ def format(q) q.breakable(force: true) q.format(consequent) q.breakable(force: true) + + q.text("end") end end @@ -3173,6 +3181,9 @@ def pretty_print(q) q.group(2, "(", ")") do q.text("case") + q.breakable + q.pp(keyword) + if value q.breakable q.pp(value) @@ -3280,6 +3291,7 @@ def on_case(value, consequent) tokens.delete(keyword) Case.new( + keyword: keyword, value: value, consequent: consequent, location: keyword.location.to(consequent.location) diff --git a/test/fixtures/case.rb b/test/fixtures/case.rb index 72415407..57dfb594 100644 --- a/test/fixtures/case.rb +++ b/test/fixtures/case.rb @@ -8,3 +8,8 @@ when bar baz end +% +case # comment +when foo + bar +end From c3ffb5d98691a1e0ba7b31a0c9bf05df830b3461 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:25:03 -0500 Subject: [PATCH 22/67] `UntilMod` and `WhileMod` nodes that wrap a `Begin` should be forced into their modifier forms. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6f7bec..cdd0f6fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure we do not change block bounds within control flow constructs. - Ensure parentheses are added around keywords changing to their modifier forms. - Allow conditionals to take modifier form if they are using the `then` keyword with a `VoidStmt`. +- `UntilMod` and `WhileMod` nodes that wrap a `Begin` should be forced into their modifier forms. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 03c636f4..3a559f75 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -12216,7 +12216,22 @@ def child_nodes end def format(q) - LoopFormatter.new("until", self, statement).format(q) + # If we're in the modifier form and we're modifying a `begin`, then this + # is a special case where we need to explicitly use the modifier form + # because otherwise the semantic meaning changes. This looks like: + # + # begin + # foo + # end until bar + # + # The above is effectively a `do...until` loop. + if statement.is_a?(Begin) + q.format(statement) + q.text(" until ") + q.format(predicate) + else + LoopFormatter.new("until", self, statement).format(q) + end end def pretty_print(q) @@ -12785,7 +12800,22 @@ def child_nodes end def format(q) - LoopFormatter.new("while", self, statement).format(q) + # If we're in the modifier form and we're modifying a `begin`, then this + # is a special case where we need to explicitly use the modifier form + # because otherwise the semantic meaning changes. This looks like: + # + # begin + # foo + # end while bar + # + # The above is effectively a `do...while` loop. + if statement.is_a?(Begin) + q.format(statement) + q.text(" while ") + q.format(predicate) + else + LoopFormatter.new("while", self, statement).format(q) + end end def pretty_print(q) From d4c2f956128ea11527ce53977c99d7a2b7c24856 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:31:05 -0500 Subject: [PATCH 23/67] Ensure `For` loops keep their trailing commas. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 15 ++++++++------- test/fixtures/for.rb | 4 ++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd0f6fc..b27254c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure parentheses are added around keywords changing to their modifier forms. - Allow conditionals to take modifier form if they are using the `then` keyword with a `VoidStmt`. - `UntilMod` and `WhileMod` nodes that wrap a `Begin` should be forced into their modifier forms. +- Ensure `For` loops keep their trailing commas. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 3a559f75..b200eb47 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -5957,6 +5957,7 @@ def to_json(*opts) # ) -> For def on_for(index, collection, statements) beginning = find_token(Kw, "for") + in_keyword = find_token(Kw, "in") ending = find_token(Kw, "end") # Consume the do keyword if it exists so that it doesn't get confused for @@ -5972,6 +5973,11 @@ def on_for(index, collection, statements) ending.location.start_char ) + if index.is_a?(MLHS) + comma_range = index.location.end_char...in_keyword.location.start_char + index.comma = true if source[comma_range].strip.start_with?(",") + end + For.new( index: index, collection: collection, @@ -7736,11 +7742,7 @@ def child_nodes def format(q) q.group do - q.group do - q.format(target) - q.text(",") if target.is_a?(MLHS) && target.comma - end - + q.group { q.format(target) } q.text(" =") q.indent do q.breakable @@ -7954,7 +7956,6 @@ class MLHS # list, which impacts destructuring. It's an attr_accessor so that while # the syntax tree is being built it can be set by its parent node attr_accessor :comma - alias comma? comma # [Location] the location of this node attr_reader :location @@ -7975,6 +7976,7 @@ def child_nodes def format(q) q.seplist(parts) { |part| q.format(part) } + q.text(",") if comma end def pretty_print(q) @@ -8077,7 +8079,6 @@ def format(q) q.indent do q.breakable("") q.format(contents) - q.text(",") if contents.is_a?(MLHS) && contents.comma? end q.breakable("") diff --git a/test/fixtures/for.rb b/test/fixtures/for.rb index c1a848e6..62b207ee 100644 --- a/test/fixtures/for.rb +++ b/test/fixtures/for.rb @@ -34,3 +34,7 @@ bar end end +% +for foo, in [[foo, bar]] + foo +end From 875de00069194b81293f921821a74eaeb9fb7f6b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:40:57 -0500 Subject: [PATCH 24/67] Replicate content for `__END__` keyword exactly. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27254c6..9bd98490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow conditionals to take modifier form if they are using the `then` keyword with a `VoidStmt`. - `UntilMod` and `WhileMod` nodes that wrap a `Begin` should be forced into their modifier forms. - Ensure `For` loops keep their trailing commas. +- Replicate content for `__END__` keyword exactly. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index b200eb47..e0a6908e 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -612,7 +612,7 @@ def on_END(statements) # some other content that is not executed by the program # class EndContent - # [String] the content after the script + # [Array[ String ]] the content after the script attr_reader :value # [Location] the location of this node @@ -634,7 +634,10 @@ def child_nodes def format(q) q.text("__END__") q.breakable(force: true) - q.text(value) + + q.seplist(value, -> { q.breakable(literal: true, force: true) }) do |line| + q.text(line) + end end def pretty_print(q) @@ -642,16 +645,15 @@ def pretty_print(q) q.text("__end__") q.breakable - q.pp(value) + q.pp(value.join("\n")) q.pp(Comment::List.new(comments)) end end def to_json(*opts) - { type: :__end__, value: value, loc: location, cmts: comments }.to_json( - *opts - ) + { type: :__end__, value: value.join("\n"), loc: location, cmts: comments } + .to_json(*opts) end end @@ -660,7 +662,7 @@ def to_json(*opts) def on___end__(value) @__end__ = EndContent.new( - value: lines[lineno..-1].join("\n"), + value: lines[lineno..-1], location: Location.token(line: lineno, char: char_pos, size: value.size) ) end @@ -9042,7 +9044,11 @@ def child_nodes def format(q) q.format(statements) - q.breakable(force: true) + + # We're going to put a newline on the end so that it always has one unless + # it ends with the special __END__ syntax. In that case we want to + # replicate the text exactly so we will just let it be. + q.breakable(force: true) unless statements.body.last.is_a?(EndContent) end def pretty_print(q) From 919bda94706d6a6ef6b0aa1b412b2ebd7bfd25c9 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 19:55:06 -0500 Subject: [PATCH 25/67] Remove escaped forward slashes from regular expression literals when converting to `%r`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 33 +++++++++++++++++++++++---------- test/fixtures/regexp_literal.rb | 4 ++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd98490..51db4b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - The ability to "check" formatting by formatting the output of the first format. - Comments can now be attached to the `case` keyword. +- Remove escaped forward slashes from regular expression literals when converting to `%r`. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index e0a6908e..45689311 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -612,7 +612,7 @@ def on_END(statements) # some other content that is not executed by the program # class EndContent - # [Array[ String ]] the content after the script + # [String] the content after the script attr_reader :value # [Location] the location of this node @@ -635,9 +635,8 @@ def format(q) q.text("__END__") q.breakable(force: true) - q.seplist(value, -> { q.breakable(literal: true, force: true) }) do |line| - q.text(line) - end + separator = -> { q.breakable(indent: false, force: true) } + q.seplist(value.split(/\r?\n/, -1), separator) { |line| q.text(line) } end def pretty_print(q) @@ -645,15 +644,14 @@ def pretty_print(q) q.text("__end__") q.breakable - q.pp(value.join("\n")) + q.pp(value) q.pp(Comment::List.new(comments)) end end def to_json(*opts) - { type: :__end__, value: value.join("\n"), loc: location, cmts: comments } - .to_json(*opts) + { type: :__end__, value: value, loc: location, cmts: comments }.to_json(*opts) end end @@ -662,7 +660,7 @@ def to_json(*opts) def on___end__(value) @__end__ = EndContent.new( - value: lines[lineno..-1], + value: source[(char_pos + value.length)..-1], location: Location.token(line: lineno, char: char_pos, size: value.size) ) end @@ -9702,11 +9700,26 @@ def format(q) q.format_each(parts) q.text(ending) end + elsif braces + q.group do + q.text("%r{") + + parts.each do |part| + if part.is_a?(TStringContent) + q.text(part.value.gsub("\\/", "/")) + else + q.format(part) + end + end + + q.text("}") + q.text(ending[1..-1]) + end else q.group do - q.text(braces ? "%r{" : "/") + q.text("/") q.format_each(parts) - q.text(braces ? "}" : "/") + q.text("/") q.text(ending[1..-1]) end end diff --git a/test/fixtures/regexp_literal.rb b/test/fixtures/regexp_literal.rb index 8ae0a03d..0569426d 100644 --- a/test/fixtures/regexp_literal.rb +++ b/test/fixtures/regexp_literal.rb @@ -49,3 +49,7 @@ foo %r{= bar} % foo(/ bar/) +% +/foo\/bar/ +- +%r{foo/bar} From adda1a369cd7ad5d297a0dfdee9f912cf4576b17 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Mon, 6 Dec 2021 20:01:32 -0500 Subject: [PATCH 26/67] Keep block `If`, `Unless`, `While`, and `Until` forms if there is an assignment in the predicate. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 43 +++++++++++++++++++++++++++++++---------- test/fixtures/if.rb | 4 ++++ test/fixtures/unless.rb | 4 ++++ test/fixtures/until.rb | 4 ++++ test/fixtures/while.rb | 4 ++++ 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51db4b5d..1657eeb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - `UntilMod` and `WhileMod` nodes that wrap a `Begin` should be forced into their modifier forms. - Ensure `For` loops keep their trailing commas. - Replicate content for `__END__` keyword exactly. +- Keep block `If`, `Unless`, `While`, and `Until` forms if there is an assignment in the predicate. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 45689311..b2eb32b0 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -6586,6 +6586,15 @@ def initialize(keyword, node) end def format(q) + # If the predicate of the conditional contains an assignment (in which + # case we can't know for certain that that assignment doesn't impact the + # statements inside the conditional) then we can't use the modifier form + # and we must use the block form. + if [Assign, MAssign, OpAssign].include?(node.predicate.class) + format_break(q, force: true) + return + end + if node.consequent || node.statements.empty? q.group { format_break(q, force: true) } else @@ -12093,17 +12102,18 @@ def initialize(keyword, node, statements) end def format(q) + # If the predicate of the loop contains an assignment (in which case we + # can't know for certain that that assignment doesn't impact the + # statements inside the loop) then we can't use the modifier form and we + # must use the block form. + if [Assign, MAssign, OpAssign].include?(node.predicate.class) + format_break(q) + q.break_parent + return + end + q.group do - q.if_break do - q.text("#{keyword} ") - q.nest(keyword.length + 1) { q.format(node.predicate) } - q.indent do - q.breakable("") - q.format(statements) - end - q.breakable("") - q.text("end") - end.if_flat do + q.if_break { format_break(q) }.if_flat do ModifierParentheses.call(q) do q.format(statements) q.text(" #{keyword} ") @@ -12112,6 +12122,19 @@ def format(q) end end end + + private + + def format_break(q) + q.text("#{keyword} ") + q.nest(keyword.length + 1) { q.format(node.predicate) } + q.indent do + q.breakable("") + q.format(statements) + end + q.breakable("") + q.text("end") + end end # Until represents an +until+ loop. diff --git a/test/fixtures/if.rb b/test/fixtures/if.rb index d8c8bfb2..38c67e52 100644 --- a/test/fixtures/if.rb +++ b/test/fixtures/if.rb @@ -20,3 +20,7 @@ foo = if bar then baz end - foo = (baz if bar) +% +if foo += 1 + foo +end diff --git a/test/fixtures/unless.rb b/test/fixtures/unless.rb index f21ffdf2..f5b01c3f 100644 --- a/test/fixtures/unless.rb +++ b/test/fixtures/unless.rb @@ -20,3 +20,7 @@ foo = unless bar then baz end - foo = (baz unless bar) +% +unless foo += 1 + foo +end diff --git a/test/fixtures/until.rb b/test/fixtures/until.rb index 2b0352a9..4eb2c8cb 100644 --- a/test/fixtures/until.rb +++ b/test/fixtures/until.rb @@ -15,3 +15,7 @@ foo = until bar do baz end - foo = (baz until bar) +% +until foo += 1 + foo +end diff --git a/test/fixtures/while.rb b/test/fixtures/while.rb index 39eb1dfd..3dcf839d 100644 --- a/test/fixtures/while.rb +++ b/test/fixtures/while.rb @@ -15,3 +15,7 @@ foo = while bar do baz end - foo = (baz while bar) +% +while foo += 1 + foo +end From 7312e80ca7f50c8f14ae600780ee716896456d78 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 09:56:23 -0500 Subject: [PATCH 27/67] Force using braces if the block is within the predicate of a conditional or loop. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1657eeb6..f0bb643e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure `For` loops keep their trailing commas. - Replicate content for `__END__` keyword exactly. - Keep block `If`, `Unless`, `While`, and `Until` forms if there is an assignment in the predicate. +- Force using braces if the block is within the predicate of a conditional or loop. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index b2eb32b0..6ef80ada 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2723,6 +2723,8 @@ def format(q) [block_open.value, block_close, block_open.value, block_close] elsif forced_do_end_bounds?(q) ["do", "end", "do", "end"] + elsif forced_brace_bounds?(q) + ["{", "}", "{", "}"] else ["do", "end", "{", "}"] end @@ -2764,6 +2766,13 @@ def forced_do_end_bounds?(q) [Break, Next, Return, Super].include?(q.parent.call.class) end + # If we're the predicate of a loop or conditional, then we're going to have + # to go with the {..} bounds. + def forced_brace_bounds?(q) + parent = q.parents.to_a[1] + [If, IfMod, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(parent.class) && node == parent.predicate.block + end + def format_break(q, opening, closing) q.text(" ") q.format(BlockOpenFormatter.new(opening, block_open)) From 8c5a72ae6f17188a92e40bc04fdb9ba327bd81e0 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 10:24:27 -0500 Subject: [PATCH 28/67] Allow for the possibility that `CommandCall` nodes might not have arguments. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0bb643e..4d75fbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Replicate content for `__END__` keyword exactly. - Keep block `If`, `Unless`, `While`, and `Until` forms if there is an assignment in the predicate. - Force using braces if the block is within the predicate of a conditional or loop. +- Allow for the possibility that `CommandCall` nodes might not have arguments. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 6ef80ada..365b214e 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3581,7 +3581,7 @@ class CommandCall # [Const | Ident | Op] the message being send attr_reader :message - # [Args] the arguments going along with the message + # [nil | Args] the arguments going along with the message attr_reader :arguments # [Location] the location of this node @@ -3617,15 +3617,17 @@ def format(q) q.format(receiver) q.format(CallOperatorFormatter.new(operator)) q.format(message) - q.text(" ") end - width = doc_width(doc) + if arguments + width = doc_width(doc) + 1 + q.text(" ") - if width > (q.maxwidth / 2) - q.format(arguments) - else - q.nest(width) { q.format(arguments) } + if width > (q.maxwidth / 2) + q.format(arguments) + else + q.nest(width) { q.format(arguments) } + end end end end @@ -3643,8 +3645,10 @@ def pretty_print(q) q.breakable q.pp(message) - q.breakable - q.pp(arguments) + if arguments + q.breakable + q.pp(arguments) + end q.pp(Comment::List.new(comments)) end @@ -3695,7 +3699,7 @@ def doc_width(parent) # untyped receiver, # (:"::" | Op | Period) operator, # (Const | Ident | Op) message, - # Args arguments + # (nil | Args) arguments # ) -> CommandCall def on_command_call(receiver, operator, message, arguments) ending = arguments || message From 29d675f0559f43aefa504f22752939c8ae0a9686 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 10:33:31 -0500 Subject: [PATCH 29/67] Explicitly handle `?"` so that it formats properly. --- CHANGELOG.md | 2 ++ lib/syntax_tree.rb | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d75fbf5..8eb49e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Keep block `If`, `Unless`, `While`, and `Until` forms if there is an assignment in the predicate. - Force using braces if the block is within the predicate of a conditional or loop. - Allow for the possibility that `CommandCall` nodes might not have arguments. +- Explicitly handle `?"` so that it formats properly. +- Check that a block is within the predicate in a more relaxed way. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 365b214e..53f8ff30 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -482,7 +482,7 @@ def format(q) q.text(value) else q.text(q.quote) - q.text(value[1]) + q.text(value[1] == "\"" ? "\\\"" : value[1]) q.text(q.quote) end end @@ -2769,8 +2769,8 @@ def forced_do_end_bounds?(q) # If we're the predicate of a loop or conditional, then we're going to have # to go with the {..} bounds. def forced_brace_bounds?(q) - parent = q.parents.to_a[1] - [If, IfMod, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(parent.class) && node == parent.predicate.block + parent, grandparent, = q.parents.to_a + [If, IfMod, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(grandparent.class) && parent == grandparent.predicate end def format_break(q, opening, closing) From d9810d5c64d81358ebc300ff632eaf4a327c6dff Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 10:53:20 -0500 Subject: [PATCH 30/67] Ensure the `Return` breaks with brackets and not parentheses. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb49e92..4fed0b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow for the possibility that `CommandCall` nodes might not have arguments. - Explicitly handle `?"` so that it formats properly. - Check that a block is within the predicate in a more relaxed way. +- Ensure the `Return` breaks with brackets and not parentheses. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 53f8ff30..224772ba 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2930,20 +2930,35 @@ def format(q) q.text(keyword) if arguments.parts.any? - if arguments.parts.length == 1 && arguments.parts.first.is_a?(Paren) - q.format(arguments) - else - q.if_break { q.text("(") } - q.indent do - q.breakable(" ") + if arguments.parts.length == 1 + part = arguments.parts.first + + if part.is_a?(Paren) + q.format(arguments) + elsif part.is_a?(ArrayLiteral) + q.text(" ") q.format(arguments) + else + format_arguments(q, "(", ")") end - q.breakable("") - q.if_break { q.text(")") } + else + format_arguments(q, " [", "]") end end end end + + private + + def format_arguments(q, opening, closing) + q.if_break { q.text(opening) } + q.indent do + q.breakable(" ") + q.format(node.arguments) + end + q.breakable("") + q.if_break { q.text(closing) } + end end # Break represents using the +break+ keyword. From b63b1b3969a4f76cb3d8eec0bb67d8bdc1ef8802 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:02:00 -0500 Subject: [PATCH 31/67] Ensure trailing comments on parameter declarations are consistent. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fed0b67..148db94b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Explicitly handle `?"` so that it formats properly. - Check that a block is within the predicate in a more relaxed way. - Ensure the `Return` breaks with brackets and not parentheses. +- Ensure trailing comments on parameter declarations are consistent. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 224772ba..bf53d54c 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -8754,10 +8754,12 @@ def format(q) parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest parts << block if block - q.nest(0) do + contents = -> { q.seplist(parts) { |part| q.format(part) } q.format(rest) if rest && rest.is_a?(ExcessedComma) - end + } + + q.parent.is_a?(Paren) ? q.nest(0, &contents) : q.group(&contents) end def pretty_print(q) From 391bf1453ca3109e41adf404afa2be2cb75ac707 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:10:58 -0500 Subject: [PATCH 32/67] Make `Command` and `CommandCall` aware that their arguments could exceed their normal expected bounds because of heredocs. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 148db94b..7d163022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Check that a block is within the predicate in a more relaxed way. - Ensure the `Return` breaks with brackets and not parentheses. - Ensure trailing comments on parameter declarations are consistent. +- Make `Command` and `CommandCall` aware that their arguments could exceed their normal expected bounds because of heredocs. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index bf53d54c..b5e813f6 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3577,7 +3577,13 @@ def on_command(message, arguments) Command.new( message: message, arguments: arguments, - location: message.location.to(arguments.location) + location: + Location.new( + start_line: message.location.start_line, + start_char: message.location.start_char, + end_line: [message.location.end_line, arguments.location.end_line].max, + end_char: arguments.location.end_char + ) ) end @@ -3724,7 +3730,13 @@ def on_command_call(receiver, operator, message, arguments) operator: operator, message: message, arguments: arguments, - location: receiver.location.to(ending.location) + location: + Location.new( + start_line: receiver.location.start_line, + start_char: receiver.location.start_char, + end_line: [receiver.location.end_line, ending.location.end_line].max, + end_char: ending.location.end_char + ) ) end From 48e3866adc18bb0753645d6346b5234343bd4dcf Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:14:45 -0500 Subject: [PATCH 33/67] Only unescape forward slashes in regular expressions if converting from slash bounds to `%r` bounds. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d163022..aa4f31af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure the `Return` breaks with brackets and not parentheses. - Ensure trailing comments on parameter declarations are consistent. - Make `Command` and `CommandCall` aware that their arguments could exceed their normal expected bounds because of heredocs. +- Only unescape forward slashes in regular expressions if converting from slash bounds to `%r` bounds. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index b5e813f6..57aed082 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -9755,12 +9755,18 @@ def format(q) q.group do q.text("%r{") - parts.each do |part| - if part.is_a?(TStringContent) - q.text(part.value.gsub("\\/", "/")) - else - q.format(part) + if beginning == "/" + # If we're changing from a forward slash to a %r{, then we can + # replace any escaped forward slashes with regular forward slashes. + parts.each do |part| + if part.is_a?(TStringContent) + q.text(part.value.gsub("\\/", "/")) + else + q.format(part) + end end + else + q.format_each(parts) end q.text("}") From c8fe73bf58504d2edd463558e2035bc6de583b39 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:20:03 -0500 Subject: [PATCH 34/67] Allow `When` nodes to grab trailing comments away from their statements lists. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4f31af..3e8b4c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure trailing comments on parameter declarations are consistent. - Make `Command` and `CommandCall` aware that their arguments could exceed their normal expected bounds because of heredocs. - Only unescape forward slashes in regular expressions if converting from slash bounds to `%r` bounds. +- Allow `When` nodes to grab trailing comments away from their statements lists. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 57aed082..5cbd0ca7 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -12759,7 +12759,10 @@ def on_when(arguments, statements, consequent) beginning = find_token(Kw, "when") ending = consequent || find_token(Kw, "end") - statements.bind(arguments.location.end_char, ending.location.start_char) + statements.bind( + find_next_statement_start(arguments.location.end_char), + ending.location.start_char + ) When.new( arguments: arguments, From 59510a15b4698e9b629caf46cf8b6e3b7f46950b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:22:15 -0500 Subject: [PATCH 35/67] Allow flip-flop operators to be formatted correctly within `IfMod` and `UnlessMod` nodes. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e8b4c1a..9b6025b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Make `Command` and `CommandCall` aware that their arguments could exceed their normal expected bounds because of heredocs. - Only unescape forward slashes in regular expressions if converting from slash bounds to `%r` bounds. - Allow `When` nodes to grab trailing comments away from their statements lists. +- Allow flip-flop operators to be formatted correctly within `IfMod` and `UnlessMod` nodes. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 5cbd0ca7..0d71b083 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -4708,8 +4708,7 @@ def initialize(operator, node) end def format(q) - parent = q.parent - space = parent.is_a?(If) || parent.is_a?(Unless) + space = [If, IfMod, Unless, UnlessMod].include?(q.parent.class) left = node.left right = node.right From 31633159066ce2665cb37bda80392af4fed01d4a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:30:50 -0500 Subject: [PATCH 36/67] Allow `IfMod` and `UnlessMod` to know about heredocs moving their bounds. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b6025b8..4b3dc8a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Only unescape forward slashes in regular expressions if converting from slash bounds to `%r` bounds. - Allow `When` nodes to grab trailing comments away from their statements lists. - Allow flip-flop operators to be formatted correctly within `IfMod` and `UnlessMod` nodes. +- Allow `IfMod` and `UnlessMod` to know about heredocs moving their bounds. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 0d71b083..6adfc794 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -6973,7 +6973,13 @@ def on_if_mod(predicate, statement) IfMod.new( statement: statement, predicate: predicate, - location: statement.location.to(predicate.location) + location: + Location.new( + start_line: statement.location.start_line, + start_char: statement.location.start_char, + end_line: [statement.location.end_line, predicate.location.end_line].max, + end_char: predicate.location.end_char + ) ) end @@ -12127,7 +12133,13 @@ def on_unless_mod(predicate, statement) UnlessMod.new( statement: statement, predicate: predicate, - location: statement.location.to(predicate.location) + location: + Location.new( + start_line: statement.location.start_line, + start_char: statement.location.start_char, + end_line: [statement.location.end_line, predicate.location.end_line].max, + end_char: predicate.location.end_char + ) ) end From be97d5b2446d1d0d8e747e75433e8e6282860da5 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:37:19 -0500 Subject: [PATCH 37/67] Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3dc8a3..c4fc3af0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - The ability to "check" formatting by formatting the output of the first format. - Comments can now be attached to the `case` keyword. - Remove escaped forward slashes from regular expression literals when converting to `%r`. +- Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 6adfc794..9cf6e627 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1355,7 +1355,11 @@ def format(q) q.indent do q.breakable("") q.seplist(contents.parts, -> { q.breakable }) do |part| - q.format(part.parts.first) + if part.is_a?(StringLiteral) + q.format(part.parts.first) + else + q.text(part.value[1..-1]) + end end end q.breakable("") @@ -1450,10 +1454,17 @@ def to_json(*opts) def qwords? contents && contents.comments.empty? && contents.parts.length > 1 && contents.parts.all? do |part| - part.is_a?(StringLiteral) && part.comments.empty? && - part.parts.length == 1 && - part.parts.first.is_a?(TStringContent) && - !part.parts.first.value.match?(/[\s\[\]\\]/) + case part + when StringLiteral + part.comments.empty? && + part.parts.length == 1 && + part.parts.first.is_a?(TStringContent) && + !part.parts.first.value.match?(/[\s\[\]\\]/) + when CHAR + !part.value.match?(/[\[\]\\]/) + else + false + end end end From 7c31d276461c8b53fef5fd078e526ac1edf39a39 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:40:19 -0500 Subject: [PATCH 38/67] Allow `Hash` opening braces to have trailing comments. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4fc3af0..5a0693bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Comments can now be attached to the `case` keyword. - Remove escaped forward slashes from regular expression literals when converting to `%r`. - Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. +- Allow `Hash` opening braces to have trailing comments. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 9cf6e627..68782652 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -6096,6 +6096,9 @@ def on_gvar(value) # { key => value } # class HashLiteral + # [LBrace] the left brace that opens this hash + attr_reader :lbrace + # [Array[ AssocNew | AssocSplat ]] the optional contents of the hash attr_reader :assocs @@ -6105,19 +6108,20 @@ class HashLiteral # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(assocs:, location:, comments: []) + def initialize(lbrace:, assocs:, location:, comments: []) + @lbrace = lbrace @assocs = assocs @location = location @comments = comments end def child_nodes - assocs + [lbrace] + assocs end def format(q) contents = -> do - q.text("{") + q.format(lbrace) q.indent do q.breakable q.format(HashFormatter.for(self)) @@ -6156,6 +6160,7 @@ def on_hash(assocs) rbrace = find_token(RBrace) HashLiteral.new( + lbrace: lbrace, assocs: assocs || [], location: lbrace.location.to(rbrace.location) ) From 983c0476da8d8cb01fa69c9c324297061ea438f1 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:45:55 -0500 Subject: [PATCH 39/67] Properly handle breaking parameters when there are no parentheses. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 38 ++++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0693bb..128fff47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow `When` nodes to grab trailing comments away from their statements lists. - Allow flip-flop operators to be formatted correctly within `IfMod` and `UnlessMod` nodes. - Allow `IfMod` and `UnlessMod` to know about heredocs moving their bounds. +- Properly handle breaking parameters when there are no parentheses. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 68782652..ea58001e 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -4206,12 +4206,18 @@ def format(q) q.text("def ") q.format(name) - if params.is_a?(Params) && !params.empty? - q.text("(") - q.format(params) - q.text(")") - else + if !params.is_a?(Params) q.format(params) + elsif !params.empty? + q.group do + q.text("(") + q.indent do + q.breakable("") + q.format(params) + end + q.breakable("") + q.text(")") + end end end @@ -4515,12 +4521,18 @@ def format(q) q.format(CallOperatorFormatter.new(operator)) q.format(name) - if params.is_a?(Params) && !params.empty? - q.text("(") - q.format(params) - q.text(")") - else + if !params.is_a?(Params) q.format(params) + elsif !params.empty? + q.group do + q.text("(") + q.indent do + q.breakable("") + q.format(params) + end + q.breakable("") + q.text(")") + end end end @@ -8787,12 +8799,10 @@ def format(q) parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest parts << block if block - contents = -> { + q.nest(0) do q.seplist(parts) { |part| q.format(part) } q.format(rest) if rest && rest.is_a?(ExcessedComma) - } - - q.parent.is_a?(Paren) ? q.nest(0, &contents) : q.group(&contents) + end end def pretty_print(q) From b937a269f060f85f8bcadb645498f25c64b068ed Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 11:51:14 -0500 Subject: [PATCH 40/67] Add parentheses if `Yield` breaks onto multiple lines. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 128fff47..892bebb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Remove escaped forward slashes from regular expression literals when converting to `%r`. - Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. - Allow `Hash` opening braces to have trailing comments. +- Add parentheses if `Yield` breaks onto multiple lines. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index ea58001e..4292cce6 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -13309,8 +13309,18 @@ def child_nodes def format(q) q.group do q.text("yield") - q.text(" ") if arguments.is_a?(Args) - q.format(arguments) + + if arguments.is_a?(Paren) + q.format(arguments) + else + q.if_break { q.text("(") }.if_flat { q.text(" ") } + q.indent do + q.breakable("") + q.format(arguments) + end + q.breakable("") + q.if_break { q.text(")") } + end end end From b8161a2a829c4e018c2e5c70258f3438393dc1c4 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 12:05:15 -0500 Subject: [PATCH 41/67] Ensure all nodes that could have heredocs nested know about their end lines. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 42 ++++++------------------------------------ 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 892bebb0..4226c675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. - Allow `Hash` opening braces to have trailing comments. - Add parentheses if `Yield` breaks onto multiple lines. +- Ensure all nodes that could have heredocs nested know about their end lines. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 4292cce6..18d32a67 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -75,7 +75,7 @@ def to(other) Location.new( start_line: start_line, start_char: start_char, - end_line: other.end_line, + end_line: [end_line, other.end_line].max, end_char: other.end_char ) end @@ -3146,13 +3146,7 @@ def on_call(receiver, operator, message) receiver: receiver, operator: operator, message: message, - location: - Location.new( - start_line: receiver.location.start_line, - start_char: receiver.location.start_char, - end_line: [ending.location.end_line, receiver.location.end_line].max, - end_char: ending.location.end_char - ) + location: receiver.location.to(ending.location) ) end @@ -3588,13 +3582,7 @@ def on_command(message, arguments) Command.new( message: message, arguments: arguments, - location: - Location.new( - start_line: message.location.start_line, - start_char: message.location.start_char, - end_line: [message.location.end_line, arguments.location.end_line].max, - end_char: arguments.location.end_char - ) + location: message.location.to(arguments.location) ) end @@ -3741,13 +3729,7 @@ def on_command_call(receiver, operator, message, arguments) operator: operator, message: message, arguments: arguments, - location: - Location.new( - start_line: receiver.location.start_line, - start_char: receiver.location.start_char, - end_line: [receiver.location.end_line, ending.location.end_line].max, - end_char: ending.location.end_char - ) + location: receiver.location.to(ending.location) ) end @@ -7001,13 +6983,7 @@ def on_if_mod(predicate, statement) IfMod.new( statement: statement, predicate: predicate, - location: - Location.new( - start_line: statement.location.start_line, - start_char: statement.location.start_char, - end_line: [statement.location.end_line, predicate.location.end_line].max, - end_char: predicate.location.end_char - ) + location: statement.location.to(predicate.location) ) end @@ -12159,13 +12135,7 @@ def on_unless_mod(predicate, statement) UnlessMod.new( statement: statement, predicate: predicate, - location: - Location.new( - start_line: statement.location.start_line, - start_char: statement.location.start_char, - end_line: [statement.location.end_line, predicate.location.end_line].max, - end_char: predicate.location.end_char - ) + location: statement.location.to(predicate.location) ) end From 74655bbcb2c587b9e5e6d9db784363d302c41654 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 12:08:05 -0500 Subject: [PATCH 42/67] Ensure comments on assignment after the `=` before the value keep their place. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4226c675..11ccadfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow `Hash` opening braces to have trailing comments. - Add parentheses if `Yield` breaks onto multiple lines. - Ensure all nodes that could have heredocs nested know about their end lines. +- Ensure comments on assignment after the `=` before the value keep their place. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 18d32a67..1059a331 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1696,7 +1696,7 @@ def format(q) q.format(target) q.text(" =") - if skip_indent_target? || skip_indent_value? + if target.comments.empty? && (skip_indent_target? || skip_indent_value?) q.text(" ") q.format(value) else From 93bf48f23954fb7ab9471d80b172edbd4c943314 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 12:30:13 -0500 Subject: [PATCH 43/67] The `MethodAddArg` node is removed in favor of an optional `arguments` field on `Call` and `FCall`. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 128 ++++++++++++++------------------------- test/syntax_tree_test.rb | 9 +-- 3 files changed, 46 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ccadfe..d0c61f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Removed - The `AccessCtrl` node in favor of just formatting correctly when you hit a `Statements` node. +- The `MethodAddArg` node is removed in favor of an optional `arguments` field on `Call` and `FCall`. ## [0.1.0] - 2021-11-16 diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 1059a331..03a044f1 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3056,9 +3056,7 @@ def format(q) end end - # Call represents a method call. This node doesn't contain the arguments being - # passed (if arguments are passed, this node will get nested under a - # MethodAddArg node). + # Call represents a method call. # # receiver.message # @@ -3072,22 +3070,26 @@ class Call # [:call | Backtick | Const | Ident | Op] the message being sent attr_reader :message + # [nil | ArgParen | Args] the arguments to the method call + attr_reader :arguments + # [Location] the location of this node attr_reader :location # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(receiver:, operator:, message:, location:, comments: []) + def initialize(receiver:, operator:, message:, arguments:, location:, comments: []) @receiver = receiver @operator = operator @message = message + @arguments = arguments @location = location @comments = comments end def child_nodes - [receiver, (operator if operator != :"::"), (message if message != :call)] + [receiver, (operator if operator != :"::"), (message if message != :call), arguments] end def format(q) @@ -3098,6 +3100,7 @@ def format(q) q.breakable(force: true) if receiver.comments.any? q.format(CallOperatorFormatter.new(operator)) q.format(message) if message != :call + q.format(arguments) if arguments end end end @@ -3116,6 +3119,11 @@ def pretty_print(q) q.breakable q.pp(message) + if arguments + q.breakable + q.pp(arguments) + end + q.pp(Comment::List.new(comments)) end end @@ -3126,6 +3134,7 @@ def to_json(*opts) receiver: receiver, op: operator, message: message, + args: arguments, loc: location, cmts: comments }.to_json(*opts) @@ -3146,6 +3155,7 @@ def on_call(receiver, operator, message) receiver: receiver, operator: operator, message: message, + arguments: nil, location: receiver.location.to(ending.location) ) end @@ -5617,24 +5627,29 @@ class FCall # [Const | Ident] the name of the method attr_reader :value + # [nil | ArgParen | Args] the arguments to the method call + attr_reader :arguments + # [Location] the location of this node attr_reader :location # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(value:, location:, comments: []) + def initialize(value:, arguments:, location:, comments: []) @value = value + @arguments = arguments @location = location @comments = comments end def child_nodes - [value] + [value, arguments] end def format(q) q.format(value) + q.format(arguments) end def pretty_print(q) @@ -5644,12 +5659,17 @@ def pretty_print(q) q.breakable q.pp(value) + if arguments + q.breakable + q.pp(arguments) + end + q.pp(Comment::List.new(comments)) end end def to_json(*opts) - { type: :fcall, value: value, loc: location, cmts: comments }.to_json( + { type: :fcall, value: value, args: arguments, loc: location, cmts: comments }.to_json( *opts ) end @@ -5658,7 +5678,7 @@ def to_json(*opts) # :call-seq: # on_fcall: ((Const | Ident) value) -> FCall def on_fcall(value) - FCall.new(value: value, location: value.location) + FCall.new(value: value, arguments: nil, location: value.location) end # Field is always the child of an assignment. It represents assigning to a @@ -7847,86 +7867,26 @@ def on_massign(target, value) ) end - # MethodAddArg represents a method call with arguments and parentheses. - # - # method(argument) - # - # MethodAddArg can also represent with a method on an object, as in: - # - # object.method(argument) - # - # Finally, MethodAddArg can represent calling a method with no receiver that - # ends in a ?. In this case, the parser knows it's a method call and not a - # local variable, so it uses a MethodAddArg node as opposed to a VCall node, - # as in: - # - # method? - # - class MethodAddArg - # [Call | FCall] the method call - attr_reader :call - - # [ArgParen | Args] the arguments to the method call - attr_reader :arguments - - # [Location] the location of this node - attr_reader :location - - # [Array[ Comment | EmbDoc ]] the comments attached to this node - attr_reader :comments - - def initialize(call:, arguments:, location:, comments: []) - @call = call - @arguments = arguments - @location = location - @comments = comments - end - - def child_nodes - [call, arguments] - end - - def format(q) - q.format(call) - q.text(" ") if !arguments.is_a?(ArgParen) && arguments.parts.any? - q.format(arguments) - end - - def pretty_print(q) - q.group(2, "(", ")") do - q.text("method_add_arg") - - q.breakable - q.pp(call) - - q.breakable - q.pp(arguments) - - q.pp(Comment::List.new(comments)) - end - end - - def to_json(*opts) - { - type: :method_add_arg, - call: call, - args: arguments, - loc: location, - cmts: comments - }.to_json(*opts) - end - end - # :call-seq: # on_method_add_arg: ( # (Call | FCall) call, # (ArgParen | Args) arguments - # ) -> MethodAddArg + # ) -> Call | FCall def on_method_add_arg(call, arguments) location = call.location - location = location.to(arguments.location) unless arguments.is_a?(Args) + location = location.to(arguments.location) if arguments.is_a?(ArgParen) - MethodAddArg.new(call: call, arguments: arguments, location: location) + if call.is_a?(FCall) + FCall.new(value: call.value, arguments: arguments, location: location) + else + Call.new( + receiver: call.receiver, + operator: call.operator, + message: call.message, + arguments: arguments, + location: location + ) + end end # MethodAddBlock represents a method call with a block argument. @@ -7934,7 +7894,7 @@ def on_method_add_arg(call, arguments) # method {} # class MethodAddBlock - # [Call | Command | CommandCall | FCall | MethodAddArg] the method call + # [Call | Command | CommandCall | FCall] the method call attr_reader :call # [BraceBlock | DoBlock] the block being sent with the method call @@ -7989,7 +7949,7 @@ def to_json(*opts) # :call-seq: # on_method_add_block: ( - # (Call | Command | CommandCall | FCall | MethodAddArg) call, + # (Call | Command | CommandCall | FCall) call, # (BraceBlock | DoBlock) block # ) -> MethodAddBlock def on_method_add_block(call, block) diff --git a/test/syntax_tree_test.rb b/test/syntax_tree_test.rb index ae40f425..3893b25c 100644 --- a/test/syntax_tree_test.rb +++ b/test/syntax_tree_test.rb @@ -464,10 +464,7 @@ def test_excessed_comma end def test_fcall - source = "method(argument)" - - at = location(chars: 0..6) - assert_node(FCall, "fcall", source, at: at, &:call) + assert_node(FCall, "fcall", "method(argument)") end def test_field @@ -634,10 +631,6 @@ def test_massign assert_node(MAssign, "massign", "first, second, third = value") end - def test_method_add_arg - assert_node(MethodAddArg, "method_add_arg", "method(argument)") - end - def test_method_add_block assert_node(MethodAddBlock, "method_add_block", "method {}") end From 06ca0062a3180e935110ae6b68335f310ea77239 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 12:41:06 -0500 Subject: [PATCH 44/67] Properly handle trailing operators in call chains with attached comments. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c61f93..641556a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow flip-flop operators to be formatted correctly within `IfMod` and `UnlessMod` nodes. - Allow `IfMod` and `UnlessMod` to know about heredocs moving their bounds. - Properly handle breaking parameters when there are no parentheses. +- Properly handle trailing operators in call chains with attached comments. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 03a044f1..84f4844f 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -3093,12 +3093,19 @@ def child_nodes end def format(q) + call_operator = CallOperatorFormatter.new(operator) + q.group do q.format(receiver) + + # If there are trailing comments on the call operator, then we need to + # use the trailing form as opposed to the leading form. + q.format(call_operator) if call_operator.comments.any? + q.group do q.indent do - q.breakable(force: true) if receiver.comments.any? - q.format(CallOperatorFormatter.new(operator)) + q.breakable(force: true) if receiver.comments.any? || call_operator.comments.any? + q.format(call_operator) if call_operator.comments.empty? q.format(message) if message != :call q.format(arguments) if arguments end From df5fc3a88220b4e63cb7c0f75a7324eb11539076 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:00:43 -0500 Subject: [PATCH 45/67] Trailing comments on parameters with no parentheses now do not force a break. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 56 ++++++++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 641556a8..c024fbd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Add parentheses if `Yield` breaks onto multiple lines. - Ensure all nodes that could have heredocs nested know about their end lines. - Ensure comments on assignment after the `=` before the value keep their place. +- Trailing comments on parameters with no parentheses now do not force a break. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 84f4844f..bc2bd6bd 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -4204,20 +4204,7 @@ def format(q) q.group do q.text("def ") q.format(name) - - if !params.is_a?(Params) - q.format(params) - elsif !params.empty? - q.group do - q.text("(") - q.indent do - q.breakable("") - q.format(params) - end - q.breakable("") - q.text(")") - end - end + q.format(params) if !params.is_a?(Params) || !params.empty? end unless bodystmt.empty? @@ -4519,20 +4506,7 @@ def format(q) q.format(target) q.format(CallOperatorFormatter.new(operator)) q.format(name) - - if !params.is_a?(Params) - q.format(params) - elsif !params.empty? - q.group do - q.text("(") - q.indent do - q.breakable("") - q.format(params) - end - q.breakable("") - q.text(")") - end - end + q.format(params) if !params.is_a?(Params) || !params.empty? end unless bodystmt.empty? @@ -6143,11 +6117,17 @@ def child_nodes def format(q) contents = -> do q.format(lbrace) - q.indent do + + if assocs.empty? + q.breakable("") + else + q.indent do + q.breakable + q.format(HashFormatter.for(self)) + end q.breakable - q.format(HashFormatter.for(self)) end - q.breakable + q.text("}") end @@ -8742,9 +8722,21 @@ def format(q) parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest parts << block if block - q.nest(0) do + contents = -> { q.seplist(parts) { |part| q.format(part) } q.format(rest) if rest && rest.is_a?(ExcessedComma) + } + + if [Def, Defs].include?(q.parent.class) + q.group(0, "(", ")") do + q.indent do + q.breakable("") + contents.call + end + q.breakable("") + end + else + q.nest(0, &contents) end end From cd2e354dc999379e0121e93a0b6169785bce6cc2 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:06:43 -0500 Subject: [PATCH 46/67] Allow `ArrayLiteral` opening brackets to have trailing comments. --- CHANGELOG.md | 3 ++- lib/syntax_tree.rb | 57 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c024fbd9..e92f393f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Comments can now be attached to the `case` keyword. - Remove escaped forward slashes from regular expression literals when converting to `%r`. - Allow arrays of `CHAR` nodes to be converted to `QWords` under certain conditions. -- Allow `Hash` opening braces to have trailing comments. +- Allow `HashLiteral` opening braces to have trailing comments. - Add parentheses if `Yield` breaks onto multiple lines. - Ensure all nodes that could have heredocs nested know about their end lines. - Ensure comments on assignment after the `=` before the value keep their place. - Trailing comments on parameters with no parentheses now do not force a break. +- Allow `ArrayLiteral` opening brackets to have trailing comments. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index bc2bd6bd..610e7529 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1388,6 +1388,9 @@ def format(q) end end + # [LBracket] the bracket that opens this array + attr_reader :lbracket + # [nil | Args] the contents of the array attr_reader :contents @@ -1397,22 +1400,18 @@ def format(q) # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(contents:, location:, comments: []) + def initialize(lbracket:, contents:, location:, comments: []) + @lbracket = lbracket @contents = contents @location = location @comments = comments end def child_nodes - [contents] + [lbracket, contents] end def format(q) - unless contents - q.text("[]") - return - end - if qwords? QWordsFormatter.new(contents).format(q) return @@ -1423,12 +1422,18 @@ def format(q) return end - q.group(0, "[", "]") do - q.indent do - q.breakable("") - q.format(contents) + q.group do + q.format(lbracket) + + if contents + q.indent do + q.breakable("") + q.format(contents) + end end + q.breakable("") + q.text("]") end end @@ -1452,6 +1457,7 @@ def to_json(*opts) private def qwords? + lbracket.comments.empty? && contents && contents.comments.empty? && contents.parts.length > 1 && contents.parts.all? do |part| case part @@ -1469,6 +1475,7 @@ def qwords? end def qsymbols? + lbracket.comments.empty? && contents && contents.comments.empty? && contents.parts.length > 1 && contents.parts.all? do |part| part.is_a?(SymbolLiteral) && part.comments.empty? @@ -1485,6 +1492,7 @@ def on_array(contents) rbracket = find_token(RBracket) ArrayLiteral.new( + lbracket: lbracket, contents: contents, location: lbracket.location.to(rbracket.location) ) @@ -7688,9 +7696,34 @@ class LBracket # [Location] the location of this node attr_reader :location - def initialize(value:, location:) + # [Array[ Comment | EmbDoc ]] the comments attached to this node + attr_reader :comments + + def initialize(value:, location:, comments: []) @value = value @location = location + @comments = comments + end + + def format(q) + q.text(value) + end + + def pretty_print(q) + q.group(2, "(", ")") do + q.text("lbracket") + + q.breakable + q.pp(value) + + q.pp(Comment::List.new(comments)) + end + end + + def to_json(*opts) + { type: :lbracket, value: value, loc: location, cmts: comments }.to_json( + *opts + ) end end From a77e4a1b291d8f3df06e8f6f336dc4a8179df289 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:09:23 -0500 Subject: [PATCH 47/67] Force using braces if the block is within the predicate of a ternary. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e92f393f..1f36533f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow `IfMod` and `UnlessMod` to know about heredocs moving their bounds. - Properly handle breaking parameters when there are no parentheses. - Properly handle trailing operators in call chains with attached comments. +- Force using braces if the block is within the predicate of a ternary. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 610e7529..0dfde529 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2789,7 +2789,7 @@ def forced_do_end_bounds?(q) # to go with the {..} bounds. def forced_brace_bounds?(q) parent, grandparent, = q.parents.to_a - [If, IfMod, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(grandparent.class) && parent == grandparent.predicate + [If, IfMod, IfOp, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(grandparent.class) && parent == grandparent.predicate end def format_break(q, opening, closing) From 279d2a7cfde9304d7476b8cabb92f4726d9da89f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:13:02 -0500 Subject: [PATCH 48/67] Properly handle trailing comments after a `then` operator on a `When` or `In` clause. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f36533f..de1838c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Properly handle breaking parameters when there are no parentheses. - Properly handle trailing operators in call chains with attached comments. - Force using braces if the block is within the predicate of a ternary. +- Properly handle trailing comments after a `then` operator on a `When` or `In` clause. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 0dfde529..194bc487 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7166,8 +7166,9 @@ def on_in(pattern, statements, consequent) beginning = find_token(Kw, "in") ending = consequent || find_token(Kw, "end") + statements_start = find_token(Kw, "then", consume: false) || pattern statements.bind( - find_next_statement_start(pattern.location.end_char), + find_next_statement_start(statements_start.location.end_char), ending.location.start_char ) @@ -12758,8 +12759,9 @@ def on_when(arguments, statements, consequent) beginning = find_token(Kw, "when") ending = consequent || find_token(Kw, "end") + statements_start = find_token(Kw, "then", consume: false) || arguments statements.bind( - find_next_statement_start(arguments.location.end_char), + find_next_statement_start(statements_start.location.end_char), ending.location.start_char ) From f794ace292eb61ea0ce1e1ef167b33d0ae9a65c9 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:20:13 -0500 Subject: [PATCH 49/67] Ensure nested `HshPtn` nodes use braces. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de1838c5..51537c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Properly handle trailing operators in call chains with attached comments. - Force using braces if the block is within the predicate of a ternary. - Properly handle trailing comments after a `then` operator on a `When` or `In` clause. +- Ensure nested `HshPtn` nodes use braces. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 194bc487..6c192059 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1597,7 +1597,7 @@ def format(q) end parent = q.parent - if parts.length == 1 || PATTERNS.any? { |pattern| parent.is_a?(pattern) } + if parts.length == 1 || PATTERNS.include?(parent.class) q.text("[") q.seplist(parts) { |part| q.format(part) } q.text("]") @@ -6382,6 +6382,12 @@ def initialize(key, value) @value = value end + # This is here so that when checking if its contained within a parent + # pattern that it will return true. + def class + HshPtn + end + def comments [] end @@ -6456,7 +6462,7 @@ def format(q) end parent = q.parent - if PATTERNS.any? { |pattern| parent.is_a?(pattern) } + if PATTERNS.include?(parent.class) q.text("{ ") contents.call q.text(" }") From c05de439b25cc45b41e049beca349ff46d6ac7c5 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:35:43 -0500 Subject: [PATCH 50/67] Force using braces if the block is within a `Binary` within the predicate of a loop or conditional. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51537c78..478459d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Force using braces if the block is within the predicate of a ternary. - Properly handle trailing comments after a `then` operator on a `When` or `In` clause. - Ensure nested `HshPtn` nodes use braces. +- Force using braces if the block is within a `Binary` within the predicate of a loop or conditional. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 6c192059..dd17b0f1 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -2288,11 +2288,14 @@ def format(q) q.group do q.group { q.format(left) } q.text(" ") unless power - q.text(operator) - q.indent do - q.breakable(power ? "" : " ") - q.format(right) + q.group do + q.text(operator) + + q.indent do + q.breakable(power ? "" : " ") + q.format(right) + end end end end @@ -2788,8 +2791,14 @@ def forced_do_end_bounds?(q) # If we're the predicate of a loop or conditional, then we're going to have # to go with the {..} bounds. def forced_brace_bounds?(q) - parent, grandparent, = q.parents.to_a - [If, IfMod, IfOp, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(grandparent.class) && parent == grandparent.predicate + parents = q.parents.to_a + parents.each_with_index.any? do |parent, index| + # If we hit certain breakpoints then we know we're safe. + break false if [Paren, Statements].include?(parent.class) + + [If, IfMod, IfOp, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(parent.class) && + parent.predicate == parents[index - 1] + end end def format_break(q, opening, closing) From a8b239c46e6957a08fbc59a0c7d363d44526067f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:49:56 -0500 Subject: [PATCH 51/67] Make sure `StringLiteral` and `StringEmbExpr` know that they can be extended by heredocs. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 53 +++++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 478459d7..afec4430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Properly handle trailing comments after a `then` operator on a `When` or `In` clause. - Ensure nested `HshPtn` nodes use braces. - Force using braces if the block is within a `Binary` within the predicate of a loop or conditional. +- Make sure `StringLiteral` and `StringEmbExpr` know that they can be extended by heredocs. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index dd17b0f1..c504868e 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -10522,21 +10522,6 @@ def on_sclass(target, bodystmt) # value # end - # stmts_add is a parser event that represents a single statement inside a - # list of statements within any lexical block. It accepts as arguments the - # parent stmts node as well as an stmt which can be any expression in - # Ruby. - def on_stmts_add(statements, statement) - location = - if statements.body.empty? - statement.location - else - statements.location.to(statement.location) - end - - Statements.new(self, body: statements.body << statement, location: location) - end - # Everything that has a block of code inside of it has a list of statements. # Normally we would just track those as a node that has an array body, but we # have some special handling in order to handle empty statement lists. They @@ -10711,6 +10696,21 @@ def attach_comments(start_char, end_char) end end + # stmts_add is a parser event that represents a single statement inside a + # list of statements within any lexical block. It accepts as arguments the + # parent stmts node as well as an stmt which can be any expression in + # Ruby. + def on_stmts_add(statements, statement) + location = + if statements.body.empty? + statement.location + else + statements.location.to(statement.location) + end + + Statements.new(self, body: statements.body << statement, location: location) + end + # :call-seq: # on_stmts_new: () -> Statements def on_stmts_new @@ -10982,10 +10982,15 @@ def on_string_embexpr(statements) embexpr_end.location.start_char ) - StringEmbExpr.new( - statements: statements, - location: embexpr_beg.location.to(embexpr_end.location) - ) + location = + Location.new( + start_line: embexpr_beg.location.start_line, + start_char: embexpr_beg.location.start_char, + end_line: [embexpr_end.location.end_line, statements.location.end_line].max, + end_char: embexpr_end.location.end_char + ) + + StringEmbExpr.new(statements: statements, location: location) end # StringLiteral represents a string literal. @@ -11087,10 +11092,18 @@ def on_string_literal(string) tstring_beg = find_token(TStringBeg) tstring_end = find_token(TStringEnd) + location = + Location.new( + start_line: tstring_beg.location.start_line, + start_char: tstring_beg.location.start_char, + end_line: [tstring_end.location.end_line, string.location.end_line].max, + end_char: tstring_end.location.end_char + ) + StringLiteral.new( parts: string.parts, quote: tstring_beg.value, - location: tstring_beg.location.to(tstring_end.location) + location: location ) end end From e9e8409e7533280c0f69d3bb3222efb810027d13 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 13:55:14 -0500 Subject: [PATCH 52/67] Ensure `Int` nodes with preceding unary `+` get formatted properly. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afec4430..cd0ceed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure nested `HshPtn` nodes use braces. - Force using braces if the block is within a `Binary` within the predicate of a loop or conditional. - Make sure `StringLiteral` and `StringEmbExpr` know that they can be extended by heredocs. +- Ensure `Int` nodes with preceding unary `+` get formatted properly. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index c504868e..6c97b15a 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -7220,7 +7220,7 @@ def child_nodes end def format(q) - if !value.start_with?("0") && value.length >= 5 && !value.include?("_") + if !value.start_with?(/\+?0/) && value.length >= 5 && !value.include?("_") # If it's a plain integer and it doesn't have any underscores separating # the values, then we're going to insert them every 3 characters # starting from the right. From 6818082ba7d2c1304ea229caa7528e26d654a579 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 14:28:44 -0500 Subject: [PATCH 53/67] Allow different line suffix nodes to have different priorities. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 7 +++++-- lib/syntax_tree/prettyprint.rb | 28 +++++++++++++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd0ceed1..cdf6b97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure comments on assignment after the `=` before the value keep their place. - Trailing comments on parameters with no parentheses now do not force a break. - Allow `ArrayLiteral` opening brackets to have trailing comments. +- Allow different line suffix nodes to have different priorities. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 6c97b15a..3a12f169 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -113,6 +113,9 @@ def initialize(error, lineno, column) # A slightly enhanced PP that knows how to format recursively including # comments. class Formatter < PP + COMMENT_PRIORITY = 1 + HEREDOC_PRIORITY = 2 + attr_reader :stack, :quote def initialize(*) @@ -140,7 +143,7 @@ def format(node) # Print all comments that were found after the node. trailing.each do |comment| - line_suffix do + line_suffix(priority: COMMENT_PRIORITY) do text(" ") comment.format(self) break_parent @@ -6231,7 +6234,7 @@ def format(q) q.group do q.format(beginning) - q.line_suffix do + q.line_suffix(priority: Formatter::HEREDOC_PRIORITY) do q.group do breakable.call diff --git a/lib/syntax_tree/prettyprint.rb b/lib/syntax_tree/prettyprint.rb index 2805b724..5a119067 100644 --- a/lib/syntax_tree/prettyprint.rb +++ b/lib/syntax_tree/prettyprint.rb @@ -213,9 +213,12 @@ def pretty_print(q) # constantly check where the line ends to avoid accidentally printing some # content after a line suffix node. class LineSuffix - attr_reader :contents + DEFAULT_PRIORITY = 1 - def initialize(contents: []) + attr_reader :priority, :contents + + def initialize(priority: DEFAULT_PRIORITY, contents: []) + @priority = priority @contents = contents end @@ -741,10 +744,17 @@ def flush # This is a separate command stack that includes the same kind of triplets # as the commands variable. It is used to keep track of things that should - # go at the end of printed lines once the other doc nodes are - # accounted for. Typically this is used to implement comments. + # go at the end of printed lines once the other doc nodes are accounted for. + # Typically this is used to implement comments. line_suffixes = [] + # This is a special sort used to order the line suffixes by both the + # priority set on the line suffix and the index it was in the original + # array. + line_suffix_sort = ->(line_suffix) { + [-line_suffix.last, -line_suffixes.index(line_suffix)] + } + # This is a linear stack instead of a mutually recursive call defined on # the individual doc nodes for efficiency. while commands.any? @@ -783,7 +793,7 @@ def flush commands << [indent, mode, doc.flat_contents] if doc.flat_contents end when LineSuffix - line_suffixes << [indent, mode, doc.contents] + line_suffixes << [indent, mode, doc.contents, doc.priority] when Breakable if mode == MODE_FLAT if doc.force? @@ -804,7 +814,7 @@ def flush # to flush them now, as we are about to add a newline. if line_suffixes.any? commands << [indent, mode, doc] - commands += line_suffixes.reverse + commands += line_suffixes.sort_by(&line_suffix_sort) line_suffixes = [] next end @@ -838,7 +848,7 @@ def flush end if commands.empty? && line_suffixes.any? - commands += line_suffixes.reverse + commands += line_suffixes.sort_by(&line_suffix_sort) line_suffixes = [] end end @@ -1012,8 +1022,8 @@ def indent # Inserts a LineSuffix node into the print tree. The contents of the node are # determined by the block. - def line_suffix - doc = LineSuffix.new + def line_suffix(priority: LineSuffix::DEFAULT_PRIORITY) + doc = LineSuffix.new(priority: priority) target << doc with_target(doc.contents) { yield } From c67e06a04a8f01fc2efa25029b18765d879e6b86 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 14:52:35 -0500 Subject: [PATCH 54/67] Read the encoding before reading the entire file --- CHANGELOG.md | 1 + exe/stree | 32 +++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf6b97a..e56a50da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Trailing comments on parameters with no parentheses now do not force a break. - Allow `ArrayLiteral` opening brackets to have trailing comments. - Allow different line suffix nodes to have different priorities. +- Better support for encoding by properly reading encoding magic comments. ### Changed diff --git a/exe/stree b/exe/stree index b9daca65..1cf53733 100755 --- a/exe/stree +++ b/exe/stree @@ -17,35 +17,35 @@ end module SyntaxTree::CLI class AST - def run(filepath) - pp SyntaxTree.parse(File.read(filepath)) + def run(filepath, source) + pp SyntaxTree.parse(source) end end class Check - def run(filepath) - formatted = SyntaxTree.format(File.read(filepath)) + def run(filepath, source) + formatted = SyntaxTree.format(source) raise if formatted != SyntaxTree.format(formatted) end end class Doc - def run(filepath) + def run(filepath, source) formatter = SyntaxTree::Formatter.new([]) - SyntaxTree.parse(File.read(filepath)).format(formatter) + SyntaxTree.parse(source).format(formatter) pp formatter.groups.first end end class Format - def run(filepath) - puts SyntaxTree.format(File.read(filepath)) + def run(filepath, source) + puts SyntaxTree.format(source) end end class Write - def run(filepath) - File.write(filepath, SyntaxTree.format(File.read(filepath))) + def run(filepath, source) + File.write(filepath, SyntaxTree.format(source)) end end end @@ -71,8 +71,18 @@ errored = false ARGV.each do |pattern| Dir.glob(pattern).each do |filepath| + next unless File.file?(filepath) + begin - mode.run(filepath) if File.file?(filepath) + encoding = + File.open(filepath, "r") do |file| + header = file.readline + header += file.readline if header.start_with?("#!") + Ripper.new(header).tap(&:parse).encoding + end + + source = File.read(filepath, encoding: encoding) + mode.run(filepath, source) rescue => error warn("!!! Failed on #{filepath}") warn(error.message) From 85911c99ac35d216ada7deec27e1ed985c0a26fc Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 15:16:06 -0500 Subject: [PATCH 55/67] Properly handle byte-order mark column offsets at the beginnings of files. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56a50da..ede6bf2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Force using braces if the block is within a `Binary` within the predicate of a loop or conditional. - Make sure `StringLiteral` and `StringEmbExpr` know that they can be extended by heredocs. - Ensure `Int` nodes with preceding unary `+` get formatted properly. +- Properly handle byte-order mark column offsets at the beginnings of files. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 3a12f169..1bceb834 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -48,8 +48,11 @@ def initialize(start, line) end end + # Technically it's possible for the column index to be a negative value if + # there's a BOM at the beginning of the file, which is the reason we need to + # compare it to 0 here. def [](byteindex) - @indices[byteindex] + @indices[byteindex < 0 ? 0 : byteindex] end end From 0c0e3293851cebe71b22783f787c237171ecb307 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 15:31:36 -0500 Subject: [PATCH 56/67] Ensure `Words`, `Symbols`, `QWords`, and `QSymbols` properly format when their contents contain brackets. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 89 ++++++++++++++++++++++++++++++++------- test/fixtures/qsymbols.rb | 2 + test/fixtures/qwords.rb | 2 + test/fixtures/symbols.rb | 2 + test/fixtures/words.rb | 2 + 6 files changed, 82 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ede6bf2a..4deb5243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Make sure `StringLiteral` and `StringEmbExpr` know that they can be extended by heredocs. - Ensure `Int` nodes with preceding unary `+` get formatted properly. - Properly handle byte-order mark column offsets at the beginnings of files. +- Ensure `Words`, `Symbols`, `QWords`, and `QSymbols` properly format when their contents contain brackets. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 1bceb834..946a5860 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1506,6 +1506,7 @@ def on_array(contents) tstring_end = find_token(TStringEnd) contents.class.new( + beginning: contents.beginning, elements: contents.elements, location: contents.location.to(tstring_end.location) ) @@ -9257,6 +9258,9 @@ def nearest_nodes(node, comment) # %i[one two three] # class QSymbols + # [QSymbolsBeg] the token that opens this array literal + attr_reader :beginning + # [Array[ TStringContent ]] the elements of the array attr_reader :elements @@ -9266,7 +9270,8 @@ class QSymbols # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(elements:, location:, comments: []) + def initialize(beginning:, elements:, location:, comments: []) + @beginning = beginning @elements = elements @location = location @comments = comments @@ -9277,7 +9282,14 @@ def child_nodes end def format(q) - q.group(0, "%i[", "]") do + opening, closing = "%i[", "]" + + if elements.any? { |element| element.match?(/[\[\]]/) } + opening = beginning.value + closing = Quotes.matching(opening[2]) + end + + q.group(0, opening, closing) do q.indent do q.breakable("") q.seplist(elements, -> { q.breakable }) do |element| @@ -9313,6 +9325,7 @@ def to_json(*opts) # on_qsymbols_add: (QSymbols qsymbols, TStringContent element) -> QSymbols def on_qsymbols_add(qsymbols, element) QSymbols.new( + beginning: qsymbols.beginning, elements: qsymbols.elements << element, location: qsymbols.location.to(element.location) ) @@ -9354,9 +9367,9 @@ def on_qsymbols_beg(value) # :call-seq: # on_qsymbols_new: () -> QSymbols def on_qsymbols_new - qsymbols_beg = find_token(QSymbolsBeg) + beginning = find_token(QSymbolsBeg) - QSymbols.new(elements: [], location: qsymbols_beg.location) + QSymbols.new(beginning: beginning, elements: [], location: beginning.location) end # QWords represents a string literal array without interpolation. @@ -9364,6 +9377,9 @@ def on_qsymbols_new # %w[one two three] # class QWords + # [QWordsBeg] the token that opens this array literal + attr_reader :beginning + # [Array[ TStringContent ]] the elements of the array attr_reader :elements @@ -9373,7 +9389,8 @@ class QWords # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(elements:, location:, comments: []) + def initialize(beginning:, elements:, location:, comments: []) + @beginning = beginning @elements = elements @location = location @comments = comments @@ -9384,7 +9401,14 @@ def child_nodes end def format(q) - q.group(0, "%w[", "]") do + opening, closing = "%w[", "]" + + if elements.any? { |element| element.match?(/[\[\]]/) } + opening = beginning.value + closing = Quotes.matching(opening[2]) + end + + q.group(0, opening, closing) do q.indent do q.breakable("") q.seplist(elements, -> { q.breakable }) do |element| @@ -9417,6 +9441,7 @@ def to_json(*opts) # on_qwords_add: (QWords qwords, TStringContent element) -> QWords def on_qwords_add(qwords, element) QWords.new( + beginning: qwords.beginning, elements: qwords.elements << element, location: qwords.location.to(element.location) ) @@ -9458,9 +9483,9 @@ def on_qwords_beg(value) # :call-seq: # on_qwords_new: () -> QWords def on_qwords_new - qwords_beg = find_token(QWordsBeg) + beginning = find_token(QWordsBeg) - QWords.new(elements: [], location: qwords_beg.location) + QWords.new(beginning: beginning, elements: [], location: beginning.location) end # RationalLiteral represents the use of a rational number literal. @@ -11331,6 +11356,9 @@ def on_symbol_literal(value) # %I[one two three] # class Symbols + # [SymbolsBeg] the token that opens this array literal + attr_reader :beginning + # [Array[ Word ]] the words in the symbol array literal attr_reader :elements @@ -11340,7 +11368,8 @@ class Symbols # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(elements:, location:, comments: []) + def initialize(beginning:, elements:, location:, comments: []) + @beginning = beginning @elements = elements @location = location @comments = comments @@ -11351,7 +11380,14 @@ def child_nodes end def format(q) - q.group(0, "%I[", "]") do + opening, closing = "%I[", "]" + + if elements.any? { |element| element.match?(/[\[\]]/) } + opening = beginning.value + closing = Quotes.matching(opening[2]) + end + + q.group(0, opening, closing) do q.indent do q.breakable("") q.seplist(elements, -> { q.breakable }) do |element| @@ -11387,6 +11423,7 @@ def to_json(*opts) # on_symbols_add: (Symbols symbols, Word word) -> Symbols def on_symbols_add(symbols, word) Symbols.new( + beginning: symbols.beginning, elements: symbols.elements << word, location: symbols.location.to(word.location) ) @@ -11429,9 +11466,9 @@ def on_symbols_beg(value) # :call-seq: # on_symbols_new: () -> Symbols def on_symbols_new - symbols_beg = find_token(SymbolsBeg) + beginning = find_token(SymbolsBeg) - Symbols.new(elements: [], location: symbols_beg.location) + Symbols.new(beginning: beginning, elements: [], location: beginning.location) end # TLambda represents the beginning of a lambda literal. @@ -11682,6 +11719,10 @@ def initialize(value:, location:, comments: []) @comments = comments end + def match?(pattern) + value.match?(pattern) + end + def child_nodes [] end @@ -13008,6 +13049,10 @@ def initialize(parts:, location:, comments: []) @comments = comments end + def match?(pattern) + parts.any? { |part| part.is_a?(TStringContent) && part.match?(pattern) } + end + def child_nodes parts end @@ -13057,6 +13102,9 @@ def on_word_new # %W[one two three] # class Words + # [WordsBeg] the token that opens this array literal + attr_reader :beginning + # [Array[ Word ]] the elements of this array attr_reader :elements @@ -13066,7 +13114,8 @@ class Words # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(elements:, location:, comments: []) + def initialize(beginning:, elements:, location:, comments: []) + @beginning = beginning @elements = elements @location = location @comments = comments @@ -13077,7 +13126,14 @@ def child_nodes end def format(q) - q.group(0, "%W[", "]") do + opening, closing = "%W[", "]" + + if elements.any? { |element| element.match?(/[\[\]]/) } + opening = beginning.value + closing = Quotes.matching(opening[2]) + end + + q.group(0, opening, closing) do q.indent do q.breakable("") q.seplist(elements, -> { q.breakable }) do |element| @@ -13110,6 +13166,7 @@ def to_json(*opts) # on_words_add: (Words words, Word word) -> Words def on_words_add(words, word) Words.new( + beginning: words.beginning, elements: words.elements << word, location: words.location.to(word.location) ) @@ -13152,9 +13209,9 @@ def on_words_beg(value) # :call-seq: # on_words_new: () -> Words def on_words_new - words_beg = find_token(WordsBeg) + beginning = find_token(WordsBeg) - Words.new(elements: [], location: words_beg.location) + Words.new(beginning: beginning, elements: [], location: beginning.location) end # def on_words_sep(value) diff --git a/test/fixtures/qsymbols.rb b/test/fixtures/qsymbols.rb index c9ebe9b4..ece444f7 100644 --- a/test/fixtures/qsymbols.rb +++ b/test/fixtures/qsymbols.rb @@ -15,3 +15,5 @@ %i[foo] % %i[foo] # comment +% +%i{foo[]} diff --git a/test/fixtures/qwords.rb b/test/fixtures/qwords.rb index 14b25be6..902a0158 100644 --- a/test/fixtures/qwords.rb +++ b/test/fixtures/qwords.rb @@ -15,3 +15,5 @@ %w[foo] % %w[foo] # comment +% +%w{foo[]} diff --git a/test/fixtures/symbols.rb b/test/fixtures/symbols.rb index c67d6555..5e2673f3 100644 --- a/test/fixtures/symbols.rb +++ b/test/fixtures/symbols.rb @@ -17,3 +17,5 @@ %I[foo] % %I[foo] # comment +% +%I{foo[]} diff --git a/test/fixtures/words.rb b/test/fixtures/words.rb index 021eac7a..cbebbc39 100644 --- a/test/fixtures/words.rb +++ b/test/fixtures/words.rb @@ -17,3 +17,5 @@ %W[foo] % %W[foo] # comment +% +%W{foo[]} From de91eadac6fb6ca38d044f65ce0e804f41bbdb82 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 7 Dec 2021 15:39:46 -0500 Subject: [PATCH 57/67] Support singleton single-line method definitions. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 45 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4deb5243..82dd6635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow `ArrayLiteral` opening brackets to have trailing comments. - Allow different line suffix nodes to have different priorities. - Better support for encoding by properly reading encoding magic comments. +- Support singleton single-line method definitions. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 946a5860..fecf2274 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -4277,6 +4277,12 @@ def to_json(*opts) # def method = result # class DefEndless + # [untyped] the target where the method is being defined + attr_reader :target + + # [Op | Period] the operator being used to declare the method + attr_reader :operator + # [Backtick | Const | Ident | Kw | Op] the name of the method attr_reader :name @@ -4292,7 +4298,9 @@ class DefEndless # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(name:, paren:, statement:, location:, comments: []) + def initialize(target:, operator:, name:, paren:, statement:, location:, comments: []) + @target = target + @operator = operator @name = name @paren = paren @statement = statement @@ -4301,14 +4309,21 @@ def initialize(name:, paren:, statement:, location:, comments: []) end def child_nodes - [name, paren, statement] + [target, operator, name, paren, statement] end def format(q) q.group do q.text("def ") + + if target + q.format(target) + q.format(CallOperatorFormatter.new(operator)) + end + q.format(name) q.format(paren) if paren && !paren.contents.empty? + q.text(" =") q.group do q.indent do @@ -4323,6 +4338,14 @@ def pretty_print(q) q.group(2, "(", ")") do q.text("def_endless") + if target + q.breakable + q.pp(target) + + q.breakable + q.pp(operator) + end + q.breakable q.pp(name) @@ -4369,6 +4392,8 @@ def on_def(name, params, bodystmt) unless bodystmt.is_a?(BodyStmt) node = DefEndless.new( + target: nil, + operator: nil, name: name, paren: params, statement: bodystmt, @@ -4612,6 +4637,22 @@ def on_defs(target, operator, name, params, bodystmt) end beginning = find_token(Kw, "def") + + # If we don't have a bodystmt node, then we have a single-line method + unless bodystmt.is_a?(BodyStmt) + node = + DefEndless.new( + target: target, + operator: operator, + name: name, + paren: params, + statement: bodystmt, + location: beginning.location.to(bodystmt.location) + ) + + return node + end + ending = find_token(Kw, "end") bodystmt.bind( From 96e482f232a977addd2f868611eff3b62b0a6aae Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 09:58:38 -0500 Subject: [PATCH 58/67] Ensure ternaries being broken out into `if`...`else`...`end` get wrapped in parentheses if necessary. Support `stree-ignore` comments to ignore formatting nodes. --- CHANGELOG.md | 2 + lib/syntax_tree.rb | 205 ++++++++++++++++++++++++++------------------- 2 files changed, 123 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82dd6635..15e18741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Allow different line suffix nodes to have different priorities. - Better support for encoding by properly reading encoding magic comments. - Support singleton single-line method definitions. +- Support `stree-ignore` comments to ignore formatting nodes. ### Changed @@ -64,6 +65,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Ensure `Int` nodes with preceding unary `+` get formatted properly. - Properly handle byte-order mark column offsets at the beginnings of files. - Ensure `Words`, `Symbols`, `QWords`, and `QSymbols` properly format when their contents contain brackets. +- Ensure ternaries being broken out into `if`...`else`...`end` get wrapped in parentheses if necessary. ### Removed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index fecf2274..7c8f2aa6 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -119,10 +119,12 @@ class Formatter < PP COMMENT_PRIORITY = 1 HEREDOC_PRIORITY = 2 - attr_reader :stack, :quote + attr_reader :source, :stack, :quote - def initialize(*) - super + def initialize(source, ...) + super(...) + + @source = source @stack = [] @quote = "\"" end @@ -142,7 +144,13 @@ def format(node) breakable(force: true) end - doc = node.format(self) + # If the node has a stree-ignore comment right before it, then we're + # going to just print out the node as it was seen in the source. + if leading.last&.value&.include?("stree-ignore") + doc = text(source[node.location.start_char...node.location.end_char]) + else + doc = node.format(self) + end # Print all comments that were found after the node. trailing.each do |comment| @@ -271,7 +279,7 @@ def self.parse(source) def self.format(source) output = [] - formatter = Formatter.new(output) + formatter = Formatter.new(source, output) parse(source).format(formatter) formatter.flush @@ -6654,51 +6662,6 @@ def on_ident(value) ) end - # If you have a modifier statement (for instance a modifier if statement or a - # modifier while loop) there are times when you need to wrap the entire - # statement in parentheses. This occurs when you have something like: - # - # foo[:foo] = - # if bar? - # baz - # end - # - # Normally we would shorten this to an inline version, which would result in: - # - # foo[:foo] = baz if bar? - # - # but this actually has different semantic meaning. The first example will - # result in a nil being inserted into the hash for the :foo key, whereas the - # second example will result in an empty hash because the if statement applies - # to the entire assignment. - # - # We can fix this in a couple of ways. We can use the then keyword, as in: - # - # foo[:foo] = if bar? then baz end - # - # But this isn't used very often. We can also just leave it as is with the - # multi-line version, but for a short predicate and short value it looks - # verbose. The last option and the one used here is to add parentheses on - # both sides of the expression, as in: - # - # foo[:foo] = (baz if bar?) - # - # This approach maintains the nice conciseness of the inline version, while - # keeping the correct semantic meaning. - module ModifierParentheses - def self.call(q) - parens = [Args, Assign, Assoc, Binary, Call, Defined, MAssign, OpAssign] - - if parens.include?(q.parent.class) - q.text("(") - yield - q.text(")") - else - yield - end - end - end - # Formats an If or Unless node. class ConditionalFormatter # [String] the keyword associated with this conditional @@ -6727,7 +6690,7 @@ def format(q) else q.group do q.if_break { format_break(q, force: false) }.if_flat do - ModifierParentheses.call(q) do + Parentheses.flat(q) do q.format(node.statements) q.text(" #{keyword} ") q.format(node.predicate) @@ -6887,38 +6850,20 @@ def child_nodes end def format(q) - q.group do - q.if_break do - q.text("if ") - q.nest("if ".length) { q.format(predicate) } - - q.indent do - q.breakable - q.format(truthy) - end - - q.breakable - q.text("else") - - q.indent do - q.breakable - q.format(falsy) - end - - q.breakable - q.text("end") - end.if_flat do - q.format(predicate) - q.text(" ?") - - q.breakable - q.format(truthy) - q.text(" :") + # stree-ignore + no_ternary = [ + Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp, + Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super, + Undef, Unless, UnlessMod, UntilMod, VarAlias, VoidStmt, WhileMod, Yield, + Yield0, ZSuper + ] - q.breakable - q.format(falsy) - end + if force_flat.include?(truthy.class) || force_flat.include?(falsy.class) + q.group { format_flat(q) } + return end + + q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } } end def pretty_print(q) @@ -6948,6 +6893,43 @@ def to_json(*opts) cmts: comments }.to_json(*opts) end + + private + + def format_break(q) + Parentheses.break(q) do + q.text("if ") + q.nest("if ".length) { q.format(predicate) } + + q.indent do + q.breakable + q.format(truthy) + end + + q.breakable + q.text("else") + + q.indent do + q.breakable + q.format(falsy) + end + + q.breakable + q.text("end") + end + end + + def format_flat(q) + q.format(predicate) + q.text(" ?") + + q.breakable + q.format(truthy) + q.text(" :") + + q.breakable + q.format(falsy) + end end # :call-seq: @@ -6986,7 +6968,7 @@ def format(q) q.breakable q.text("end") end.if_flat do - ModifierParentheses.call(q) do + Parentheses.flat(q) do q.format(node.statement) q.text(" #{keyword} ") q.format(node.predicate) @@ -8648,6 +8630,61 @@ def on_opassign(target, operator, value) ) end + # If you have a modifier statement (for instance a modifier if statement or a + # modifier while loop) there are times when you need to wrap the entire + # statement in parentheses. This occurs when you have something like: + # + # foo[:foo] = + # if bar? + # baz + # end + # + # Normally we would shorten this to an inline version, which would result in: + # + # foo[:foo] = baz if bar? + # + # but this actually has different semantic meaning. The first example will + # result in a nil being inserted into the hash for the :foo key, whereas the + # second example will result in an empty hash because the if statement applies + # to the entire assignment. + # + # We can fix this in a couple of ways. We can use the then keyword, as in: + # + # foo[:foo] = if bar? then baz end + # + # But this isn't used very often. We can also just leave it as is with the + # multi-line version, but for a short predicate and short value it looks + # verbose. The last option and the one used here is to add parentheses on + # both sides of the expression, as in: + # + # foo[:foo] = (baz if bar?) + # + # This approach maintains the nice conciseness of the inline version, while + # keeping the correct semantic meaning. + module Parentheses + NODES = [Args, Assign, Assoc, Binary, Call, Defined, MAssign, OpAssign] + + def self.flat(q) + return yield unless NODES.include?(q.parent.class) + + q.text("(") + yield + q.text(")") + end + + def self.break(q) + return yield unless NODES.include?(q.parent.class) + + q.text("(") + q.indent do + q.breakable("") + yield + end + q.breakable("") + q.text(")") + end + end + # def on_operator_ambiguous(value) # value # end @@ -10749,7 +10786,7 @@ def attach_comments(start_char, end_char) location = comment.location if !comment.inline? && (start_char <= location.start_char) && - (end_char >= location.end_char) + (end_char >= location.end_char) && !comment.value.include?("stree-ignore") parser_comments.delete_at(comment_index) while (node = body[body_index]) && @@ -12278,7 +12315,7 @@ def format(q) q.group do q.if_break { format_break(q) }.if_flat do - ModifierParentheses.call(q) do + Parentheses.flat(q) do q.format(statements) q.text(" #{keyword} ") q.format(node.predicate) From 1fad560f894a9e9270d7f07f97c051fa34a97f45 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 10:06:04 -0500 Subject: [PATCH 59/67] Add special formatting for arrays of `VarRef` nodes whose sum width is greater than 2 * the maximum width. --- CHANGELOG.md | 1 + lib/syntax_tree.rb | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e18741..6e22bede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Better support for encoding by properly reading encoding magic comments. - Support singleton single-line method definitions. - Support `stree-ignore` comments to ignore formatting nodes. +- Add special formatting for arrays of `VarRef` nodes whose sum width is greater than 2 * the maximum width. ### Changed diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index 7c8f2aa6..af9ef582 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -1402,6 +1402,27 @@ def format(q) end end + class VarRefsFormatter + # [Args] the contents of the array + attr_reader :contents + + def initialize(contents) + @contents = contents + end + + def format(q) + q.group(0, "[", "]") do + q.indent do + q.breakable("") + q.seplist(contents.parts, -> { q.fill_breakable(", ") }) do |part| + q.format(part) + end + end + q.breakable("") + end + end + end + # [LBracket] the bracket that opens this array attr_reader :lbracket @@ -1436,6 +1457,11 @@ def format(q) return end + if var_refs?(q) + VarRefsFormatter.new(contents).format(q) + return + end + q.group do q.format(lbracket) @@ -1495,6 +1521,14 @@ def qsymbols? part.is_a?(SymbolLiteral) && part.comments.empty? end end + + def var_refs?(q) + lbracket.comments.empty? && + contents && + contents.comments.empty? && + contents.parts.all? { |part| part.is_a?(VarRef) && part.comments.empty? } && + (contents.parts.sum { |part| part.value.value.length + 2 } > q.maxwidth * 2) + end end # :call-seq: @@ -6850,7 +6884,6 @@ def child_nodes end def format(q) - # stree-ignore no_ternary = [ Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp, Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super, From 0c5ff396bee378906b73d0c8dfa728925d142e79 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 10:18:05 -0500 Subject: [PATCH 60/67] Reformat with stree --- lib/syntax_tree.rb | 158 +++++++++++++++++++++++++++++++-------------- 1 file changed, 111 insertions(+), 47 deletions(-) diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index af9ef582..b8abe211 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -69,8 +69,7 @@ def initialize(start_line:, start_char:, end_line:, end_char:) def ==(other) other.is_a?(Location) && start_line == other.start_line && - start_char == other.start_char && - end_line == other.end_line && + start_char == other.start_char && end_line == other.end_line && end_char == other.end_char end @@ -146,7 +145,7 @@ def format(node) # If the node has a stree-ignore comment right before it, then we're # going to just print out the node as it was seen in the source. - if leading.last&.value&.include?("stree-ignore") + if leading.last&.ignore? doc = text(source[node.location.start_char...node.location.end_char]) else doc = node.format(self) @@ -665,7 +664,9 @@ def pretty_print(q) end def to_json(*opts) - { type: :__end__, value: value, loc: location, cmts: comments }.to_json(*opts) + { type: :__end__, value: value, loc: location, cmts: comments }.to_json( + *opts + ) end end @@ -1414,9 +1415,13 @@ def format(q) q.group(0, "[", "]") do q.indent do q.breakable("") - q.seplist(contents.parts, -> { q.fill_breakable(", ") }) do |part| - q.format(part) + + separator = -> do + q.text(",") + q.fill_breakable end + + q.seplist(contents.parts, separator) { |part| q.format(part) } end q.breakable("") end @@ -1497,13 +1502,12 @@ def to_json(*opts) private def qwords? - lbracket.comments.empty? && - contents && contents.comments.empty? && contents.parts.length > 1 && + lbracket.comments.empty? && contents && contents.comments.empty? && + contents.parts.length > 1 && contents.parts.all? do |part| case part when StringLiteral - part.comments.empty? && - part.parts.length == 1 && + part.comments.empty? && part.parts.length == 1 && part.parts.first.is_a?(TStringContent) && !part.parts.first.value.match?(/[\s\[\]\\]/) when CHAR @@ -1515,19 +1519,22 @@ def qwords? end def qsymbols? - lbracket.comments.empty? && - contents && contents.comments.empty? && contents.parts.length > 1 && + lbracket.comments.empty? && contents && contents.comments.empty? && + contents.parts.length > 1 && contents.parts.all? do |part| part.is_a?(SymbolLiteral) && part.comments.empty? end end def var_refs?(q) - lbracket.comments.empty? && - contents && - contents.comments.empty? && - contents.parts.all? { |part| part.is_a?(VarRef) && part.comments.empty? } && - (contents.parts.sum { |part| part.value.value.length + 2 } > q.maxwidth * 2) + lbracket.comments.empty? && contents && contents.comments.empty? && + contents.parts.all? do |part| + part.is_a?(VarRef) && part.comments.empty? + end && + ( + contents.parts.sum { |part| part.value.value.length + 2 } > + q.maxwidth * 2 + ) end end @@ -2793,11 +2800,11 @@ def format(q) if unchangeable_bounds?(q) [block_open.value, block_close, block_open.value, block_close] elsif forced_do_end_bounds?(q) - ["do", "end", "do", "end"] + %w[do end do end] elsif forced_brace_bounds?(q) - ["{", "}", "{", "}"] + %w[{ } { }] else - ["do", "end", "{", "}"] + %w[do end { }] end # If the receiver of this block a Command or CommandCall node, then there @@ -2811,8 +2818,9 @@ def format(q) end q.group do - q.if_break { format_break(q, break_opening, break_closing) } - .if_flat { format_flat(q, flat_opening, flat_closing) } + q.if_break { format_break(q, break_opening, break_closing) }.if_flat do + format_flat(q, flat_opening, flat_closing) + end end end @@ -2845,8 +2853,17 @@ def forced_brace_bounds?(q) # If we hit certain breakpoints then we know we're safe. break false if [Paren, Statements].include?(parent.class) - [If, IfMod, IfOp, Unless, UnlessMod, While, WhileMod, Until, UntilMod].include?(parent.class) && - parent.predicate == parents[index - 1] + [ + If, + IfMod, + IfOp, + Unless, + UnlessMod, + While, + WhileMod, + Until, + UntilMod + ].include?(parent.class) && parent.predicate == parents[index - 1] end end @@ -3145,7 +3162,14 @@ class Call # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(receiver:, operator:, message:, arguments:, location:, comments: []) + def initialize( + receiver:, + operator:, + message:, + arguments:, + location:, + comments: [] + ) @receiver = receiver @operator = operator @message = message @@ -3155,7 +3179,12 @@ def initialize(receiver:, operator:, message:, arguments:, location:, comments: end def child_nodes - [receiver, (operator if operator != :"::"), (message if message != :call), arguments] + [ + receiver, + (operator if operator != :"::"), + (message if message != :call), + arguments + ] end def format(q) @@ -3170,11 +3199,14 @@ def format(q) q.group do q.indent do - q.breakable(force: true) if receiver.comments.any? || call_operator.comments.any? + if receiver.comments.any? || call_operator.comments.any? + q.breakable(force: true) + end q.format(call_operator) if call_operator.comments.empty? q.format(message) if message != :call - q.format(arguments) if arguments end + + q.format(arguments) if arguments end end end @@ -3873,6 +3905,10 @@ def trailing? @trailing end + def ignore? + value[1..-1].strip == "stree-ignore" + end + def comments [] end @@ -4340,7 +4376,15 @@ class DefEndless # [Array[ Comment | EmbDoc ]] the comments attached to this node attr_reader :comments - def initialize(target:, operator:, name:, paren:, statement:, location:, comments: []) + def initialize( + target:, + operator:, + name:, + paren:, + statement:, + location:, + comments: [] + ) @target = target @operator = operator @name = name @@ -5124,9 +5168,9 @@ def quotes(q) matching = Quotes.matching(quote[2]) pattern = /[\n#{Regexp.escape(matching)}'"]/ - if parts.any? do |part| + if parts.any? { |part| part.is_a?(TStringContent) && part.value.match?(pattern) - end + } [quote, matching] elsif Quotes.locked?(self) ["#{":" unless hash_key}'", "'"] @@ -5390,6 +5434,10 @@ def inline? false end + def ignore? + false + end + def comments [] end @@ -5757,9 +5805,13 @@ def pretty_print(q) end def to_json(*opts) - { type: :fcall, value: value, args: arguments, loc: location, cmts: comments }.to_json( - *opts - ) + { + type: :fcall, + value: value, + args: arguments, + loc: location, + cmts: comments + }.to_json(*opts) end end @@ -6884,7 +6936,7 @@ def child_nodes end def format(q) - no_ternary = [ + force_flat = [ Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp, Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super, Undef, Unless, UnlessMod, UntilMod, VarAlias, VoidStmt, WhileMod, Yield, @@ -7694,7 +7746,10 @@ def to_json(*opts) def on_lambda(params, statements) beginning = find_token(TLambda) - if tokens.any? { |token| token.is_a?(TLamBeg) && token.location.start_char > beginning.location.start_char } + if tokens.any? { |token| + token.is_a?(TLamBeg) && + token.location.start_char > beginning.location.start_char + } opening = find_token(TLamBeg) closing = find_token(RBrace) else @@ -8856,9 +8911,7 @@ def initialize( # it's missing. def empty? requireds.empty? && optionals.empty? && !rest && posts.empty? && - keywords.empty? && - !keyword_rest && - !block + keywords.empty? && !keyword_rest && !block end def child_nodes @@ -8889,10 +8942,10 @@ def format(q) parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest parts << block if block - contents = -> { + contents = -> do q.seplist(parts) { |part| q.format(part) } q.format(rest) if rest && rest.is_a?(ExcessedComma) - } + end if [Def, Defs].include?(q.parent.class) q.group(0, "(", ")") do @@ -9480,7 +9533,11 @@ def on_qsymbols_beg(value) def on_qsymbols_new beginning = find_token(QSymbolsBeg) - QSymbols.new(beginning: beginning, elements: [], location: beginning.location) + QSymbols.new( + beginning: beginning, + elements: [], + location: beginning.location + ) end # QWords represents a string literal array without interpolation. @@ -10755,7 +10812,8 @@ def format(q) access_controls = Hash.new do |hash, node| - hash[node] = node.is_a?(VCall) && %w[private protected public].include?(node.value.value) + hash[node] = node.is_a?(VCall) && + %w[private protected public].include?(node.value.value) end body.each_with_index do |statement, index| @@ -10819,7 +10877,7 @@ def attach_comments(start_char, end_char) location = comment.location if !comment.inline? && (start_char <= location.start_char) && - (end_char >= location.end_char) && !comment.value.include?("stree-ignore") + (end_char >= location.end_char) && !comment.ignore? parser_comments.delete_at(comment_index) while (node = body[body_index]) && @@ -11128,7 +11186,8 @@ def on_string_embexpr(statements) Location.new( start_line: embexpr_beg.location.start_line, start_char: embexpr_beg.location.start_char, - end_line: [embexpr_end.location.end_line, statements.location.end_line].max, + end_line: + [embexpr_end.location.end_line, statements.location.end_line].max, end_char: embexpr_end.location.end_char ) @@ -11238,7 +11297,8 @@ def on_string_literal(string) Location.new( start_line: tstring_beg.location.start_line, start_char: tstring_beg.location.start_char, - end_line: [tstring_end.location.end_line, string.location.end_line].max, + end_line: + [tstring_end.location.end_line, string.location.end_line].max, end_char: tstring_end.location.end_char ) @@ -11579,7 +11639,11 @@ def on_symbols_beg(value) def on_symbols_new beginning = find_token(SymbolsBeg) - Symbols.new(beginning: beginning, elements: [], location: beginning.location) + Symbols.new( + beginning: beginning, + elements: [], + location: beginning.location + ) end # TLambda represents the beginning of a lambda literal. From c9b8906a7eb69ff7d6e5375352bcf4c3d78b3e62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Dec 2021 17:25:59 +0000 Subject: [PATCH 61/67] Bump parser from 3.0.3.1 to 3.0.3.2 Bumps [parser](https://github.com/whitequark/parser) from 3.0.3.1 to 3.0.3.2. - [Release notes](https://github.com/whitequark/parser/releases) - [Changelog](https://github.com/whitequark/parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/whitequark/parser/compare/v3.0.3.1...v3.0.3.2) --- updated-dependencies: - dependency-name: parser dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 94bd8c7e..ccdd3b3a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,7 +10,7 @@ GEM benchmark-ips (2.9.2) docile (1.4.0) minitest (5.14.4) - parser (3.0.3.1) + parser (3.0.3.2) ast (~> 2.4.1) rake (13.0.6) ruby_parser (3.18.1) From 2febac5537caa559aaef00632d8ed616778129cf Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 10:47:55 -0500 Subject: [PATCH 62/67] Extract CLI to its own file --- exe/stree | 95 ++-------------------------------- lib/syntax_tree/cli.rb | 115 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 lib/syntax_tree/cli.rb diff --git a/exe/stree b/exe/stree index 1cf53733..6ec4acae 100755 --- a/exe/stree +++ b/exe/stree @@ -1,95 +1,8 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require_relative File.expand_path("../lib/syntax_tree", __dir__) +$:.unshift(File.expand_path("../lib", __dir__)) +require "syntax_tree" +require "syntax_tree/cli" -help = <<~EOF - stree MDOE FILE - - MODE: one of "a", "ast", "c", "check", "d", "doc", "f", "format", "w", or "write" - FILE: one or more paths to files to parse -EOF - -if ARGV.length < 2 - warn(help) - exit(1) -end - -module SyntaxTree::CLI - class AST - def run(filepath, source) - pp SyntaxTree.parse(source) - end - end - - class Check - def run(filepath, source) - formatted = SyntaxTree.format(source) - raise if formatted != SyntaxTree.format(formatted) - end - end - - class Doc - def run(filepath, source) - formatter = SyntaxTree::Formatter.new([]) - SyntaxTree.parse(source).format(formatter) - pp formatter.groups.first - end - end - - class Format - def run(filepath, source) - puts SyntaxTree.format(source) - end - end - - class Write - def run(filepath, source) - File.write(filepath, SyntaxTree.format(source)) - end - end -end - -mode = - case ARGV.shift - when "a", "ast" - SyntaxTree::CLI::AST.new - when "c", "check" - SyntaxTree::CLI::Check.new - when "d", "doc" - SyntaxTree::CLI::Doc.new - when "f", "format" - SyntaxTree::CLI::Format.new - when "w", "write" - SyntaxTree::CLI::Write.new - else - warn(help) - exit(1) - end - -errored = false - -ARGV.each do |pattern| - Dir.glob(pattern).each do |filepath| - next unless File.file?(filepath) - - begin - encoding = - File.open(filepath, "r") do |file| - header = file.readline - header += file.readline if header.start_with?("#!") - Ripper.new(header).tap(&:parse).encoding - end - - source = File.read(filepath, encoding: encoding) - mode.run(filepath, source) - rescue => error - warn("!!! Failed on #{filepath}") - warn(error.message) - warn(error.backtrace) - errored = true - end - end -end - -exit(errored ? 1 : 0) +exit(SyntaxTree::CLI.run(ARGV)) diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb new file mode 100644 index 00000000..f30217e6 --- /dev/null +++ b/lib/syntax_tree/cli.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +class SyntaxTree + module CLI + # An action of the CLI that prints out the AST for the given source. + class AST + def run(filepath, source) + pp SyntaxTree.parse(source) + end + end + + # An action of the CLI that formats the source twice to check if the first + # format is not idempotent. + class Check + def run(filepath, source) + formatted = SyntaxTree.format(source) + raise if formatted != SyntaxTree.format(formatted) + end + end + + # An action of the CLI that prints out the doc tree IR for the given source. + class Doc + def run(filepath, source) + formatter = Formatter.new([]) + SyntaxTree.parse(source).format(formatter) + pp formatter.groups.first + end + end + + # An action of the CLI that formats the input source and prints it out. + class Format + def run(filepath, source) + puts SyntaxTree.format(source) + end + end + + # An action of the CLI that formats the input source and writes the + # formatted output back to the file. + class Write + def run(filepath, source) + File.write(filepath, SyntaxTree.format(source)) + end + end + + # The help message displayed if the input arguments are not correctly + # ordered or formatted. + HELP = <<~HELP + stree MODE FILE + + MODE: ast | check | doc | format | write + FILE: one or more paths to files to parse + HELP + + class << self + # Run the CLI over the given array of strings that make up the arguments + # passed to the invocation. + def run(argv) + if argv.length < 2 + warn(HELP) + return 1 + end + + arg, *patterns = argv + action = + case arg + when "a", "ast" + AST.new + when "c", "check" + Check.new + when "d", "doc" + Doc.new + when "f", "format" + Format.new + when "w", "write" + Write.new + else + warn(HELP) + return 1 + end + + errored = false + patterns.each do |pattern| + Dir.glob(pattern).each do |filepath| + errored |= run_for(action, filepath) if File.file?(filepath) + end + end + + errored ? 1 : 0 + end + + private + + def source_for(filepath) + encoding = + File.open(filepath, "r") do |file| + header = file.readline + header += file.readline if header.start_with?("#!") + Ripper.new(header).tap(&:parse).encoding + end + + File.read(filepath, encoding: encoding) + end + + def run_for(action, filepath) + action.run(filepath, source_for(filepath)) + false + rescue => error + warn("!!! Failed on #{filepath}") + warn(error.message) + warn(error.backtrace) + true + end + end + end +end From 8686c194e56891620c39b5ddff1a2e5685808707 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 11:18:49 -0500 Subject: [PATCH 63/67] Better output formatting for the CLI. --- CHANGELOG.md | 1 + lib/syntax_tree/cli.rb | 95 +++++++++++++++++++++++++++++++++--------- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e22bede..d63b82ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Support singleton single-line method definitions. - Support `stree-ignore` comments to ignore formatting nodes. - Add special formatting for arrays of `VarRef` nodes whose sum width is greater than 2 * the maximum width. +- Better output formatting for the CLI. ### Changed diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index f30217e6..de7b639b 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -2,8 +2,36 @@ class SyntaxTree module CLI + # A utility wrapper around colored strings in the output. + class ColoredString + COLORS = { default: "0", gray: "38;5;102", yellow: "33" } + + attr_reader :code, :string + + def initialize(color, string) + @code = COLORS[color] + @string = string + end + + def to_s + "\033[#{code}m#{string}\033[0m" + end + end + + # The parent action class for the CLI that implements the basics. + class Action + def run(filepath, source) + end + + def success + end + + def failure + end + end + # An action of the CLI that prints out the AST for the given source. - class AST + class AST < Action def run(filepath, source) pp SyntaxTree.parse(source) end @@ -11,15 +39,26 @@ def run(filepath, source) # An action of the CLI that formats the source twice to check if the first # format is not idempotent. - class Check + class Check < Action def run(filepath, source) formatted = SyntaxTree.format(source) - raise if formatted != SyntaxTree.format(formatted) + return true if formatted == SyntaxTree.format(formatted) + + puts "[#{ColoredString.new(:yellow, "warn")}] #{filepath}" + false + end + + def success + puts "All files matched expected format." + end + + def failure + warn("The listed files did not match the expected format.") end end # An action of the CLI that prints out the doc tree IR for the given source. - class Doc + class Doc < Action def run(filepath, source) formatter = Formatter.new([]) SyntaxTree.parse(source).format(formatter) @@ -28,7 +67,7 @@ def run(filepath, source) end # An action of the CLI that formats the input source and prints it out. - class Format + class Format < Action def run(filepath, source) puts SyntaxTree.format(source) end @@ -36,9 +75,18 @@ def run(filepath, source) # An action of the CLI that formats the input source and writes the # formatted output back to the file. - class Write + class Write < Action def run(filepath, source) - File.write(filepath, SyntaxTree.format(source)) + print filepath + start = Time.now + + formatted = SyntaxTree.format(source) + File.write(filepath, formatted) + + delta = ((Time.now - start) * 1000).round + color = source == formatted ? :gray : :default + + puts "\r#{ColoredString.new(color, filepath)} #{delta}ms" end end @@ -81,15 +129,32 @@ def run(argv) errored = false patterns.each do |pattern| Dir.glob(pattern).each do |filepath| - errored |= run_for(action, filepath) if File.file?(filepath) + next unless File.file?(filepath) + + begin + action.run(filepath, source_for(filepath)) + rescue => error + warn("!!! Failed on #{filepath}") + warn(error.message) + warn(error.backtrace) + errored = true + end end end - - errored ? 1 : 0 + + if errored + action.failure + 1 + else + action.success + 0 + end end private + # Returns the source from the given filepath taking into account any + # potential magic encoding comments. def source_for(filepath) encoding = File.open(filepath, "r") do |file| @@ -100,16 +165,6 @@ def source_for(filepath) File.read(filepath, encoding: encoding) end - - def run_for(action, filepath) - action.run(filepath, source_for(filepath)) - false - rescue => error - warn("!!! Failed on #{filepath}") - warn(error.message) - warn(error.backtrace) - true - end end end end From 720674f89ccf3705bcf96d57dfba874ebe8a8b7f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 13:36:27 -0500 Subject: [PATCH 64/67] Better error messages in the CLI --- lib/syntax_tree.rb | 38 +++++++++--- lib/syntax_tree/cli.rb | 107 +++++++++++++++++++++++++++------ lib/syntax_tree/prettyprint.rb | 4 +- 3 files changed, 117 insertions(+), 32 deletions(-) diff --git a/lib/syntax_tree.rb b/lib/syntax_tree.rb index b8abe211..fc05cd83 100644 --- a/lib/syntax_tree.rb +++ b/lib/syntax_tree.rb @@ -26,12 +26,14 @@ class SyntaxTree < Ripper # every character in the string is 1 byte in length, so we can just return the # start of the line + the index. class SingleByteString + attr_reader :start + def initialize(start) @start = start end def [](byteindex) - @start + byteindex + start + byteindex end end @@ -40,7 +42,10 @@ def [](byteindex) # an array of indices, such that array[byteindex] will be equal to the index # of the character within the string. class MultiByteString + attr_reader :start, :indices + def initialize(start, line) + @start = start @indices = [] line.each_char.with_index(start) do |char, index| @@ -52,7 +57,7 @@ def initialize(start, line) # there's a BOM at the beginning of the file, which is the reason we need to # compare it to 0 here. def [](byteindex) - @indices[byteindex < 0 ? 0 : byteindex] + indices[byteindex < 0 ? 0 : byteindex] end end @@ -186,6 +191,10 @@ def parents # [Array[ String ]] the list of lines in the source attr_reader :lines + # [Array[ SingleByteString | MultiByteString ]] the list of objects that + # represent the start of each line in character offsets + attr_reader :line_counts + # [Array[ untyped ]] a running list of tokens that have been found in the # source. This list changes a lot as certain nodes will "consume" these tokens # to determine their bounds. @@ -299,7 +308,7 @@ def self.format(source) # this line, then we add the number of columns into this line that we've gone # through. def char_pos - @line_counts[lineno - 1][column] + line_counts[lineno - 1][column] end # As we build up a list of tokens, we'll periodically need to go backwards and @@ -313,7 +322,7 @@ def char_pos # (which would happen to be the innermost keyword). Then the outer one would # only be able to grab the first one. In this way all of the tokens act as # their own stack. - def find_token(type, value = :any, consume: true) + def find_token(type, value = :any, consume: true, location: nil) index = tokens.rindex do |token| token.is_a?(type) && (value == :any || (token.value == value)) @@ -326,8 +335,16 @@ def find_token(type, value = :any, consume: true) # could also be caused by accidentally attempting to consume a token twice # by two different parser event handlers. unless index - message = "Cannot find expected #{value == :any ? type : value}" - raise ParseError.new(message, lineno, column) + token = value == :any ? type.name.split("::", 2).last : value + message = "Cannot find expected #{token}" + + if location + lineno = location.start_line + column = location.start_char - line_counts[lineno - 1].start + raise ParseError.new(message, lineno, column) + else + raise ParseError.new(message, lineno, column) + end end tokens.delete_at(index) @@ -1552,7 +1569,8 @@ def on_array(contents) location: lbracket.location.to(rbracket.location) ) else - tstring_end = find_token(TStringEnd) + tstring_end = + find_token(TStringEnd, location: contents.beginning.location) contents.class.new( beginning: contents.beginning, @@ -5195,7 +5213,7 @@ def on_dyna_symbol(string_content) if find_token(SymBeg, consume: false) # A normal dynamic symbol symbeg = find_token(SymBeg) - tstring_end = find_token(TStringEnd) + tstring_end = find_token(TStringEnd, location: symbeg.location) DynaSymbol.new( quote: symbeg.value, @@ -11291,7 +11309,7 @@ def on_string_literal(string) ) else tstring_beg = find_token(TStringBeg) - tstring_end = find_token(TStringEnd) + tstring_end = find_token(TStringEnd, location: tstring_beg.location) location = Location.new( @@ -13503,7 +13521,7 @@ def on_xstring_literal(xstring) location: heredoc.location ) else - ending = find_token(TStringEnd) + ending = find_token(TStringEnd, location: xstring.location) XStringLiteral.new( parts: xstring.parts, diff --git a/lib/syntax_tree/cli.rb b/lib/syntax_tree/cli.rb index de7b639b..b57b84c4 100644 --- a/lib/syntax_tree/cli.rb +++ b/lib/syntax_tree/cli.rb @@ -3,18 +3,28 @@ class SyntaxTree module CLI # A utility wrapper around colored strings in the output. - class ColoredString - COLORS = { default: "0", gray: "38;5;102", yellow: "33" } + class Color + attr_reader :value, :code - attr_reader :code, :string - - def initialize(color, string) - @code = COLORS[color] - @string = string + def initialize(value, code) + @value = value + @code = code end def to_s - "\033[#{code}m#{string}\033[0m" + "\033[#{code}m#{value}\033[0m" + end + + def self.gray(value) + new(value, "38;5;102") + end + + def self.red(value) + new(value, "1;31") + end + + def self.yellow(value) + new(value, "33") end end @@ -37,23 +47,52 @@ def run(filepath, source) end end + # An action of the CLI that ensures that the filepath is formatted as + # expected. + class Check < Action + class UnformattedError < StandardError + end + + def run(filepath, source) + raise UnformattedError if source != SyntaxTree.format(source) + rescue + warn("[#{Color.yellow("warn")}] #{filepath}") + raise + end + + def success + puts("All files matched expected format.") + end + + def failure + warn("The listed files did not match the expected format.") + end + end + # An action of the CLI that formats the source twice to check if the first # format is not idempotent. - class Check < Action + class Debug < Action + class NonIdempotentFormatError < StandardError + end + def run(filepath, source) + warning = "[#{Color.yellow("warn")}] #{filepath}" formatted = SyntaxTree.format(source) - return true if formatted == SyntaxTree.format(formatted) - puts "[#{ColoredString.new(:yellow, "warn")}] #{filepath}" - false + if formatted != SyntaxTree.format(formatted) + raise NonIdempotentFormatError + end + rescue + warn(warning) + raise end def success - puts "All files matched expected format." + puts("All files can be formatted idempotently.") end def failure - warn("The listed files did not match the expected format.") + warn("The listed files could not be formatted idempotently.") end end @@ -83,10 +122,10 @@ def run(filepath, source) formatted = SyntaxTree.format(source) File.write(filepath, formatted) + color = source == formatted ? Color.gray(filepath) : filepath delta = ((Time.now - start) * 1000).round - color = source == formatted ? :gray : :default - puts "\r#{ColoredString.new(color, filepath)} #{delta}ms" + puts "\r#{color} #{delta}ms" end end @@ -95,7 +134,7 @@ def run(filepath, source) HELP = <<~HELP stree MODE FILE - MODE: ast | check | doc | format | write + MODE: ast | check | debug | doc | format | write FILE: one or more paths to files to parse HELP @@ -115,7 +154,9 @@ def run(argv) AST.new when "c", "check" Check.new - when "d", "doc" + when "debug" + Debug.new + when "doc" Doc.new when "f", "format" Format.new @@ -130,11 +171,37 @@ def run(argv) patterns.each do |pattern| Dir.glob(pattern).each do |filepath| next unless File.file?(filepath) + source = source_for(filepath) begin - action.run(filepath, source_for(filepath)) + action.run(filepath, source) + rescue ParseError => error + warn("Error: #{error.message}") + lines = source.lines + + maximum = [error.lineno + 3, lines.length].min + digits = Math.log10(maximum).ceil + + ([error.lineno - 3, 0].max...maximum).each do |line_index| + line_number = line_index + 1 + + if line_number == error.lineno + part1 = Color.red(">") + part2 = Color.gray("%#{digits}d |" % line_number) + warn("#{part1} #{part2} #{lines[line_index]}") + + part3 = Color.gray(" %#{digits}s |" % " ") + warn("#{part3} #{" " * error.column}#{Color.red("^")}") + else + prefix = Color.gray(" %#{digits}d |" % line_number) + warn("#{prefix} #{lines[line_index]}") + end + end + + errored = true + rescue Check::UnformattedError, Debug::NonIdempotentFormatError + errored = true rescue => error - warn("!!! Failed on #{filepath}") warn(error.message) warn(error.backtrace) errored = true diff --git a/lib/syntax_tree/prettyprint.rb b/lib/syntax_tree/prettyprint.rb index 5a119067..48aeff56 100644 --- a/lib/syntax_tree/prettyprint.rb +++ b/lib/syntax_tree/prettyprint.rb @@ -751,9 +751,9 @@ def flush # This is a special sort used to order the line suffixes by both the # priority set on the line suffix and the index it was in the original # array. - line_suffix_sort = ->(line_suffix) { + line_suffix_sort = ->(line_suffix) do [-line_suffix.last, -line_suffixes.index(line_suffix)] - } + end # This is a linear stack instead of a mutually recursive call defined on # the individual doc nodes for efficiency. From 5a7db2e464d79e8471935cb4511040d77744f961 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 13:43:20 -0500 Subject: [PATCH 65/67] Document the write command --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 6550c1b0..42d82d42 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,13 @@ class MyClass ... ``` +or + +```sh +$ stree write program.rb +program.rb 1ms +``` + ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. From 9f65a3aefe3e24e80c64f594a4586d0ebaaf6686 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 13:44:17 -0500 Subject: [PATCH 66/67] =?UTF-8?q?Bump=20to=20version=201.0.0=20?= =?UTF-8?q?=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 ++++- lib/syntax_tree/version.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d63b82ee..7229020e 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] +## [1.0.0] + ### Added - The ability to "check" formatting by formatting the output of the first format. @@ -80,5 +82,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - 🎉 Initial release! 🎉 -[unreleased]: https://github.com/kddnewton/syntax_tree/compare/v0.1.0...HEAD +[unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/kddnewton/syntax_tree/compare/v0.1.0...v1.0.0 [0.1.0]: https://github.com/kddnewton/syntax_tree/compare/8aa1f5...v0.1.0 diff --git a/lib/syntax_tree/version.rb b/lib/syntax_tree/version.rb index 4c45315d..166791b6 100644 --- a/lib/syntax_tree/version.rb +++ b/lib/syntax_tree/version.rb @@ -3,5 +3,5 @@ require "ripper" class SyntaxTree < Ripper - VERSION = "0.1.0" + VERSION = "1.0.0" end From 62121c1f2cb2f5cf2b0e0b998bad712967285cb6 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Dec 2021 13:45:07 -0500 Subject: [PATCH 67/67] Bump Gemfile.lock --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ccdd3b3a..4094a6c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - syntax_tree (0.1.0) + syntax_tree (1.0.0) GEM remote: https://rubygems.org/