syntax_tree 2.4.1 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/CHANGELOG.md +37 -1
- data/Gemfile +0 -2
- data/Gemfile.lock +5 -3
- data/README.md +63 -2
- data/Rakefile +5 -20
- data/lib/syntax_tree/basic_visitor.rb +74 -0
- data/lib/syntax_tree/formatter/trailing_comma.rb +13 -0
- data/lib/syntax_tree/formatter.rb +8 -2
- data/lib/syntax_tree/node.rb +174 -118
- data/lib/syntax_tree/parser.rb +112 -0
- data/lib/syntax_tree/plugin/trailing_comma.rb +4 -0
- data/lib/syntax_tree/rake/check_task.rb +66 -0
- data/lib/syntax_tree/rake/write_task.rb +66 -0
- data/lib/syntax_tree/rake_tasks.rb +4 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/field_visitor.rb +9 -3
- data/lib/syntax_tree/visitor/match_visitor.rb +2 -2
- data/lib/syntax_tree/visitor.rb +4 -66
- data/lib/syntax_tree.rb +5 -27
- data/syntax_tree.gemspec +3 -0
- metadata +36 -3
- data/lib/syntax_tree/prettyprint.rb +0 -1159
data/lib/syntax_tree/node.rb
CHANGED
@@ -123,7 +123,7 @@ module SyntaxTree
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def construct_keys
|
126
|
-
|
126
|
+
PrettierPrint.format(+"") { |q| Visitor::MatchVisitor.new(q).visit(self) }
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -597,10 +597,30 @@ module SyntaxTree
|
|
597
597
|
q.indent do
|
598
598
|
q.breakable("")
|
599
599
|
q.format(arguments)
|
600
|
+
q.if_break { q.text(",") } if q.trailing_comma? && trailing_comma?
|
600
601
|
end
|
601
602
|
q.breakable("")
|
602
603
|
end
|
603
604
|
end
|
605
|
+
|
606
|
+
private
|
607
|
+
|
608
|
+
def trailing_comma?
|
609
|
+
case arguments
|
610
|
+
in Args[parts: [*, ArgBlock]]
|
611
|
+
# If the last argument is a block, then we can't put a trailing comma
|
612
|
+
# after it without resulting in a syntax error.
|
613
|
+
false
|
614
|
+
in Args[parts: [Command | CommandCall]]
|
615
|
+
# If the only argument is a command or command call, then a trailing
|
616
|
+
# comma would be parsed as part of that expression instead of on this
|
617
|
+
# one, so we don't want to add a trailing comma.
|
618
|
+
false
|
619
|
+
else
|
620
|
+
# Otherwise, we should be okay to add a trailing comma.
|
621
|
+
true
|
622
|
+
end
|
623
|
+
end
|
604
624
|
end
|
605
625
|
|
606
626
|
# Args represents a list of arguments being passed to a method call or array
|
@@ -859,6 +879,7 @@ module SyntaxTree
|
|
859
879
|
end
|
860
880
|
|
861
881
|
q.seplist(contents.parts, separator) { |part| q.format(part) }
|
882
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
862
883
|
end
|
863
884
|
q.breakable("")
|
864
885
|
end
|
@@ -954,6 +975,7 @@ module SyntaxTree
|
|
954
975
|
q.indent do
|
955
976
|
q.breakable("")
|
956
977
|
q.format(contents)
|
978
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
957
979
|
end
|
958
980
|
end
|
959
981
|
|
@@ -1109,7 +1131,11 @@ module SyntaxTree
|
|
1109
1131
|
q.group do
|
1110
1132
|
q.format(constant)
|
1111
1133
|
q.text("[")
|
1112
|
-
q.
|
1134
|
+
q.indent do
|
1135
|
+
q.breakable("")
|
1136
|
+
q.seplist(parts) { |part| q.format(part) }
|
1137
|
+
end
|
1138
|
+
q.breakable("")
|
1113
1139
|
q.text("]")
|
1114
1140
|
end
|
1115
1141
|
|
@@ -1119,7 +1145,11 @@ module SyntaxTree
|
|
1119
1145
|
parent = q.parent
|
1120
1146
|
if parts.length == 1 || PATTERNS.include?(parent.class)
|
1121
1147
|
q.text("[")
|
1122
|
-
q.
|
1148
|
+
q.indent do
|
1149
|
+
q.breakable("")
|
1150
|
+
q.seplist(parts) { |part| q.format(part) }
|
1151
|
+
end
|
1152
|
+
q.breakable("")
|
1123
1153
|
q.text("]")
|
1124
1154
|
elsif parts.empty?
|
1125
1155
|
q.text("[]")
|
@@ -1388,7 +1418,7 @@ module SyntaxTree
|
|
1388
1418
|
module HashKeyFormatter
|
1389
1419
|
# Formats the keys of a hash literal using labels.
|
1390
1420
|
class Labels
|
1391
|
-
LABEL =
|
1421
|
+
LABEL = /\A[A-Za-z_](\w*[\w!?])?\z/
|
1392
1422
|
|
1393
1423
|
def format_key(q, key)
|
1394
1424
|
case key
|
@@ -1666,52 +1696,6 @@ module SyntaxTree
|
|
1666
1696
|
end
|
1667
1697
|
end
|
1668
1698
|
|
1669
|
-
# This module will remove any breakables from the list of contents so that no
|
1670
|
-
# newlines are present in the output.
|
1671
|
-
module RemoveBreaks
|
1672
|
-
class << self
|
1673
|
-
def call(doc)
|
1674
|
-
marker = Object.new
|
1675
|
-
stack = [doc]
|
1676
|
-
|
1677
|
-
while stack.any?
|
1678
|
-
doc = stack.pop
|
1679
|
-
|
1680
|
-
if doc == marker
|
1681
|
-
stack.pop
|
1682
|
-
next
|
1683
|
-
end
|
1684
|
-
|
1685
|
-
stack += [doc, marker]
|
1686
|
-
|
1687
|
-
case doc
|
1688
|
-
when PrettyPrint::Align, PrettyPrint::Indent, PrettyPrint::Group
|
1689
|
-
doc.contents.map! { |child| remove_breaks(child) }
|
1690
|
-
stack += doc.contents.reverse
|
1691
|
-
when PrettyPrint::IfBreak
|
1692
|
-
doc.flat_contents.map! { |child| remove_breaks(child) }
|
1693
|
-
stack += doc.flat_contents.reverse
|
1694
|
-
end
|
1695
|
-
end
|
1696
|
-
end
|
1697
|
-
|
1698
|
-
private
|
1699
|
-
|
1700
|
-
def remove_breaks(doc)
|
1701
|
-
case doc
|
1702
|
-
when PrettyPrint::Breakable
|
1703
|
-
text = PrettyPrint::Text.new
|
1704
|
-
text.add(object: doc.force? ? "; " : doc.separator, width: doc.width)
|
1705
|
-
text
|
1706
|
-
when PrettyPrint::IfBreak
|
1707
|
-
PrettyPrint::Align.new(indent: 0, contents: doc.flat_contents)
|
1708
|
-
else
|
1709
|
-
doc
|
1710
|
-
end
|
1711
|
-
end
|
1712
|
-
end
|
1713
|
-
end
|
1714
|
-
|
1715
1699
|
# BlockVar represents the parameters being declared for a block. Effectively
|
1716
1700
|
# this node is everything contained within the pipes. This includes all of the
|
1717
1701
|
# various parameter types, as well as block-local variable declarations.
|
@@ -1752,8 +1736,7 @@ module SyntaxTree
|
|
1752
1736
|
|
1753
1737
|
def format(q)
|
1754
1738
|
q.group(0, "|", "|") do
|
1755
|
-
|
1756
|
-
RemoveBreaks.call(doc)
|
1739
|
+
q.remove_breaks(q.format(params))
|
1757
1740
|
|
1758
1741
|
if locals.any?
|
1759
1742
|
q.text("; ")
|
@@ -2802,10 +2785,17 @@ module SyntaxTree
|
|
2802
2785
|
q.format(value)
|
2803
2786
|
q.text(" ")
|
2804
2787
|
q.format(operator)
|
2805
|
-
|
2806
|
-
|
2807
|
-
|
2808
|
-
|
2788
|
+
|
2789
|
+
case pattern
|
2790
|
+
in AryPtn | FndPtn | HshPtn
|
2791
|
+
q.text(" ")
|
2792
|
+
q.format(pattern)
|
2793
|
+
else
|
2794
|
+
q.group do
|
2795
|
+
q.indent do
|
2796
|
+
q.breakable
|
2797
|
+
q.format(pattern)
|
2798
|
+
end
|
2809
2799
|
end
|
2810
2800
|
end
|
2811
2801
|
end
|
@@ -3077,8 +3067,20 @@ module SyntaxTree
|
|
3077
3067
|
doc =
|
3078
3068
|
q.nest(0) do
|
3079
3069
|
q.format(receiver)
|
3080
|
-
|
3081
|
-
|
3070
|
+
|
3071
|
+
# If there are leading comments on the message then we know we have
|
3072
|
+
# a newline in the source that is forcing these things apart. In
|
3073
|
+
# this case we will have to use a trailing operator.
|
3074
|
+
if message.comments.any?(&:leading?)
|
3075
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3076
|
+
q.indent do
|
3077
|
+
q.breakable("")
|
3078
|
+
q.format(message)
|
3079
|
+
end
|
3080
|
+
else
|
3081
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3082
|
+
q.format(message)
|
3083
|
+
end
|
3082
3084
|
end
|
3083
3085
|
|
3084
3086
|
case arguments
|
@@ -3096,31 +3098,6 @@ module SyntaxTree
|
|
3096
3098
|
|
3097
3099
|
private
|
3098
3100
|
|
3099
|
-
# This is a somewhat naive method that is attempting to sum up the width of
|
3100
|
-
# the doc nodes that make up the given doc node. This is used to align
|
3101
|
-
# content.
|
3102
|
-
def doc_width(parent)
|
3103
|
-
queue = [parent]
|
3104
|
-
width = 0
|
3105
|
-
|
3106
|
-
until queue.empty?
|
3107
|
-
doc = queue.shift
|
3108
|
-
|
3109
|
-
case doc
|
3110
|
-
when PrettyPrint::Text
|
3111
|
-
width += doc.width
|
3112
|
-
when PrettyPrint::Indent, PrettyPrint::Align, PrettyPrint::Group
|
3113
|
-
queue = doc.contents + queue
|
3114
|
-
when PrettyPrint::IfBreak
|
3115
|
-
queue = doc.break_contents + queue
|
3116
|
-
when PrettyPrint::Breakable
|
3117
|
-
width = 0
|
3118
|
-
end
|
3119
|
-
end
|
3120
|
-
|
3121
|
-
width
|
3122
|
-
end
|
3123
|
-
|
3124
3101
|
def argument_alignment(q, doc)
|
3125
3102
|
# Very special handling case for rspec matchers. In general with rspec
|
3126
3103
|
# matchers you expect to see something like:
|
@@ -3138,7 +3115,7 @@ module SyntaxTree
|
|
3138
3115
|
if %w[to not_to to_not].include?(message.value)
|
3139
3116
|
0
|
3140
3117
|
else
|
3141
|
-
width =
|
3118
|
+
width = q.last_position(doc) + 1
|
3142
3119
|
width > (q.maxwidth / 2) ? 0 : width
|
3143
3120
|
end
|
3144
3121
|
end
|
@@ -4611,16 +4588,26 @@ module SyntaxTree
|
|
4611
4588
|
|
4612
4589
|
def format(q)
|
4613
4590
|
q.format(constant) if constant
|
4614
|
-
q.group(0, "[", "]") do
|
4615
|
-
q.text("*")
|
4616
|
-
q.format(left)
|
4617
|
-
q.comma_breakable
|
4618
4591
|
|
4619
|
-
|
4620
|
-
q.
|
4592
|
+
q.group do
|
4593
|
+
q.text("[")
|
4594
|
+
|
4595
|
+
q.indent do
|
4596
|
+
q.breakable("")
|
4621
4597
|
|
4622
|
-
|
4623
|
-
|
4598
|
+
q.text("*")
|
4599
|
+
q.format(left)
|
4600
|
+
q.comma_breakable
|
4601
|
+
|
4602
|
+
q.seplist(values) { |value| q.format(value) }
|
4603
|
+
q.comma_breakable
|
4604
|
+
|
4605
|
+
q.text("*")
|
4606
|
+
q.format(right)
|
4607
|
+
end
|
4608
|
+
|
4609
|
+
q.breakable("")
|
4610
|
+
q.text("]")
|
4624
4611
|
end
|
4625
4612
|
end
|
4626
4613
|
end
|
@@ -4823,6 +4810,7 @@ module SyntaxTree
|
|
4823
4810
|
q.indent do
|
4824
4811
|
q.breakable
|
4825
4812
|
q.seplist(assocs) { |assoc| q.format(assoc) }
|
4813
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
4826
4814
|
end
|
4827
4815
|
q.breakable
|
4828
4816
|
end
|
@@ -4891,17 +4879,9 @@ module SyntaxTree
|
|
4891
4879
|
end
|
4892
4880
|
|
4893
4881
|
def format(q)
|
4894
|
-
# This is a very specific behavior
|
4895
|
-
#
|
4896
|
-
|
4897
|
-
breakable = -> do
|
4898
|
-
q.target << PrettyPrint::Breakable.new(
|
4899
|
-
" ",
|
4900
|
-
1,
|
4901
|
-
indent: false,
|
4902
|
-
force: true
|
4903
|
-
)
|
4904
|
-
end
|
4882
|
+
# This is a very specific behavior where you want to force a newline, but
|
4883
|
+
# don't want to force the break parent.
|
4884
|
+
breakable = -> { q.breakable(indent: false, force: :skip_break_parent) }
|
4905
4885
|
|
4906
4886
|
q.group do
|
4907
4887
|
q.format(beginning)
|
@@ -5197,6 +5177,8 @@ module SyntaxTree
|
|
5197
5177
|
case node
|
5198
5178
|
in predicate: Assign | Command | CommandCall | MAssign | OpAssign
|
5199
5179
|
false
|
5180
|
+
in predicate: Not[parentheses: false]
|
5181
|
+
false
|
5200
5182
|
in {
|
5201
5183
|
statements: { body: [truthy] },
|
5202
5184
|
consequent: Else[statements: { body: [falsy] }]
|
@@ -5325,9 +5307,8 @@ module SyntaxTree
|
|
5325
5307
|
# force it into the output but we _don't_ want to explicitly
|
5326
5308
|
# break the parent. If a break-parent shows up in the tree, then
|
5327
5309
|
# it's going to force it all the way up to the tree, which is
|
5328
|
-
# going to negate the ternary.
|
5329
|
-
|
5330
|
-
q.target << PrettyPrint::Breakable.new(" ", 1, force: true)
|
5310
|
+
# going to negate the ternary.
|
5311
|
+
q.breakable(force: :skip_break_parent)
|
5331
5312
|
q.format(node.consequent.statements)
|
5332
5313
|
end
|
5333
5314
|
end
|
@@ -5956,7 +5937,7 @@ module SyntaxTree
|
|
5956
5937
|
# ->(value) { value * 2 }
|
5957
5938
|
#
|
5958
5939
|
class Lambda < Node
|
5959
|
-
# [
|
5940
|
+
# [LambdaVar | Paren] the parameter declaration for this lambda
|
5960
5941
|
attr_reader :params
|
5961
5942
|
|
5962
5943
|
# [BodyStmt | Statements] the expressions to be executed in this lambda
|
@@ -6011,24 +5992,100 @@ module SyntaxTree
|
|
6011
5992
|
node.is_a?(Command) || node.is_a?(CommandCall)
|
6012
5993
|
end
|
6013
5994
|
|
6014
|
-
|
6015
|
-
|
5995
|
+
if force_parens
|
5996
|
+
q.text("{")
|
5997
|
+
|
5998
|
+
unless statements.empty?
|
5999
|
+
q.indent do
|
6000
|
+
q.breakable
|
6001
|
+
q.format(statements)
|
6002
|
+
end
|
6003
|
+
q.breakable
|
6004
|
+
end
|
6005
|
+
|
6006
|
+
q.text("}")
|
6007
|
+
else
|
6008
|
+
q.text("do")
|
6009
|
+
|
6010
|
+
unless statements.empty?
|
6011
|
+
q.indent do
|
6012
|
+
q.breakable
|
6013
|
+
q.format(statements)
|
6014
|
+
end
|
6015
|
+
end
|
6016
|
+
|
6016
6017
|
q.breakable
|
6017
|
-
q.
|
6018
|
+
q.text("end")
|
6018
6019
|
end
|
6019
|
-
|
6020
|
-
q.breakable
|
6021
|
-
q.text(force_parens ? "}" : "end")
|
6022
6020
|
end
|
6023
6021
|
.if_flat do
|
6024
|
-
q.text("{
|
6025
|
-
|
6026
|
-
|
6022
|
+
q.text("{")
|
6023
|
+
|
6024
|
+
unless statements.empty?
|
6025
|
+
q.text(" ")
|
6026
|
+
q.format(statements)
|
6027
|
+
q.text(" ")
|
6028
|
+
end
|
6029
|
+
|
6030
|
+
q.text("}")
|
6027
6031
|
end
|
6028
6032
|
end
|
6029
6033
|
end
|
6030
6034
|
end
|
6031
6035
|
|
6036
|
+
# LambdaVar represents the parameters being declared for a lambda. Effectively
|
6037
|
+
# this node is everything contained within the parentheses. This includes all
|
6038
|
+
# of the various parameter types, as well as block-local variable
|
6039
|
+
# declarations.
|
6040
|
+
#
|
6041
|
+
# -> (positional, optional = value, keyword:, █ local) do
|
6042
|
+
# end
|
6043
|
+
#
|
6044
|
+
class LambdaVar < Node
|
6045
|
+
# [Params] the parameters being declared with the block
|
6046
|
+
attr_reader :params
|
6047
|
+
|
6048
|
+
# [Array[ Ident ]] the list of block-local variable declarations
|
6049
|
+
attr_reader :locals
|
6050
|
+
|
6051
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
6052
|
+
attr_reader :comments
|
6053
|
+
|
6054
|
+
def initialize(params:, locals:, location:, comments: [])
|
6055
|
+
@params = params
|
6056
|
+
@locals = locals
|
6057
|
+
@location = location
|
6058
|
+
@comments = comments
|
6059
|
+
end
|
6060
|
+
|
6061
|
+
def accept(visitor)
|
6062
|
+
visitor.visit_lambda_var(self)
|
6063
|
+
end
|
6064
|
+
|
6065
|
+
def child_nodes
|
6066
|
+
[params, *locals]
|
6067
|
+
end
|
6068
|
+
|
6069
|
+
alias deconstruct child_nodes
|
6070
|
+
|
6071
|
+
def deconstruct_keys(_keys)
|
6072
|
+
{ params: params, locals: locals, location: location, comments: comments }
|
6073
|
+
end
|
6074
|
+
|
6075
|
+
def empty?
|
6076
|
+
params.empty? && locals.empty?
|
6077
|
+
end
|
6078
|
+
|
6079
|
+
def format(q)
|
6080
|
+
q.format(params)
|
6081
|
+
|
6082
|
+
if locals.any?
|
6083
|
+
q.text("; ")
|
6084
|
+
q.seplist(locals, -> { q.text(", ") }) { |local| q.format(local) }
|
6085
|
+
end
|
6086
|
+
end
|
6087
|
+
end
|
6088
|
+
|
6032
6089
|
# LBrace represents the use of a left brace, i.e., {.
|
6033
6090
|
class LBrace < Node
|
6034
6091
|
# [String] the left brace
|
@@ -8314,8 +8371,7 @@ module SyntaxTree
|
|
8314
8371
|
# same line in the source, then we're going to leave them in place and
|
8315
8372
|
# assume that's the way the developer wanted this expression
|
8316
8373
|
# represented.
|
8317
|
-
|
8318
|
-
RemoveBreaks.call(doc)
|
8374
|
+
q.remove_breaks(q.group(0, '#{', "}") { q.format(statements) })
|
8319
8375
|
else
|
8320
8376
|
q.group do
|
8321
8377
|
q.text('#{')
|
data/lib/syntax_tree/parser.rb
CHANGED
@@ -1940,6 +1940,41 @@ module SyntaxTree
|
|
1940
1940
|
token.location.start_char > beginning.location.start_char
|
1941
1941
|
end
|
1942
1942
|
|
1943
|
+
# We need to do some special mapping here. Since ripper doesn't support
|
1944
|
+
# capturing lambda var until 3.2, we need to normalize all of that here.
|
1945
|
+
params =
|
1946
|
+
case params
|
1947
|
+
in Paren[contents: Params]
|
1948
|
+
# In this case we've gotten to the <3.2 parentheses wrapping a set of
|
1949
|
+
# parameters case. Here we need to manually scan for lambda locals.
|
1950
|
+
range = (params.location.start_char + 1)...params.location.end_char
|
1951
|
+
locals = lambda_locals(source[range])
|
1952
|
+
|
1953
|
+
location = params.contents.location
|
1954
|
+
location = location.to(locals.last.location) if locals.any?
|
1955
|
+
|
1956
|
+
Paren.new(
|
1957
|
+
lparen: params.lparen,
|
1958
|
+
contents:
|
1959
|
+
LambdaVar.new(
|
1960
|
+
params: params.contents,
|
1961
|
+
locals: locals,
|
1962
|
+
location: location
|
1963
|
+
),
|
1964
|
+
location: params.location,
|
1965
|
+
comments: params.comments
|
1966
|
+
)
|
1967
|
+
in Params
|
1968
|
+
# In this case we've gotten to the <3.2 plain set of parameters. In
|
1969
|
+
# this case there cannot be lambda locals, so we will wrap the
|
1970
|
+
# parameters into a lambda var that has no locals.
|
1971
|
+
LambdaVar.new(params: params, locals: [], location: params.location)
|
1972
|
+
in LambdaVar
|
1973
|
+
# In this case we've gotten to 3.2+ lambda var. In this case we don't
|
1974
|
+
# need to do anything and can just the value as given.
|
1975
|
+
params
|
1976
|
+
end
|
1977
|
+
|
1943
1978
|
if braces
|
1944
1979
|
opening = find_token(TLamBeg)
|
1945
1980
|
closing = find_token(RBrace)
|
@@ -1962,6 +1997,83 @@ module SyntaxTree
|
|
1962
1997
|
)
|
1963
1998
|
end
|
1964
1999
|
|
2000
|
+
# :call-seq:
|
2001
|
+
# on_lambda_var: (Params params, Array[ Ident ] locals) -> LambdaVar
|
2002
|
+
def on_lambda_var(params, locals)
|
2003
|
+
location = params.location
|
2004
|
+
location = location.to(locals.last.location) if locals.any?
|
2005
|
+
|
2006
|
+
LambdaVar.new(params: params, locals: locals || [], location: location)
|
2007
|
+
end
|
2008
|
+
|
2009
|
+
# Ripper doesn't support capturing lambda local variables until 3.2. To
|
2010
|
+
# mitigate this, we have to parse that code for ourselves. We use the range
|
2011
|
+
# from the parentheses to find where we _should_ be looking. Then we check
|
2012
|
+
# if the resulting tokens match a pattern that we determine means that the
|
2013
|
+
# declaration has block-local variables. Once it does, we parse those out
|
2014
|
+
# and convert them into Ident nodes.
|
2015
|
+
def lambda_locals(source)
|
2016
|
+
tokens = Ripper.lex(source)
|
2017
|
+
|
2018
|
+
# First, check that we have a semi-colon. If we do, then we can start to
|
2019
|
+
# parse the tokens _after_ the semicolon.
|
2020
|
+
index = tokens.rindex { |token| token[1] == :on_semicolon }
|
2021
|
+
return [] unless index
|
2022
|
+
|
2023
|
+
# Next, map over the tokens and convert them into Ident nodes. Bail out
|
2024
|
+
# midway through if we encounter a token we didn't expect. Basically we're
|
2025
|
+
# making our own mini-parser here. To do that we'll walk through a small
|
2026
|
+
# state machine:
|
2027
|
+
#
|
2028
|
+
# ┌────────┐ ┌────────┐ ┌─────────┐
|
2029
|
+
# │ │ │ │ │┌───────┐│
|
2030
|
+
# ──> │ item │ ─── ident ──> │ next │ ─── rparen ──> ││ final ││
|
2031
|
+
# │ │ <── comma ─── │ │ │└───────┘│
|
2032
|
+
# └────────┘ └────────┘ └─────────┘
|
2033
|
+
# │ ^ │ ^
|
2034
|
+
# └──┘ └──┘
|
2035
|
+
# ignored_nl, sp nl, sp
|
2036
|
+
#
|
2037
|
+
state = :item
|
2038
|
+
transitions = {
|
2039
|
+
item: {
|
2040
|
+
on_ignored_nl: :item,
|
2041
|
+
on_sp: :item,
|
2042
|
+
on_ident: :next
|
2043
|
+
},
|
2044
|
+
next: {
|
2045
|
+
on_nl: :next,
|
2046
|
+
on_sp: :next,
|
2047
|
+
on_comma: :item,
|
2048
|
+
on_rparen: :final
|
2049
|
+
},
|
2050
|
+
final: {
|
2051
|
+
}
|
2052
|
+
}
|
2053
|
+
|
2054
|
+
tokens[(index + 1)..].each_with_object([]) do |token, locals|
|
2055
|
+
(lineno, column), type, value, = token
|
2056
|
+
|
2057
|
+
# Make the state transition for the parser. If there isn't a transition
|
2058
|
+
# from the current state to a new state for this type, then we're in a
|
2059
|
+
# pattern that isn't actually locals. In that case we can return [].
|
2060
|
+
state = transitions[state].fetch(type) { return [] }
|
2061
|
+
|
2062
|
+
# If we hit an identifier, then add it to our list.
|
2063
|
+
next if type != :on_ident
|
2064
|
+
|
2065
|
+
location =
|
2066
|
+
Location.token(
|
2067
|
+
line: lineno,
|
2068
|
+
char: line_counts[lineno - 1][column],
|
2069
|
+
column: column,
|
2070
|
+
size: value.size
|
2071
|
+
)
|
2072
|
+
|
2073
|
+
locals << Ident.new(value: value, location: location)
|
2074
|
+
end
|
2075
|
+
end
|
2076
|
+
|
1965
2077
|
# :call-seq:
|
1966
2078
|
# on_lbrace: (String value) -> LBrace
|
1967
2079
|
def on_lbrace(value)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/tasklib"
|
5
|
+
|
6
|
+
require "syntax_tree"
|
7
|
+
require "syntax_tree/cli"
|
8
|
+
|
9
|
+
module SyntaxTree
|
10
|
+
module Rake
|
11
|
+
# A Rake task that runs check on a set of source files.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'syntax_tree/rake/check_task'
|
16
|
+
#
|
17
|
+
# SyntaxTree::Rake::CheckTask.new do |t|
|
18
|
+
# t.source_files = '{app,config,lib}/**/*.rb'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# This will create task that can be run with:
|
22
|
+
#
|
23
|
+
# rake stree_check
|
24
|
+
#
|
25
|
+
class CheckTask < ::Rake::TaskLib
|
26
|
+
# Name of the task.
|
27
|
+
# Defaults to :"stree:check".
|
28
|
+
attr_accessor :name
|
29
|
+
|
30
|
+
# Glob pattern to match source files.
|
31
|
+
# Defaults to 'lib/**/*.rb'.
|
32
|
+
attr_accessor :source_files
|
33
|
+
|
34
|
+
# The set of plugins to require.
|
35
|
+
# Defaults to [].
|
36
|
+
attr_accessor :plugins
|
37
|
+
|
38
|
+
def initialize(
|
39
|
+
name = :"stree:check",
|
40
|
+
source_files = ::Rake::FileList["lib/**/*.rb"],
|
41
|
+
plugins = []
|
42
|
+
)
|
43
|
+
@name = name
|
44
|
+
@source_files = source_files
|
45
|
+
@plugins = plugins
|
46
|
+
|
47
|
+
yield self if block_given?
|
48
|
+
define_task
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def define_task
|
54
|
+
desc "Runs `stree check` over source files"
|
55
|
+
task(name) { run_task }
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_task
|
59
|
+
arguments = ["check"]
|
60
|
+
arguments << "--plugins=#{plugins.join(",")}" if plugins.any?
|
61
|
+
|
62
|
+
SyntaxTree::CLI.run(arguments + Array(source_files))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|