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.
@@ -123,7 +123,7 @@ module SyntaxTree
123
123
  end
124
124
 
125
125
  def construct_keys
126
- PP.format(+"") { |q| Visitor::MatchVisitor.new(q).visit(self) }
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.seplist(parts) { |part| q.format(part) }
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.seplist(parts) { |part| q.format(part) }
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 = /^[@$_A-Za-z]([_A-Za-z0-9]*)?([!_=?A-Za-z0-9])?$/
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
- doc = q.format(params)
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
- q.group do
2806
- q.indent do
2807
- q.breakable
2808
- q.format(pattern)
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
- q.format(CallOperatorFormatter.new(operator), stackable: false)
3081
- q.format(message)
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 = doc_width(doc) + 1
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
- q.seplist(values) { |value| q.format(value) }
4620
- q.comma_breakable
4592
+ q.group do
4593
+ q.text("[")
4594
+
4595
+ q.indent do
4596
+ q.breakable("")
4621
4597
 
4622
- q.text("*")
4623
- q.format(right)
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 that should probably be included in the
4895
- # prettyprint module. It's when you want to force a newline, but don't
4896
- # want to force the break parent.
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. Maybe this should be an option in
5329
- # prettyprint? As in force: :no_break_parent or something.
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
- # [Params | Paren] the parameter declaration for this lambda
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
- q.text(force_parens ? "{" : "do")
6015
- q.indent do
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.format(statements)
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
- q.format(statements)
6026
- q.text(" }")
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:, &block; 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
- doc = q.group(0, '#{', "}") { q.format(statements) }
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('#{')
@@ -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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "syntax_tree/formatter/trailing_comma"
4
+ SyntaxTree::Formatter.prepend(SyntaxTree::Formatter::TrailingComma)
@@ -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