syntax_tree 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 872e3355e589d7a18b916de52fc30d8ac7e52a9c746550082ed8fc503bd178b8
4
- data.tar.gz: d27214567e9b0c9f9cf14b8874e839d7c90d7fec89d9a94c117e4e83028165a2
3
+ metadata.gz: 123ef3ac2fa068f2c1f5d653c1eaf0d57c68ab0c17fc632401f0aead46ed8577
4
+ data.tar.gz: 166fedda529629024fae922db271b4f96294851ea16ea065ffd4f89c2d4671e9
5
5
  SHA512:
6
- metadata.gz: 0d131267335b65e4ebd9c12c86275a64d556a9314a1d509b4a8c3629f553ed06e304abce59faab2b13a64bdd8a825101a3baf52e97e60cde8b5eeba2488621e7
7
- data.tar.gz: 12f8ecf7e54688af0a2bac223320197113012c8b83a1db1135f51bf8192d8012462499f810c05bfb6d8d88f7c59551a467e4104cd12ec4094fd5722c3f940b64
6
+ metadata.gz: 3b3bf4c737c16159b2bac2329e9999b841c88a43e21a783da7df7e691b27e27f5fb85dbfceb3ec97bcd2309993fec7f5b994abc2e71ec4c3c6f3dfc76a2da05e
7
+ data.tar.gz: 62d250a1322985c29f53eb6259e084e3ebb2dd54e4e507250ba2338fdf149d15fe6e4683b8026602d0a8e219935fc91c8d2459a15a31f0a43054970886d7b262
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.1] - 2021-12-09
10
+
11
+ ### Added
12
+
13
+ - [#7](https://github.com/kddnewton/syntax_tree/issues/7) Better formatting for hashes and arrays that are values in hashes.
14
+ - [#9](https://github.com/kddnewton/syntax_tree/issues/9) Special handling for RSpec matchers when nesting `CommandCall` nodes.
15
+ - [#10](https://github.com/kddnewton/syntax_tree/issues/10) Force the maintaining of the modifier forms of conditionals and loops if the statement includes an assignment. Also, for the maintaining of the block form of conditionals and loops if the predicate includes an assignment.
16
+
9
17
  ## [1.1.0] - 2021-12-08
10
18
 
11
19
  ### Added
@@ -89,6 +97,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
89
97
 
90
98
  - 🎉 Initial release! 🎉
91
99
 
92
- [unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.0.0...HEAD
100
+ [unreleased]: https://github.com/kddnewton/syntax_tree/compare/v1.1.1...HEAD
101
+ [1.1.1]: https://github.com/kddnewton/syntax_tree/compare/v1.1.0...v1.1.1
102
+ [1.1.0]: https://github.com/kddnewton/syntax_tree/compare/v1.0.0...v1.1.0
93
103
  [1.0.0]: https://github.com/kddnewton/syntax_tree/compare/v0.1.0...v1.0.0
94
104
  [0.1.0]: https://github.com/kddnewton/syntax_tree/compare/8aa1f5...v0.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- syntax_tree (1.1.0)
4
+ syntax_tree (1.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Build Status](https://github.com/kddnewton/syntax_tree/workflows/Main/badge.svg)](https://github.com/kddnewton/syntax_tree/actions)
4
4
  [![Gem Version](https://img.shields.io/gem/v/syntax_tree.svg)](https://rubygems.org/gems/syntax_tree)
5
5
 
6
- A fast ripper subclass used for parsing and formatting Ruby code.
6
+ A fast Ruby parser and formatter with only standard library dependencies.
7
7
 
8
8
  ## Installation
9
9
 
@@ -55,7 +55,7 @@ class SyntaxTree
55
55
 
56
56
  def run(filepath, source)
57
57
  raise UnformattedError if source != SyntaxTree.format(source)
58
- rescue
58
+ rescue StandardError
59
59
  warn("[#{Color.yellow("warn")}] #{filepath}")
60
60
  raise
61
61
  end
@@ -82,7 +82,7 @@ class SyntaxTree
82
82
  if formatted != SyntaxTree.format(formatted)
83
83
  raise NonIdempotentFormatError
84
84
  end
85
- rescue
85
+ rescue StandardError
86
86
  warn(warning)
87
87
  raise
88
88
  end
@@ -126,7 +126,7 @@ class SyntaxTree
126
126
  delta = ((Time.now - start) * 1000).round
127
127
 
128
128
  puts "\r#{color} #{delta}ms"
129
- rescue
129
+ rescue StandardError
130
130
  puts "\r#{filepath}"
131
131
  raise
132
132
  end
@@ -174,7 +174,7 @@ class SyntaxTree
174
174
  patterns.each do |pattern|
175
175
  Dir.glob(pattern).each do |filepath|
176
176
  next unless File.file?(filepath)
177
- source = source_for(filepath)
177
+ source = SyntaxTree.read(filepath)
178
178
 
179
179
  begin
180
180
  action.run(filepath, source)
@@ -210,19 +210,6 @@ class SyntaxTree
210
210
 
211
211
  private
212
212
 
213
- # Returns the source from the given filepath taking into account any
214
- # potential magic encoding comments.
215
- def source_for(filepath)
216
- encoding =
217
- File.open(filepath, "r") do |file|
218
- header = file.readline
219
- header += file.readline if header.start_with?("#!")
220
- Ripper.new(header).tap(&:parse).encoding
221
- end
222
-
223
- File.read(filepath, encoding: encoding)
224
- end
225
-
226
213
  # Highlights a snippet from a source and parse error.
227
214
  def highlight_error(error, source)
228
215
  lines = source.lines
@@ -113,8 +113,10 @@ class PrettyPrint
113
113
  def pretty_print(q)
114
114
  q.text("breakable")
115
115
 
116
- attributes =
117
- [("force=true" if force?), ("indent=false" unless indent?)].compact
116
+ attributes = [
117
+ ("force=true" if force?),
118
+ ("indent=false" unless indent?)
119
+ ].compact
118
120
 
119
121
  if attributes.any?
120
122
  q.text("(")
@@ -3,5 +3,5 @@
3
3
  require "ripper"
4
4
 
5
5
  class SyntaxTree < Ripper
6
- VERSION = "1.1.0"
6
+ VERSION = "1.1.1"
7
7
  end
data/lib/syntax_tree.rb CHANGED
@@ -133,8 +133,8 @@ class SyntaxTree < Ripper
133
133
  @quote = "\""
134
134
  end
135
135
 
136
- def format(node)
137
- stack << node
136
+ def format(node, stackable: true)
137
+ stack << node if stackable
138
138
  doc = nil
139
139
 
140
140
  # If there are comments, then we're going to format them around the node
@@ -168,7 +168,7 @@ class SyntaxTree < Ripper
168
168
  doc = node.format(self)
169
169
  end
170
170
 
171
- stack.pop
171
+ stack.pop if stackable
172
172
  doc
173
173
  end
174
174
 
@@ -294,6 +294,19 @@ class SyntaxTree < Ripper
294
294
  output.join
295
295
  end
296
296
 
297
+ # Returns the source from the given filepath taking into account any potential
298
+ # magic encoding comments.
299
+ def self.read(filepath)
300
+ encoding =
301
+ File.open(filepath, "r") do |file|
302
+ header = file.readline
303
+ header += file.readline if header.start_with?("#!")
304
+ Ripper.new(header).tap(&:parse).encoding
305
+ end
306
+
307
+ File.read(filepath, encoding: encoding)
308
+ end
309
+
297
310
  private
298
311
 
299
312
  # ----------------------------------------------------------------------------
@@ -763,11 +776,11 @@ class SyntaxTree < Ripper
763
776
 
764
777
  q.group do
765
778
  q.text(keyword)
766
- q.format(left_argument)
779
+ q.format(left_argument, stackable: false)
767
780
  q.group do
768
781
  q.nest(keyword.length) do
769
782
  q.breakable(force: left_argument.comments.any?)
770
- q.format(AliasArgumentFormatter.new(right))
783
+ q.format(AliasArgumentFormatter.new(right), stackable: false)
771
784
  end
772
785
  end
773
786
  end
@@ -1654,7 +1667,7 @@ class SyntaxTree < Ripper
1654
1667
  end
1655
1668
 
1656
1669
  def child_nodes
1657
- [constant, *required, rest, *posts]
1670
+ [constant, *requireds, rest, *posts]
1658
1671
  end
1659
1672
 
1660
1673
  def format(q)
@@ -1742,6 +1755,23 @@ class SyntaxTree < Ripper
1742
1755
  )
1743
1756
  end
1744
1757
 
1758
+ # Determins if the following value should be indented or not.
1759
+ module AssignFormatting
1760
+ def self.skip_indent?(value)
1761
+ (value.is_a?(Call) && skip_indent?(value.receiver)) ||
1762
+ [
1763
+ ArrayLiteral,
1764
+ HashLiteral,
1765
+ Heredoc,
1766
+ Lambda,
1767
+ QSymbols,
1768
+ QWords,
1769
+ Symbols,
1770
+ Words
1771
+ ].include?(value.class)
1772
+ end
1773
+ end
1774
+
1745
1775
  # Assign represents assigning something to a variable or constant. Generally,
1746
1776
  # the left side of the assignment is going to be any node that ends with the
1747
1777
  # name "Field".
@@ -1778,7 +1808,7 @@ class SyntaxTree < Ripper
1778
1808
  q.format(target)
1779
1809
  q.text(" =")
1780
1810
 
1781
- if target.comments.empty? && (skip_indent_target? || skip_indent_value?)
1811
+ if skip_indent?
1782
1812
  q.text(" ")
1783
1813
  q.format(value)
1784
1814
  else
@@ -1816,21 +1846,9 @@ class SyntaxTree < Ripper
1816
1846
 
1817
1847
  private
1818
1848
 
1819
- def skip_indent_target?
1820
- target.is_a?(ARefField)
1821
- end
1822
-
1823
- def skip_indent_value?
1824
- [
1825
- ArrayLiteral,
1826
- HashLiteral,
1827
- Heredoc,
1828
- Lambda,
1829
- QSymbols,
1830
- QWords,
1831
- Symbols,
1832
- Words
1833
- ].any? { |type| value.is_a?(type) }
1849
+ def skip_indent?
1850
+ target.comments.empty? &&
1851
+ (target.is_a?(ARefField) || AssignFormatting.skip_indent?(value))
1834
1852
  end
1835
1853
  end
1836
1854
 
@@ -1878,15 +1896,11 @@ class SyntaxTree < Ripper
1878
1896
  end
1879
1897
 
1880
1898
  def format(q)
1881
- contents = -> do
1882
- q.parent.format_key(q, key)
1883
- q.indent do
1884
- q.breakable
1885
- q.format(value)
1886
- end
1899
+ if value.is_a?(HashLiteral)
1900
+ format_contents(q)
1901
+ else
1902
+ q.group { format_contents(q) }
1887
1903
  end
1888
-
1889
- value.is_a?(HashLiteral) ? contents.call : q.group(&contents)
1890
1904
  end
1891
1905
 
1892
1906
  def pretty_print(q)
@@ -1912,6 +1926,22 @@ class SyntaxTree < Ripper
1912
1926
  cmts: comments
1913
1927
  }.to_json(*opts)
1914
1928
  end
1929
+
1930
+ private
1931
+
1932
+ def format_contents(q)
1933
+ q.parent.format_key(q, key)
1934
+
1935
+ if key.comments.empty? && AssignFormatting.skip_indent?(value)
1936
+ q.text(" ")
1937
+ q.format(value)
1938
+ else
1939
+ q.indent do
1940
+ q.breakable
1941
+ q.format(value)
1942
+ end
1943
+ end
1944
+ end
1915
1945
  end
1916
1946
 
1917
1947
  # :call-seq:
@@ -2100,25 +2130,8 @@ class SyntaxTree < Ripper
2100
2130
  # This module is responsible for formatting the assocs contained within a
2101
2131
  # hash or bare hash. It first determines if every key in the hash can use
2102
2132
  # labels. If it can, it uses labels. Otherwise it uses hash rockets.
2103
- module HashFormatter
2104
- class Base
2105
- # [HashLiteral | BareAssocHash] the source of the assocs
2106
- attr_reader :container
2107
-
2108
- def initialize(container)
2109
- @container = container
2110
- end
2111
-
2112
- def comments
2113
- container.comments
2114
- end
2115
-
2116
- def format(q)
2117
- q.seplist(container.assocs) { |assoc| q.format(assoc) }
2118
- end
2119
- end
2120
-
2121
- class Labels < Base
2133
+ module HashKeyFormatter
2134
+ class Labels
2122
2135
  def format_key(q, key)
2123
2136
  case key
2124
2137
  when Label
@@ -2133,7 +2146,7 @@ class SyntaxTree < Ripper
2133
2146
  end
2134
2147
  end
2135
2148
 
2136
- class Rockets < Base
2149
+ class Rockets
2137
2150
  def format_key(q, key)
2138
2151
  case key
2139
2152
  when Label
@@ -2173,7 +2186,7 @@ class SyntaxTree < Ripper
2173
2186
  end
2174
2187
  end
2175
2188
 
2176
- (labels ? Labels : Rockets).new(container)
2189
+ (labels ? Labels : Rockets).new
2177
2190
  end
2178
2191
  end
2179
2192
 
@@ -2204,7 +2217,11 @@ class SyntaxTree < Ripper
2204
2217
  end
2205
2218
 
2206
2219
  def format(q)
2207
- q.format(HashFormatter.for(self))
2220
+ q.seplist(assocs) { |assoc| q.format(assoc) }
2221
+ end
2222
+
2223
+ def format_key(q, key)
2224
+ (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key)
2208
2225
  end
2209
2226
 
2210
2227
  def pretty_print(q)
@@ -2887,7 +2904,7 @@ class SyntaxTree < Ripper
2887
2904
 
2888
2905
  def format_break(q, opening, closing)
2889
2906
  q.text(" ")
2890
- q.format(BlockOpenFormatter.new(opening, block_open))
2907
+ q.format(BlockOpenFormatter.new(opening, block_open), stackable: false)
2891
2908
 
2892
2909
  if node.block_var
2893
2910
  q.text(" ")
@@ -2907,7 +2924,7 @@ class SyntaxTree < Ripper
2907
2924
 
2908
2925
  def format_flat(q, opening, closing)
2909
2926
  q.text(" ")
2910
- q.format(BlockOpenFormatter.new(opening, block_open))
2927
+ q.format(BlockOpenFormatter.new(opening, block_open), stackable: false)
2911
2928
 
2912
2929
  if node.block_var
2913
2930
  q.breakable
@@ -3220,7 +3237,11 @@ class SyntaxTree < Ripper
3220
3237
  if receiver.comments.any? || call_operator.comments.any?
3221
3238
  q.breakable(force: true)
3222
3239
  end
3223
- q.format(call_operator) if call_operator.comments.empty?
3240
+
3241
+ if call_operator.comments.empty?
3242
+ q.format(call_operator, stackable: false)
3243
+ end
3244
+
3224
3245
  q.format(message) if message != :call
3225
3246
  end
3226
3247
 
@@ -3768,19 +3789,13 @@ class SyntaxTree < Ripper
3768
3789
  doc =
3769
3790
  q.nest(0) do
3770
3791
  q.format(receiver)
3771
- q.format(CallOperatorFormatter.new(operator))
3792
+ q.format(CallOperatorFormatter.new(operator), stackable: false)
3772
3793
  q.format(message)
3773
3794
  end
3774
3795
 
3775
3796
  if arguments
3776
- width = doc_width(doc) + 1
3777
3797
  q.text(" ")
3778
-
3779
- if width > (q.maxwidth / 2)
3780
- q.format(arguments)
3781
- else
3782
- q.nest(width) { q.format(arguments) }
3783
- end
3798
+ q.nest(argument_alignment(q, doc)) { q.format(arguments) }
3784
3799
  end
3785
3800
  end
3786
3801
  end
@@ -3845,6 +3860,28 @@ class SyntaxTree < Ripper
3845
3860
 
3846
3861
  width
3847
3862
  end
3863
+
3864
+ def argument_alignment(q, doc)
3865
+ # Very special handling case for rspec matchers. In general with rspec
3866
+ # matchers you expect to see something like:
3867
+ #
3868
+ # expect(foo).to receive(:bar).with(
3869
+ # 'one',
3870
+ # 'two',
3871
+ # 'three',
3872
+ # 'four',
3873
+ # 'five'
3874
+ # )
3875
+ #
3876
+ # In this case the arguments are aligned to the left side as opposed to
3877
+ # being aligned with the `receive` call.
3878
+ if %w[to not_to to_not].include?(message.value)
3879
+ 0
3880
+ else
3881
+ width = doc_width(doc) + 1
3882
+ width > (q.maxwidth / 2) ? 0 : width
3883
+ end
3884
+ end
3848
3885
  end
3849
3886
 
3850
3887
  # :call-seq:
@@ -4422,7 +4459,7 @@ class SyntaxTree < Ripper
4422
4459
 
4423
4460
  if target
4424
4461
  q.format(target)
4425
- q.format(CallOperatorFormatter.new(operator))
4462
+ q.format(CallOperatorFormatter.new(operator), stackable: false)
4426
4463
  end
4427
4464
 
4428
4465
  q.format(name)
@@ -4657,7 +4694,7 @@ class SyntaxTree < Ripper
4657
4694
  q.group do
4658
4695
  q.text("def ")
4659
4696
  q.format(target)
4660
- q.format(CallOperatorFormatter.new(operator))
4697
+ q.format(CallOperatorFormatter.new(operator), stackable: false)
4661
4698
  q.format(name)
4662
4699
  q.format(params) if !params.is_a?(Params) || !params.empty?
4663
4700
  end
@@ -5875,7 +5912,7 @@ class SyntaxTree < Ripper
5875
5912
  def format(q)
5876
5913
  q.group do
5877
5914
  q.format(parent)
5878
- q.format(CallOperatorFormatter.new(operator))
5915
+ q.format(CallOperatorFormatter.new(operator), stackable: false)
5879
5916
  q.format(name)
5880
5917
  end
5881
5918
  end
@@ -6292,23 +6329,15 @@ class SyntaxTree < Ripper
6292
6329
  end
6293
6330
 
6294
6331
  def format(q)
6295
- contents = -> do
6296
- q.format(lbrace)
6297
-
6298
- if assocs.empty?
6299
- q.breakable("")
6300
- else
6301
- q.indent do
6302
- q.breakable
6303
- q.format(HashFormatter.for(self))
6304
- end
6305
- q.breakable
6306
- end
6307
-
6308
- q.text("}")
6332
+ if q.parent.is_a?(Assoc)
6333
+ format_contents(q)
6334
+ else
6335
+ q.group { format_contents(q) }
6309
6336
  end
6337
+ end
6310
6338
 
6311
- q.parent.is_a?(Assoc) ? contents.call : q.group(&contents)
6339
+ def format_key(q, key)
6340
+ (@key_formatter ||= HashKeyFormatter.for(self)).format_key(q, key)
6312
6341
  end
6313
6342
 
6314
6343
  def pretty_print(q)
@@ -6329,6 +6358,24 @@ class SyntaxTree < Ripper
6329
6358
  *opts
6330
6359
  )
6331
6360
  end
6361
+
6362
+ private
6363
+
6364
+ def format_contents(q)
6365
+ q.format(lbrace)
6366
+
6367
+ if assocs.empty?
6368
+ q.breakable("")
6369
+ else
6370
+ q.indent do
6371
+ q.breakable
6372
+ q.seplist(assocs) { |assoc| q.format(assoc) }
6373
+ end
6374
+ q.breakable
6375
+ end
6376
+
6377
+ q.text("}")
6378
+ end
6332
6379
  end
6333
6380
 
6334
6381
  # :call-seq:
@@ -6551,12 +6598,6 @@ class SyntaxTree < Ripper
6551
6598
  @value = value
6552
6599
  end
6553
6600
 
6554
- # This is here so that when checking if its contained within a parent
6555
- # pattern that it will return true.
6556
- def class
6557
- HshPtn
6558
- end
6559
-
6560
6601
  def comments
6561
6602
  []
6562
6603
  end
@@ -6620,7 +6661,9 @@ class SyntaxTree < Ripper
6620
6661
  def format(q)
6621
6662
  parts = keywords.map { |(key, value)| KeywordFormatter.new(key, value) }
6622
6663
  parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
6623
- contents = -> { q.seplist(parts) { |part| q.format(part) } }
6664
+ contents = -> do
6665
+ q.seplist(parts) { |part| q.format(part, stackable: false) }
6666
+ end
6624
6667
 
6625
6668
  if constant
6626
6669
  q.format(constant)
@@ -6766,6 +6809,23 @@ class SyntaxTree < Ripper
6766
6809
  )
6767
6810
  end
6768
6811
 
6812
+ # If the predicate of a conditional or loop contains an assignment (in which
6813
+ # case we can't know for certain that that assignment doesn't impact the
6814
+ # statements inside the conditional) then we can't use the modifier form
6815
+ # and we must use the block form.
6816
+ module ContainsAssignment
6817
+ def self.call(parent)
6818
+ queue = [parent]
6819
+
6820
+ while node = queue.shift
6821
+ return true if [Assign, MAssign, OpAssign].include?(node.class)
6822
+ queue += node.child_nodes
6823
+ end
6824
+
6825
+ false
6826
+ end
6827
+ end
6828
+
6769
6829
  # Formats an If or Unless node.
6770
6830
  class ConditionalFormatter
6771
6831
  # [String] the keyword associated with this conditional
@@ -6784,7 +6844,7 @@ class SyntaxTree < Ripper
6784
6844
  # case we can't know for certain that that assignment doesn't impact the
6785
6845
  # statements inside the conditional) then we can't use the modifier form
6786
6846
  # and we must use the block form.
6787
- if [Assign, MAssign, OpAssign].include?(node.predicate.class)
6847
+ if ContainsAssignment.call(node.predicate)
6788
6848
  format_break(q, force: true)
6789
6849
  return
6790
6850
  end
@@ -7060,23 +7120,31 @@ class SyntaxTree < Ripper
7060
7120
  end
7061
7121
 
7062
7122
  def format(q)
7063
- q.group do
7064
- q.if_break do
7065
- q.text("#{keyword} ")
7066
- q.nest(keyword.length + 1) { q.format(node.predicate) }
7067
- q.indent do
7068
- q.breakable
7069
- q.format(node.statement)
7070
- end
7071
- q.breakable
7072
- q.text("end")
7073
- end.if_flat do
7074
- Parentheses.flat(q) do
7075
- q.format(node.statement)
7076
- q.text(" #{keyword} ")
7077
- q.format(node.predicate)
7078
- end
7079
- end
7123
+ if ContainsAssignment.call(node.statement)
7124
+ q.group { format_flat(q) }
7125
+ else
7126
+ q.group { q.if_break { format_break(q) }.if_flat { format_flat(q) } }
7127
+ end
7128
+ end
7129
+
7130
+ private
7131
+
7132
+ def format_break(q)
7133
+ q.text("#{keyword} ")
7134
+ q.nest(keyword.length + 1) { q.format(node.predicate) }
7135
+ q.indent do
7136
+ q.breakable
7137
+ q.format(node.statement)
7138
+ end
7139
+ q.breakable
7140
+ q.text("end")
7141
+ end
7142
+
7143
+ def format_flat(q)
7144
+ Parentheses.flat(q) do
7145
+ q.format(node.statement)
7146
+ q.text(" #{keyword} ")
7147
+ q.format(node.predicate)
7080
7148
  end
7081
7149
  end
7082
7150
  end
@@ -7314,7 +7382,12 @@ class SyntaxTree < Ripper
7314
7382
  beginning = find_token(Kw, "in")
7315
7383
  ending = consequent || find_token(Kw, "end")
7316
7384
 
7317
- statements_start = find_token(Kw, "then", consume: false) || pattern
7385
+ statements_start = pattern
7386
+ if token = find_token(Kw, "then", consume: false)
7387
+ tokens.delete(token)
7388
+ statements_start = token
7389
+ end
7390
+
7318
7391
  statements.bind(
7319
7392
  find_next_statement_start(statements_start.location.end_char),
7320
7393
  ending.location.start_char
@@ -7857,6 +7930,10 @@ class SyntaxTree < Ripper
7857
7930
  @comments = comments
7858
7931
  end
7859
7932
 
7933
+ def child_nodes
7934
+ []
7935
+ end
7936
+
7860
7937
  def format(q)
7861
7938
  q.text(value)
7862
7939
  end
@@ -9077,16 +9154,15 @@ class SyntaxTree < Ripper
9077
9154
  keyword_rest,
9078
9155
  block
9079
9156
  )
9080
- parts =
9081
- [
9082
- *requireds,
9083
- *optionals&.flatten(1),
9084
- rest,
9085
- *posts,
9086
- *keywords&.flat_map { |(key, value)| [key, value || nil] },
9087
- (keyword_rest if keyword_rest != :nil),
9088
- block
9089
- ].compact
9157
+ parts = [
9158
+ *requireds,
9159
+ *optionals&.flatten(1),
9160
+ rest,
9161
+ *posts,
9162
+ *keywords&.flat_map { |(key, value)| [key, value || nil] },
9163
+ (keyword_rest if keyword_rest != :nil),
9164
+ block
9165
+ ].compact
9090
9166
 
9091
9167
  location =
9092
9168
  if parts.any?
@@ -11204,8 +11280,10 @@ class SyntaxTree < Ripper
11204
11280
  Location.new(
11205
11281
  start_line: embexpr_beg.location.start_line,
11206
11282
  start_char: embexpr_beg.location.start_char,
11207
- end_line:
11208
- [embexpr_end.location.end_line, statements.location.end_line].max,
11283
+ end_line: [
11284
+ embexpr_end.location.end_line,
11285
+ statements.location.end_line
11286
+ ].max,
11209
11287
  end_char: embexpr_end.location.end_char
11210
11288
  )
11211
11289
 
@@ -11315,8 +11393,10 @@ class SyntaxTree < Ripper
11315
11393
  Location.new(
11316
11394
  start_line: tstring_beg.location.start_line,
11317
11395
  start_char: tstring_beg.location.start_char,
11318
- end_line:
11319
- [tstring_end.location.end_line, string.location.end_line].max,
11396
+ end_line: [
11397
+ tstring_end.location.end_line,
11398
+ string.location.end_line
11399
+ ].max,
11320
11400
  end_char: tstring_end.location.end_char
11321
11401
  )
11322
11402
 
@@ -12418,11 +12498,7 @@ class SyntaxTree < Ripper
12418
12498
  end
12419
12499
 
12420
12500
  def format(q)
12421
- # If the predicate of the loop contains an assignment (in which case we
12422
- # can't know for certain that that assignment doesn't impact the
12423
- # statements inside the loop) then we can't use the modifier form and we
12424
- # must use the block form.
12425
- if [Assign, MAssign, OpAssign].include?(node.predicate.class)
12501
+ if ContainsAssignment.call(node.predicate)
12426
12502
  format_break(q)
12427
12503
  q.break_parent
12428
12504
  return
@@ -12583,8 +12659,13 @@ class SyntaxTree < Ripper
12583
12659
  # foo
12584
12660
  # end until bar
12585
12661
  #
12586
- # The above is effectively a `do...until` loop.
12587
- if statement.is_a?(Begin)
12662
+ # Also, if the statement of the modifier includes an assignment, then we
12663
+ # can't know for certain that it won't impact the predicate, so we need to
12664
+ # force it to stay as it is. This looks like:
12665
+ #
12666
+ # foo = bar until foo
12667
+ #
12668
+ if statement.is_a?(Begin) || ContainsAssignment.call(statement)
12588
12669
  q.format(statement)
12589
12670
  q.text(" until ")
12590
12671
  q.format(predicate)
@@ -13027,7 +13108,12 @@ class SyntaxTree < Ripper
13027
13108
  beginning = find_token(Kw, "when")
13028
13109
  ending = consequent || find_token(Kw, "end")
13029
13110
 
13030
- statements_start = find_token(Kw, "then", consume: false) || arguments
13111
+ statements_start = arguments
13112
+ if token = find_token(Kw, "then", consume: false)
13113
+ tokens.delete(token)
13114
+ statements_start = token
13115
+ end
13116
+
13031
13117
  statements.bind(
13032
13118
  find_next_statement_start(statements_start.location.end_char),
13033
13119
  ending.location.start_char
@@ -13171,8 +13257,13 @@ class SyntaxTree < Ripper
13171
13257
  # foo
13172
13258
  # end while bar
13173
13259
  #
13174
- # The above is effectively a `do...while` loop.
13175
- if statement.is_a?(Begin)
13260
+ # Also, if the statement of the modifier includes an assignment, then we
13261
+ # can't know for certain that it won't impact the predicate, so we need to
13262
+ # force it to stay as it is. This looks like:
13263
+ #
13264
+ # foo = bar while foo
13265
+ #
13266
+ if statement.is_a?(Begin) || ContainsAssignment.call(statement)
13176
13267
  q.format(statement)
13177
13268
  q.text(" while ")
13178
13269
  q.format(predicate)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: syntax_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-08 00:00:00.000000000 Z
11
+ date: 2021-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler