syntax_tree 2.4.1 → 2.7.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.
@@ -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