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 +4 -4
- data/CHANGELOG.md +11 -1
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/lib/syntax_tree/cli.rb +4 -17
- data/lib/syntax_tree/prettyprint.rb +4 -2
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree.rb +222 -131
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 123ef3ac2fa068f2c1f5d653c1eaf0d57c68ab0c17fc632401f0aead46ed8577
|
4
|
+
data.tar.gz: 166fedda529629024fae922db271b4f96294851ea16ea065ffd4f89c2d4671e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
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
|
6
|
+
A fast Ruby parser and formatter with only standard library dependencies.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
data/lib/syntax_tree/cli.rb
CHANGED
@@ -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 =
|
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
|
-
|
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("(")
|
data/lib/syntax_tree/version.rb
CHANGED
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, *
|
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
|
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
|
1820
|
-
target.
|
1821
|
-
|
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
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
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
|
2104
|
-
class
|
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
|
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
|
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.
|
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
|
-
|
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
|
-
|
6296
|
-
q
|
6297
|
-
|
6298
|
-
|
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
|
-
|
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 = ->
|
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
|
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
|
-
|
7064
|
-
q.
|
7065
|
-
|
7066
|
-
|
7067
|
-
|
7068
|
-
|
7069
|
-
|
7070
|
-
|
7071
|
-
|
7072
|
-
|
7073
|
-
|
7074
|
-
|
7075
|
-
|
7076
|
-
|
7077
|
-
|
7078
|
-
|
7079
|
-
|
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 =
|
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
|
-
|
9083
|
-
|
9084
|
-
|
9085
|
-
|
9086
|
-
|
9087
|
-
|
9088
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
12587
|
-
|
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 =
|
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
|
-
#
|
13175
|
-
|
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.
|
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-
|
11
|
+
date: 2021-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|