syntax_tree 2.4.0 → 2.6.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/.github/workflows/main.yml +1 -1
- data/CHANGELOG.md +31 -1
- data/Gemfile +0 -2
- data/Gemfile.lock +5 -3
- data/README.md +62 -8
- data/Rakefile +5 -20
- data/lib/syntax_tree/formatter/single_quotes.rb +13 -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 +141 -107
- data/lib/syntax_tree/parser.rb +120 -2
- data/lib/syntax_tree/plugin/single_quotes.rb +4 -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 +8 -0
- data/lib/syntax_tree/visitor/match_visitor.rb +2 -2
- data/lib/syntax_tree/visitor.rb +3 -0
- data/lib/syntax_tree.rb +3 -27
- data/syntax_tree.gemspec +3 -0
- metadata +37 -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
|
|
@@ -1388,7 +1410,7 @@ module SyntaxTree
|
|
1388
1410
|
module HashKeyFormatter
|
1389
1411
|
# Formats the keys of a hash literal using labels.
|
1390
1412
|
class Labels
|
1391
|
-
LABEL =
|
1413
|
+
LABEL = /\A[A-Za-z_](\w*[\w!?])?\z/
|
1392
1414
|
|
1393
1415
|
def format_key(q, key)
|
1394
1416
|
case key
|
@@ -1666,52 +1688,6 @@ module SyntaxTree
|
|
1666
1688
|
end
|
1667
1689
|
end
|
1668
1690
|
|
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
1691
|
# BlockVar represents the parameters being declared for a block. Effectively
|
1716
1692
|
# this node is everything contained within the pipes. This includes all of the
|
1717
1693
|
# various parameter types, as well as block-local variable declarations.
|
@@ -1752,8 +1728,7 @@ module SyntaxTree
|
|
1752
1728
|
|
1753
1729
|
def format(q)
|
1754
1730
|
q.group(0, "|", "|") do
|
1755
|
-
|
1756
|
-
RemoveBreaks.call(doc)
|
1731
|
+
q.remove_breaks(q.format(params))
|
1757
1732
|
|
1758
1733
|
if locals.any?
|
1759
1734
|
q.text("; ")
|
@@ -3077,8 +3052,20 @@ module SyntaxTree
|
|
3077
3052
|
doc =
|
3078
3053
|
q.nest(0) do
|
3079
3054
|
q.format(receiver)
|
3080
|
-
|
3081
|
-
|
3055
|
+
|
3056
|
+
# If there are leading comments on the message then we know we have
|
3057
|
+
# a newline in the source that is forcing these things apart. In
|
3058
|
+
# this case we will have to use a trailing operator.
|
3059
|
+
if message.comments.any?(&:leading?)
|
3060
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3061
|
+
q.indent do
|
3062
|
+
q.breakable("")
|
3063
|
+
q.format(message)
|
3064
|
+
end
|
3065
|
+
else
|
3066
|
+
q.format(CallOperatorFormatter.new(operator), stackable: false)
|
3067
|
+
q.format(message)
|
3068
|
+
end
|
3082
3069
|
end
|
3083
3070
|
|
3084
3071
|
case arguments
|
@@ -3096,31 +3083,6 @@ module SyntaxTree
|
|
3096
3083
|
|
3097
3084
|
private
|
3098
3085
|
|
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
3086
|
def argument_alignment(q, doc)
|
3125
3087
|
# Very special handling case for rspec matchers. In general with rspec
|
3126
3088
|
# matchers you expect to see something like:
|
@@ -3138,7 +3100,7 @@ module SyntaxTree
|
|
3138
3100
|
if %w[to not_to to_not].include?(message.value)
|
3139
3101
|
0
|
3140
3102
|
else
|
3141
|
-
width =
|
3103
|
+
width = q.last_position(doc) + 1
|
3142
3104
|
width > (q.maxwidth / 2) ? 0 : width
|
3143
3105
|
end
|
3144
3106
|
end
|
@@ -3904,7 +3866,7 @@ module SyntaxTree
|
|
3904
3866
|
# quotes, then single quotes would deactivate it.)
|
3905
3867
|
def self.locked?(node)
|
3906
3868
|
node.parts.any? do |part|
|
3907
|
-
part.is_a?(TStringContent)
|
3869
|
+
!part.is_a?(TStringContent) || part.value.match?(/\\|#[@${]/)
|
3908
3870
|
end
|
3909
3871
|
end
|
3910
3872
|
|
@@ -4823,6 +4785,7 @@ module SyntaxTree
|
|
4823
4785
|
q.indent do
|
4824
4786
|
q.breakable
|
4825
4787
|
q.seplist(assocs) { |assoc| q.format(assoc) }
|
4788
|
+
q.if_break { q.text(",") } if q.trailing_comma?
|
4826
4789
|
end
|
4827
4790
|
q.breakable
|
4828
4791
|
end
|
@@ -4891,17 +4854,9 @@ module SyntaxTree
|
|
4891
4854
|
end
|
4892
4855
|
|
4893
4856
|
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
|
4857
|
+
# This is a very specific behavior where you want to force a newline, but
|
4858
|
+
# don't want to force the break parent.
|
4859
|
+
breakable = -> { q.breakable(indent: false, force: :skip_break_parent) }
|
4905
4860
|
|
4906
4861
|
q.group do
|
4907
4862
|
q.format(beginning)
|
@@ -5064,13 +5019,16 @@ module SyntaxTree
|
|
5064
5019
|
parts = keywords.map { |(key, value)| KeywordFormatter.new(key, value) }
|
5065
5020
|
parts << KeywordRestFormatter.new(keyword_rest) if keyword_rest
|
5066
5021
|
|
5022
|
+
nested = PATTERNS.include?(q.parent.class)
|
5067
5023
|
contents = -> do
|
5068
5024
|
q.group { q.seplist(parts) { |part| q.format(part, stackable: false) } }
|
5069
5025
|
|
5070
5026
|
# If there isn't a constant, and there's a blank keyword_rest, then we
|
5071
5027
|
# have an plain ** that needs to have a `then` after it in order to
|
5072
5028
|
# parse correctly on the next parse.
|
5073
|
-
|
5029
|
+
if !constant && keyword_rest && keyword_rest.value.nil? && !nested
|
5030
|
+
q.text(" then")
|
5031
|
+
end
|
5074
5032
|
end
|
5075
5033
|
|
5076
5034
|
# If there is a constant, we're going to format to have the constant name
|
@@ -5097,7 +5055,7 @@ module SyntaxTree
|
|
5097
5055
|
|
5098
5056
|
# If there's only one pair, then we'll just print the contents provided
|
5099
5057
|
# we're not inside another pattern.
|
5100
|
-
if !
|
5058
|
+
if !nested && parts.size == 1
|
5101
5059
|
contents.call
|
5102
5060
|
return
|
5103
5061
|
end
|
@@ -5194,6 +5152,8 @@ module SyntaxTree
|
|
5194
5152
|
case node
|
5195
5153
|
in predicate: Assign | Command | CommandCall | MAssign | OpAssign
|
5196
5154
|
false
|
5155
|
+
in predicate: Not[parentheses: false]
|
5156
|
+
false
|
5197
5157
|
in {
|
5198
5158
|
statements: { body: [truthy] },
|
5199
5159
|
consequent: Else[statements: { body: [falsy] }]
|
@@ -5322,9 +5282,8 @@ module SyntaxTree
|
|
5322
5282
|
# force it into the output but we _don't_ want to explicitly
|
5323
5283
|
# break the parent. If a break-parent shows up in the tree, then
|
5324
5284
|
# it's going to force it all the way up to the tree, which is
|
5325
|
-
# going to negate the ternary.
|
5326
|
-
|
5327
|
-
q.target << PrettyPrint::Breakable.new(" ", 1, force: true)
|
5285
|
+
# going to negate the ternary.
|
5286
|
+
q.breakable(force: :skip_break_parent)
|
5328
5287
|
q.format(node.consequent.statements)
|
5329
5288
|
end
|
5330
5289
|
end
|
@@ -5953,7 +5912,7 @@ module SyntaxTree
|
|
5953
5912
|
# ->(value) { value * 2 }
|
5954
5913
|
#
|
5955
5914
|
class Lambda < Node
|
5956
|
-
# [
|
5915
|
+
# [LambdaVar | Paren] the parameter declaration for this lambda
|
5957
5916
|
attr_reader :params
|
5958
5917
|
|
5959
5918
|
# [BodyStmt | Statements] the expressions to be executed in this lambda
|
@@ -6008,24 +5967,100 @@ module SyntaxTree
|
|
6008
5967
|
node.is_a?(Command) || node.is_a?(CommandCall)
|
6009
5968
|
end
|
6010
5969
|
|
6011
|
-
|
6012
|
-
|
5970
|
+
if force_parens
|
5971
|
+
q.text("{")
|
5972
|
+
|
5973
|
+
unless statements.empty?
|
5974
|
+
q.indent do
|
5975
|
+
q.breakable
|
5976
|
+
q.format(statements)
|
5977
|
+
end
|
5978
|
+
q.breakable
|
5979
|
+
end
|
5980
|
+
|
5981
|
+
q.text("}")
|
5982
|
+
else
|
5983
|
+
q.text("do")
|
5984
|
+
|
5985
|
+
unless statements.empty?
|
5986
|
+
q.indent do
|
5987
|
+
q.breakable
|
5988
|
+
q.format(statements)
|
5989
|
+
end
|
5990
|
+
end
|
5991
|
+
|
6013
5992
|
q.breakable
|
6014
|
-
q.
|
5993
|
+
q.text("end")
|
6015
5994
|
end
|
6016
|
-
|
6017
|
-
q.breakable
|
6018
|
-
q.text(force_parens ? "}" : "end")
|
6019
5995
|
end
|
6020
5996
|
.if_flat do
|
6021
|
-
q.text("{
|
6022
|
-
|
6023
|
-
|
5997
|
+
q.text("{")
|
5998
|
+
|
5999
|
+
unless statements.empty?
|
6000
|
+
q.text(" ")
|
6001
|
+
q.format(statements)
|
6002
|
+
q.text(" ")
|
6003
|
+
end
|
6004
|
+
|
6005
|
+
q.text("}")
|
6024
6006
|
end
|
6025
6007
|
end
|
6026
6008
|
end
|
6027
6009
|
end
|
6028
6010
|
|
6011
|
+
# LambdaVar represents the parameters being declared for a lambda. Effectively
|
6012
|
+
# this node is everything contained within the parentheses. This includes all
|
6013
|
+
# of the various parameter types, as well as block-local variable
|
6014
|
+
# declarations.
|
6015
|
+
#
|
6016
|
+
# -> (positional, optional = value, keyword:, █ local) do
|
6017
|
+
# end
|
6018
|
+
#
|
6019
|
+
class LambdaVar < Node
|
6020
|
+
# [Params] the parameters being declared with the block
|
6021
|
+
attr_reader :params
|
6022
|
+
|
6023
|
+
# [Array[ Ident ]] the list of block-local variable declarations
|
6024
|
+
attr_reader :locals
|
6025
|
+
|
6026
|
+
# [Array[ Comment | EmbDoc ]] the comments attached to this node
|
6027
|
+
attr_reader :comments
|
6028
|
+
|
6029
|
+
def initialize(params:, locals:, location:, comments: [])
|
6030
|
+
@params = params
|
6031
|
+
@locals = locals
|
6032
|
+
@location = location
|
6033
|
+
@comments = comments
|
6034
|
+
end
|
6035
|
+
|
6036
|
+
def accept(visitor)
|
6037
|
+
visitor.visit_lambda_var(self)
|
6038
|
+
end
|
6039
|
+
|
6040
|
+
def child_nodes
|
6041
|
+
[params, *locals]
|
6042
|
+
end
|
6043
|
+
|
6044
|
+
alias deconstruct child_nodes
|
6045
|
+
|
6046
|
+
def deconstruct_keys(_keys)
|
6047
|
+
{ params: params, locals: locals, location: location, comments: comments }
|
6048
|
+
end
|
6049
|
+
|
6050
|
+
def empty?
|
6051
|
+
params.empty? && locals.empty?
|
6052
|
+
end
|
6053
|
+
|
6054
|
+
def format(q)
|
6055
|
+
q.format(params)
|
6056
|
+
|
6057
|
+
if locals.any?
|
6058
|
+
q.text("; ")
|
6059
|
+
q.seplist(locals, -> { q.text(", ") }) { |local| q.format(local) }
|
6060
|
+
end
|
6061
|
+
end
|
6062
|
+
end
|
6063
|
+
|
6029
6064
|
# LBrace represents the use of a left brace, i.e., {.
|
6030
6065
|
class LBrace < Node
|
6031
6066
|
# [String] the left brace
|
@@ -8311,8 +8346,7 @@ module SyntaxTree
|
|
8311
8346
|
# same line in the source, then we're going to leave them in place and
|
8312
8347
|
# assume that's the way the developer wanted this expression
|
8313
8348
|
# represented.
|
8314
|
-
|
8315
|
-
RemoveBreaks.call(doc)
|
8349
|
+
q.remove_breaks(q.group(0, '#{', "}") { q.format(statements) })
|
8316
8350
|
else
|
8317
8351
|
q.group do
|
8318
8352
|
q.text('#{')
|
data/lib/syntax_tree/parser.rb
CHANGED
@@ -1671,9 +1671,15 @@ module SyntaxTree
|
|
1671
1671
|
# (nil | VarField) keyword_rest
|
1672
1672
|
# ) -> HshPtn
|
1673
1673
|
def on_hshptn(constant, keywords, keyword_rest)
|
1674
|
-
|
1675
|
-
|
1674
|
+
if keyword_rest
|
1675
|
+
# We're doing this to delete the token from the list so that it doesn't
|
1676
|
+
# confuse future patterns by thinking they have an extra ** on the end.
|
1677
|
+
find_token(Op, "**")
|
1678
|
+
elsif (token = find_token(Op, "**", consume: false))
|
1676
1679
|
tokens.delete(token)
|
1680
|
+
|
1681
|
+
# Create an artificial VarField if we find an extra ** on the end. This
|
1682
|
+
# means the formatting will be a little more consistent.
|
1677
1683
|
keyword_rest = VarField.new(value: nil, location: token.location)
|
1678
1684
|
end
|
1679
1685
|
|
@@ -1934,6 +1940,41 @@ module SyntaxTree
|
|
1934
1940
|
token.location.start_char > beginning.location.start_char
|
1935
1941
|
end
|
1936
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
|
+
|
1937
1978
|
if braces
|
1938
1979
|
opening = find_token(TLamBeg)
|
1939
1980
|
closing = find_token(RBrace)
|
@@ -1956,6 +1997,83 @@ module SyntaxTree
|
|
1956
1997
|
)
|
1957
1998
|
end
|
1958
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
|
+
|
1959
2077
|
# :call-seq:
|
1960
2078
|
# on_lbrace: (String value) -> LBrace
|
1961
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
|
@@ -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 format on a set of source files.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'syntax_tree/rake/write_task'
|
16
|
+
#
|
17
|
+
# SyntaxTree::Rake::WriteTask.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_write
|
24
|
+
#
|
25
|
+
class WriteTask < ::Rake::TaskLib
|
26
|
+
# Name of the task.
|
27
|
+
# Defaults to :"stree:write".
|
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:write",
|
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 write` over source files"
|
55
|
+
task(name) { run_task }
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_task
|
59
|
+
arguments = ["write"]
|
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
|
data/lib/syntax_tree/version.rb
CHANGED