syntax_tree 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -1
- data/Gemfile +0 -5
- data/Gemfile.lock +3 -15
- data/README.md +21 -0
- data/bin/bench +11 -6
- data/bin/profile +16 -7
- data/lib/syntax_tree/formatter.rb +8 -1
- data/lib/syntax_tree/node.rb +661 -97
- data/lib/syntax_tree/parser.rb +110 -27
- data/lib/syntax_tree/prettyprint.rb +25 -13
- data/lib/syntax_tree/version.rb +1 -1
- metadata +2 -2
data/lib/syntax_tree/parser.rb
CHANGED
@@ -414,12 +414,19 @@ module SyntaxTree
|
|
414
414
|
# (false | untyped) block
|
415
415
|
# ) -> Args
|
416
416
|
def on_args_add_block(arguments, block)
|
417
|
+
# First, see if there is an & operator that could potentially be
|
418
|
+
# associated with the block part of this args_add_block. If there is not,
|
419
|
+
# then just return the arguments.
|
417
420
|
operator = find_token(Op, "&", consume: false)
|
418
|
-
|
419
|
-
# If we can't find the & operator, then there's no block to add to the
|
420
|
-
# list, so we're just going to return the arguments as-is.
|
421
421
|
return arguments unless operator
|
422
422
|
|
423
|
+
# If there are any arguments and the operator we found from the list is
|
424
|
+
# not after them, then we're going to return the arguments as-is because
|
425
|
+
# we're looking at an & that occurs before the arguments are done.
|
426
|
+
if arguments.parts.any? && operator.location.start_char < arguments.location.end_char
|
427
|
+
return arguments
|
428
|
+
end
|
429
|
+
|
423
430
|
# Now we know we have an & operator, so we're going to delete it from the
|
424
431
|
# list of tokens to make sure it doesn't get confused with anything else.
|
425
432
|
tokens.delete(operator)
|
@@ -428,13 +435,6 @@ module SyntaxTree
|
|
428
435
|
location = operator.location
|
429
436
|
location = operator.location.to(block.location) if block
|
430
437
|
|
431
|
-
# If there are any arguments and the operator we found from the list is
|
432
|
-
# not after them, then we're going to return the arguments as-is because
|
433
|
-
# we're looking at an & that occurs before the arguments are done.
|
434
|
-
if arguments.parts.any? && location.start_char < arguments.location.end_char
|
435
|
-
return arguments
|
436
|
-
end
|
437
|
-
|
438
438
|
# Otherwise, we're looking at an actual block argument (with or without a
|
439
439
|
# block, which could be missing because it could be a bare & since 3.1.0).
|
440
440
|
arg_block = ArgBlock.new(value: block, location: location)
|
@@ -516,12 +516,46 @@ module SyntaxTree
|
|
516
516
|
def on_aryptn(constant, requireds, rest, posts)
|
517
517
|
parts = [constant, *requireds, rest, *posts].compact
|
518
518
|
|
519
|
+
# If there aren't any parts (no constant, no positional arguments), then
|
520
|
+
# we're matching an empty array. In this case, we're going to look for the
|
521
|
+
# left and right brackets explicitly. Otherwise, we'll just use the bounds
|
522
|
+
# of the various parts.
|
523
|
+
location =
|
524
|
+
if parts.empty?
|
525
|
+
find_token(LBracket).location.to(find_token(RBracket).location)
|
526
|
+
else
|
527
|
+
parts[0].location.to(parts[-1].location)
|
528
|
+
end
|
529
|
+
|
530
|
+
# If there's the optional then keyword, then we'll delete that and use it
|
531
|
+
# as the end bounds of the location.
|
532
|
+
if token = find_token(Kw, "then", consume: false)
|
533
|
+
tokens.delete(token)
|
534
|
+
location = location.to(token.location)
|
535
|
+
end
|
536
|
+
|
537
|
+
# If there is a plain *, then we're going to fix up the location of it
|
538
|
+
# here because it currently doesn't have anything to use for its precise
|
539
|
+
# location. If we hit a comma, then we've gone too far.
|
540
|
+
if rest.is_a?(VarField) && rest.value.nil?
|
541
|
+
tokens.rindex do |token|
|
542
|
+
case token
|
543
|
+
in Op[value: "*"]
|
544
|
+
rest = VarField.new(value: nil, location: token.location)
|
545
|
+
break
|
546
|
+
in Comma
|
547
|
+
break
|
548
|
+
else
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
519
553
|
AryPtn.new(
|
520
554
|
constant: constant,
|
521
555
|
requireds: requireds || [],
|
522
556
|
rest: rest,
|
523
557
|
posts: posts || [],
|
524
|
-
location:
|
558
|
+
location: location
|
525
559
|
)
|
526
560
|
end
|
527
561
|
|
@@ -761,8 +795,14 @@ module SyntaxTree
|
|
761
795
|
# (:call | Backtick | Const | Ident | Op) message
|
762
796
|
# ) -> Call
|
763
797
|
def on_call(receiver, operator, message)
|
764
|
-
ending =
|
765
|
-
|
798
|
+
ending =
|
799
|
+
if message != :call
|
800
|
+
message
|
801
|
+
elsif operator != :"::"
|
802
|
+
operator
|
803
|
+
else
|
804
|
+
receiver
|
805
|
+
end
|
766
806
|
|
767
807
|
Call.new(
|
768
808
|
receiver: receiver,
|
@@ -1367,15 +1407,35 @@ module SyntaxTree
|
|
1367
1407
|
# VarField right
|
1368
1408
|
# ) -> FndPtn
|
1369
1409
|
def on_fndptn(constant, left, values, right)
|
1370
|
-
|
1371
|
-
|
1410
|
+
# The opening of this find pattern is either going to be a left bracket, a
|
1411
|
+
# right left parenthesis, or the left splat. We're going to use this to
|
1412
|
+
# determine how to find the closing of the pattern, as well as determining
|
1413
|
+
# the location of the node.
|
1414
|
+
opening =
|
1415
|
+
find_token(LBracket, consume: false) ||
|
1416
|
+
find_token(LParen, consume: false) ||
|
1417
|
+
left
|
1418
|
+
|
1419
|
+
# The closing is based on the opening, which is either the matched
|
1420
|
+
# punctuation or the right splat.
|
1421
|
+
closing =
|
1422
|
+
case opening
|
1423
|
+
in LBracket
|
1424
|
+
tokens.delete(opening)
|
1425
|
+
find_token(RBracket)
|
1426
|
+
in LParen
|
1427
|
+
tokens.delete(opening)
|
1428
|
+
find_token(RParen)
|
1429
|
+
else
|
1430
|
+
right
|
1431
|
+
end
|
1372
1432
|
|
1373
1433
|
FndPtn.new(
|
1374
1434
|
constant: constant,
|
1375
1435
|
left: left,
|
1376
1436
|
values: values,
|
1377
1437
|
right: right,
|
1378
|
-
location:
|
1438
|
+
location: (constant || opening).location.to(closing.location)
|
1379
1439
|
)
|
1380
1440
|
end
|
1381
1441
|
|
@@ -1462,6 +1522,7 @@ module SyntaxTree
|
|
1462
1522
|
@heredocs[-1] = Heredoc.new(
|
1463
1523
|
beginning: heredoc.beginning,
|
1464
1524
|
ending: heredoc.ending,
|
1525
|
+
dedent: width,
|
1465
1526
|
parts: string.parts,
|
1466
1527
|
location: heredoc.location
|
1467
1528
|
)
|
@@ -1475,6 +1536,7 @@ module SyntaxTree
|
|
1475
1536
|
@heredocs[-1] = Heredoc.new(
|
1476
1537
|
beginning: heredoc.beginning,
|
1477
1538
|
ending: value.chomp,
|
1539
|
+
dedent: heredoc.dedent,
|
1478
1540
|
parts: heredoc.parts,
|
1479
1541
|
location:
|
1480
1542
|
Location.new(
|
@@ -1495,13 +1557,30 @@ module SyntaxTree
|
|
1495
1557
|
# (nil | VarField) keyword_rest
|
1496
1558
|
# ) -> HshPtn
|
1497
1559
|
def on_hshptn(constant, keywords, keyword_rest)
|
1498
|
-
|
1560
|
+
# Create an artificial VarField if we find an extra ** on the end
|
1561
|
+
if !keyword_rest && (token = find_token(Op, "**", consume: false))
|
1562
|
+
tokens.delete(token)
|
1563
|
+
keyword_rest = VarField.new(value: nil, location: token.location)
|
1564
|
+
end
|
1565
|
+
|
1566
|
+
# Delete the optional then keyword
|
1567
|
+
if token = find_token(Kw, "then", consume: false)
|
1568
|
+
tokens.delete(token)
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
parts = [constant, *keywords&.flatten(1), keyword_rest].compact
|
1572
|
+
location =
|
1573
|
+
if parts.any?
|
1574
|
+
parts[0].location.to(parts[-1].location)
|
1575
|
+
else
|
1576
|
+
find_token(LBrace).location.to(find_token(RBrace).location)
|
1577
|
+
end
|
1499
1578
|
|
1500
1579
|
HshPtn.new(
|
1501
1580
|
constant: constant,
|
1502
|
-
keywords: keywords,
|
1581
|
+
keywords: keywords || [],
|
1503
1582
|
keyword_rest: keyword_rest,
|
1504
|
-
location:
|
1583
|
+
location: location
|
1505
1584
|
)
|
1506
1585
|
end
|
1507
1586
|
|
@@ -2000,12 +2079,16 @@ module SyntaxTree
|
|
2000
2079
|
keyword_rest,
|
2001
2080
|
block
|
2002
2081
|
)
|
2082
|
+
# This is to make it so that required keyword arguments
|
2083
|
+
# have a `nil` for the value instead of a `false`.
|
2084
|
+
keywords&.map! { |(key, value)| [key, value || nil] }
|
2085
|
+
|
2003
2086
|
parts = [
|
2004
2087
|
*requireds,
|
2005
2088
|
*optionals&.flatten(1),
|
2006
2089
|
rest,
|
2007
2090
|
*posts,
|
2008
|
-
*keywords&.
|
2091
|
+
*keywords&.flatten(1),
|
2009
2092
|
(keyword_rest if keyword_rest != :nil),
|
2010
2093
|
(block if block != :&)
|
2011
2094
|
].compact
|
@@ -2626,6 +2709,7 @@ module SyntaxTree
|
|
2626
2709
|
Heredoc.new(
|
2627
2710
|
beginning: heredoc.beginning,
|
2628
2711
|
ending: heredoc.ending,
|
2712
|
+
dedent: heredoc.dedent,
|
2629
2713
|
parts: string.parts,
|
2630
2714
|
location: heredoc.location
|
2631
2715
|
)
|
@@ -2837,19 +2921,17 @@ module SyntaxTree
|
|
2837
2921
|
# parentheses they don't get reported as a paren node for some reason.
|
2838
2922
|
|
2839
2923
|
beginning = find_token(Kw, "not")
|
2840
|
-
ending = statement
|
2841
|
-
|
2842
|
-
range = beginning.location.end_char...statement.location.start_char
|
2843
|
-
paren = source[range].include?("(")
|
2924
|
+
ending = statement || beginning
|
2925
|
+
parentheses = source[beginning.location.end_char] == "("
|
2844
2926
|
|
2845
|
-
if
|
2927
|
+
if parentheses
|
2846
2928
|
find_token(LParen)
|
2847
2929
|
ending = find_token(RParen)
|
2848
2930
|
end
|
2849
2931
|
|
2850
2932
|
Not.new(
|
2851
2933
|
statement: statement,
|
2852
|
-
parentheses:
|
2934
|
+
parentheses: parentheses,
|
2853
2935
|
location: beginning.location.to(ending.location)
|
2854
2936
|
)
|
2855
2937
|
else
|
@@ -2981,7 +3063,7 @@ module SyntaxTree
|
|
2981
3063
|
# ) -> VarField
|
2982
3064
|
def on_var_field(value)
|
2983
3065
|
location =
|
2984
|
-
if value
|
3066
|
+
if value && value != :nil
|
2985
3067
|
value.location
|
2986
3068
|
else
|
2987
3069
|
# You can hit this pattern if you're assigning to a splat using
|
@@ -3180,6 +3262,7 @@ module SyntaxTree
|
|
3180
3262
|
Heredoc.new(
|
3181
3263
|
beginning: heredoc.beginning,
|
3182
3264
|
ending: heredoc.ending,
|
3265
|
+
dedent: heredoc.dedent,
|
3183
3266
|
parts: xstring.parts,
|
3184
3267
|
location: heredoc.location
|
3185
3268
|
)
|
@@ -79,7 +79,7 @@ class PrettyPrint
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def pretty_print(q)
|
82
|
-
q.group(2, "align([", "])") do
|
82
|
+
q.group(2, "align#{indent}([", "])") do
|
83
83
|
q.seplist(contents) { |content| q.pp(content) }
|
84
84
|
end
|
85
85
|
end
|
@@ -161,7 +161,7 @@ class PrettyPrint
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def pretty_print(q)
|
164
|
-
q.group(2, "group([", "])") do
|
164
|
+
q.group(2, break? ? "breakGroup([" : "group([", "])") do
|
165
165
|
q.seplist(contents) { |content| q.pp(content) }
|
166
166
|
end
|
167
167
|
end
|
@@ -458,6 +458,10 @@ class PrettyPrint
|
|
458
458
|
IfBreakBuilder.new
|
459
459
|
end
|
460
460
|
|
461
|
+
# Also effectively unnecessary, but here for compatibility.
|
462
|
+
def if_flat
|
463
|
+
end
|
464
|
+
|
461
465
|
# A noop that immediately yields.
|
462
466
|
def indent
|
463
467
|
yield
|
@@ -759,9 +763,7 @@ class PrettyPrint
|
|
759
763
|
|
760
764
|
# This is a linear stack instead of a mutually recursive call defined on
|
761
765
|
# the individual doc nodes for efficiency.
|
762
|
-
while commands.
|
763
|
-
indent, mode, doc = commands.pop
|
764
|
-
|
766
|
+
while (indent, mode, doc = commands.pop)
|
765
767
|
case doc
|
766
768
|
when Text
|
767
769
|
doc.objects.each { |object| buffer << object }
|
@@ -789,10 +791,10 @@ class PrettyPrint
|
|
789
791
|
end
|
790
792
|
end
|
791
793
|
when IfBreak
|
792
|
-
if mode == MODE_BREAK
|
793
|
-
commands << [indent, mode, doc.break_contents]
|
794
|
-
elsif mode == MODE_FLAT
|
795
|
-
commands << [indent, mode, doc.flat_contents]
|
794
|
+
if mode == MODE_BREAK && doc.break_contents.any?
|
795
|
+
commands << [indent, mode, doc.break_contents]
|
796
|
+
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
797
|
+
commands << [indent, mode, doc.flat_contents]
|
796
798
|
end
|
797
799
|
when LineSuffix
|
798
800
|
line_suffixes << [indent, mode, doc.contents, doc.priority]
|
@@ -1011,6 +1013,16 @@ class PrettyPrint
|
|
1011
1013
|
IfBreakBuilder.new(self, doc)
|
1012
1014
|
end
|
1013
1015
|
|
1016
|
+
# This is similar to if_break in that it also inserts an IfBreak node into the
|
1017
|
+
# print tree, however it's starting from the flat contents, and cannot be used
|
1018
|
+
# to build the break contents.
|
1019
|
+
def if_flat
|
1020
|
+
doc = IfBreak.new
|
1021
|
+
target << doc
|
1022
|
+
|
1023
|
+
with_target(doc.flat_contents) { yield }
|
1024
|
+
end
|
1025
|
+
|
1014
1026
|
# Very similar to the #nest method, this indents the nested content by one
|
1015
1027
|
# level by inserting an Indent node into the print tree. The contents of the
|
1016
1028
|
# node are determined by the block.
|
@@ -1116,10 +1128,10 @@ class PrettyPrint
|
|
1116
1128
|
when Group
|
1117
1129
|
commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents]
|
1118
1130
|
when IfBreak
|
1119
|
-
if mode == MODE_BREAK
|
1120
|
-
commands << [indent, mode, doc.break_contents]
|
1121
|
-
|
1122
|
-
commands << [indent, mode, doc.flat_contents]
|
1131
|
+
if mode == MODE_BREAK && doc.break_contents.any?
|
1132
|
+
commands << [indent, mode, doc.break_contents]
|
1133
|
+
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
1134
|
+
commands << [indent, mode, doc.flat_contents]
|
1123
1135
|
end
|
1124
1136
|
when Breakable
|
1125
1137
|
if mode == MODE_FLAT && !doc.force?
|
data/lib/syntax_tree/version.rb
CHANGED
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: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Newton
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|