parser 2.7.1.1 → 2.7.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -111,6 +111,11 @@ module Parser
111
111
  @parser_class = Parser::Ruby27
112
112
  end
113
113
 
114
+ opts.on '--28', 'Parse as Ruby 2.8 would' do
115
+ require 'parser/ruby28'
116
+ @parser_class = Parser::Ruby28
117
+ end
118
+
114
119
  opts.on '--mac', 'Parse as MacRuby 0.12 would' do
115
120
  require 'parser/macruby'
116
121
  @parser_class = Parser::MacRuby
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parser
4
+ module Source
5
+
6
+ class Map::EndlessDefinition < Map
7
+ attr_reader :keyword
8
+ attr_reader :operator
9
+ attr_reader :name
10
+ attr_reader :assignment
11
+
12
+ def initialize(keyword_l, operator_l, name_l, assignment_l, body_l)
13
+ @keyword = keyword_l
14
+ @operator = operator_l
15
+ @name = name_l
16
+ @assignment = assignment_l
17
+
18
+ super(@keyword.join(body_l))
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -221,20 +221,17 @@ module Parser
221
221
  #
222
222
  # @return [String]
223
223
  #
224
- def process
225
- source = @source_buffer.source.dup
226
- adjustment = 0
224
+ def process
225
+ source = @source_buffer.source
227
226
 
227
+ chunks = []
228
+ last_end = 0
228
229
  @action_root.ordered_replacements.each do |range, replacement|
229
- begin_pos = range.begin_pos + adjustment
230
- end_pos = begin_pos + range.length
231
-
232
- source[begin_pos...end_pos] = replacement
233
-
234
- adjustment += replacement.length - range.length
230
+ chunks << source[last_end...range.begin_pos] << replacement
231
+ last_end = range.end_pos
235
232
  end
236
-
237
- source
233
+ chunks << source[last_end...source.length]
234
+ chunks.join
238
235
  end
239
236
 
240
237
  ##
@@ -7,7 +7,7 @@ module Parser
7
7
  #
8
8
  # Actions are arranged in a tree and get combined so that:
9
9
  # children are strictly contained by their parent
10
- # sibblings all disjoint from one another
10
+ # sibblings all disjoint from one another and ordered
11
11
  # only actions with replacement==nil may have children
12
12
  #
13
13
  class TreeRewriter::Action
@@ -41,7 +41,7 @@ module Parser
41
41
  reps = []
42
42
  reps << [@range.begin, @insert_before] unless @insert_before.empty?
43
43
  reps << [@range, @replacement] if @replacement
44
- reps.concat(@children.sort_by(&:range).flat_map(&:ordered_replacements))
44
+ reps.concat(@children.flat_map(&:ordered_replacements))
45
45
  reps << [@range.end, @insert_after] unless @insert_after.empty?
46
46
  reps
47
47
  end
@@ -69,24 +69,24 @@ module Parser
69
69
  end
70
70
 
71
71
  def place_in_hierarchy(action)
72
- family = @children.group_by { |child| child.relationship_with(action) }
72
+ family = analyse_hierarchy(action)
73
73
 
74
74
  if family[:fusible]
75
- fuse_deletions(action, family[:fusible], [*family[:sibbling], *family[:child]])
75
+ fuse_deletions(action, family[:fusible], [*family[:sibbling_left], *family[:child], *family[:sibbling_right]])
76
76
  else
77
77
  extra_sibbling = if family[:parent] # action should be a descendant of one of the children
78
- family[:parent][0].do_combine(action)
78
+ family[:parent].do_combine(action)
79
79
  elsif family[:child] # or it should become the parent of some of the children,
80
80
  action.with(children: family[:child], enforcer: @enforcer)
81
81
  .combine_children(action.children)
82
82
  else # or else it should become an additional child
83
83
  action
84
84
  end
85
- with(children: [*family[:sibbling], extra_sibbling])
85
+ with(children: [*family[:sibbling_left], extra_sibbling, *family[:sibbling_right]])
86
86
  end
87
87
  end
88
88
 
89
- # Assumes more_children all contained within @range
89
+ # Assumes `more_children` all contained within `@range`
90
90
  def combine_children(more_children)
91
91
  more_children.inject(self) do |parent, new_child|
92
92
  parent.place_in_hierarchy(new_child)
@@ -100,22 +100,76 @@ module Parser
100
100
  without_fusible.do_combine(fused_deletion)
101
101
  end
102
102
 
103
- # Returns what relationship self should have with `action`; either of
104
- # :sibbling, :parent, :child, :fusible or raises a CloberingError
105
- # In case of equal range, returns :parent
106
- def relationship_with(action)
107
- if action.range == @range || @range.contains?(action.range)
108
- :parent
109
- elsif @range.contained?(action.range)
110
- :child
111
- elsif @range.disjoint?(action.range)
112
- :sibbling
113
- elsif !action.insertion? && !insertion?
114
- @enforcer.call(:crossing_deletions) { {range: action.range, conflict: @range} }
115
- :fusible
103
+ # Similar to @children.bsearch_index || size
104
+ # except allows for a starting point
105
+ # and `bsearch_index` is only Ruby 2.3+
106
+ def bsearch_child_index(from = 0)
107
+ size = @children.size
108
+ (from...size).bsearch { |i| yield @children[i] } || size
109
+ end
110
+
111
+ # Returns the children in a hierarchy with respect to `action`:
112
+ # :sibbling_left, sibbling_right (for those that are disjoint from `action`)
113
+ # :parent (in case one of our children contains `action`)
114
+ # :child (in case `action` strictly contains some of our children)
115
+ # :fusible (in case `action` overlaps some children but they can be fused in one deletion)
116
+ # or raises a `CloberingError`
117
+ # In case a child has equal range to `action`, it is returned as `:parent`
118
+ # Reminder: an empty range 1...1 is considered disjoint from 1...10
119
+ def analyse_hierarchy(action)
120
+ r = action.range
121
+ # left_index is the index of the first child that isn't completely to the left of action
122
+ left_index = bsearch_child_index { |child| child.range.end_pos > r.begin_pos }
123
+ # right_index is the index of the first child that is completely on the right of action
124
+ start = left_index == 0 ? 0 : left_index - 1 # See "corner case" below for reason of -1
125
+ right_index = bsearch_child_index(start) { |child| child.range.begin_pos >= r.end_pos }
126
+ center = right_index - left_index
127
+ case center
128
+ when 0
129
+ # All children are disjoint from action, nothing else to do
130
+ when -1
131
+ # Corner case: if a child has empty range == action's range
132
+ # then it will appear to be both disjoint and to the left of action,
133
+ # as well as disjoint and to the right of action.
134
+ # Since ranges are equal, we return it as parent
135
+ left_index -= 1 # Fix indices, as otherwise this child would be
136
+ right_index += 1 # considered as a sibbling (both left and right!)
137
+ parent = @children[left_index]
116
138
  else
117
- @enforcer.call(:crossing_insertions) { {range: action.range, conflict: @range} }
139
+ overlap_left = @children[left_index].range.begin_pos <=> r.begin_pos
140
+ overlap_right = @children[right_index-1].range.end_pos <=> r.end_pos
141
+
142
+ # For one child to be the parent of action, we must have:
143
+ if center == 1 && overlap_left <= 0 && overlap_right >= 0
144
+ parent = @children[left_index]
145
+ else
146
+ # Otherwise consider all non disjoint elements (center) to be contained...
147
+ contained = @children[left_index...right_index]
148
+ fusible = check_fusible(action,
149
+ (contained.shift if overlap_left < 0), # ... but check first and last one
150
+ (contained.pop if overlap_right > 0) # ... for overlaps
151
+ )
152
+ end
153
+ end
154
+
155
+ {
156
+ parent: parent,
157
+ sibbling_left: @children[0...left_index],
158
+ sibbling_right: @children[right_index...@children.size],
159
+ fusible: fusible,
160
+ child: contained,
161
+ }
162
+ end
163
+
164
+ # @param [Array(Action | nil)] fusible
165
+ def check_fusible(action, *fusible)
166
+ fusible.compact!
167
+ return if fusible.empty?
168
+ fusible.each do |child|
169
+ kind = action.insertion? || child.insertion? ? :crossing_insertions : :crossing_deletions
170
+ @enforcer.call(kind) { {range: action.range, conflict: child.range} }
118
171
  end
172
+ fusible
119
173
  end
120
174
 
121
175
  # Assumes action.range == range && action.children.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parser
4
- VERSION = '2.7.1.1'
4
+ VERSION = '2.7.1.2'
5
5
  end
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
  lib/parser/ruby25.rb
33
33
  lib/parser/ruby26.rb
34
34
  lib/parser/ruby27.rb
35
+ lib/parser/ruby28.rb
35
36
  lib/parser/macruby.rb
36
37
  lib/parser/rubymotion.rb
37
38
  )
@@ -20,6 +20,7 @@ if ENV.include?('COVERAGE') && SimpleCov.usable?
20
20
  ruby25.y
21
21
  ruby26.y
22
22
  ruby27.y
23
+ ruby28.y
23
24
  ),
24
25
  File.expand_path('../../lib/parser', __FILE__))
25
26
 
@@ -7,7 +7,7 @@ module ParseHelper
7
7
  require 'parser/macruby'
8
8
  require 'parser/rubymotion'
9
9
 
10
- ALL_VERSIONS = %w(1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 mac ios)
10
+ ALL_VERSIONS = %w(1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 mac ios)
11
11
 
12
12
  def setup
13
13
  @diagnostics = []
@@ -27,6 +27,7 @@ module ParseHelper
27
27
  when '2.5' then parser = Parser::Ruby25.new
28
28
  when '2.6' then parser = Parser::Ruby26.new
29
29
  when '2.7' then parser = Parser::Ruby27.new
30
+ when '2.8' then parser = Parser::Ruby28.new
30
31
  when 'mac' then parser = Parser::MacRuby.new
31
32
  when 'ios' then parser = Parser::RubyMotion.new
32
33
  else raise "Unrecognized Ruby version #{version}"
@@ -22,6 +22,8 @@ class TestCurrent < Minitest::Test
22
22
  assert_equal Parser::Ruby26, Parser::CurrentRuby
23
23
  when /^2\.7\.\d+/
24
24
  assert_equal Parser::Ruby27, Parser::CurrentRuby
25
+ when /^2\.8\.\d+/
26
+ assert_equal Parser::Ruby28, Parser::CurrentRuby
25
27
  else
26
28
  flunk "Update test_current for #{RUBY_VERSION}"
27
29
  end
@@ -3569,6 +3569,18 @@ class TestLexer < Minitest::Test
3569
3569
  :tIDENTIFIER, 're', [1, 3])
3570
3570
  end
3571
3571
 
3572
+ def test_endless_method
3573
+ setup_lexer(28)
3574
+
3575
+ assert_scanned('def foo() = 42',
3576
+ :kDEF, "def", [0, 3],
3577
+ :tIDENTIFIER, "foo", [4, 7],
3578
+ :tLPAREN2, "(", [7, 8],
3579
+ :tRPAREN, ")", [8, 9],
3580
+ :tEQL, "=", [10, 11],
3581
+ :tINTEGER, 42, [12, 14])
3582
+ end
3583
+
3572
3584
  def lex_numbered_parameter(input)
3573
3585
  @lex.max_numparam_stack.push
3574
3586
 
@@ -29,6 +29,7 @@ class TestParser < Minitest::Test
29
29
  SINCE_2_5 = SINCE_2_4 - %w(2.4)
30
30
  SINCE_2_6 = SINCE_2_5 - %w(2.5)
31
31
  SINCE_2_7 = SINCE_2_6 - %w(2.6)
32
+ SINCE_2_8 = SINCE_2_7 - %w(2.7)
32
33
 
33
34
  # Guidelines for test naming:
34
35
  # * Test structure follows structure of AST_FORMAT.md.
@@ -8933,6 +8934,22 @@ class TestParser < Minitest::Test
8933
8934
  %q{ ~~~~~~~ location},
8934
8935
  SINCE_2_7
8935
8936
  )
8937
+
8938
+ assert_diagnoses(
8939
+ [:error, :pm_interp_in_var_name],
8940
+ %q{case a; in "#{a}": 1; end},
8941
+ %q{ ~~~~~~~ location},
8942
+ SINCE_2_7
8943
+ )
8944
+ end
8945
+
8946
+ def test_pattern_matching_invalid_lvar_name
8947
+ assert_diagnoses(
8948
+ [:error, :lvar_name, { name: :a? }],
8949
+ %q{case a; in a?:; end},
8950
+ %q{ ~~ location},
8951
+ SINCE_2_7
8952
+ )
8936
8953
  end
8937
8954
 
8938
8955
  def test_pattern_matching_keyword_variable
@@ -9427,4 +9444,82 @@ class TestParser < Minitest::Test
9427
9444
  %{},
9428
9445
  SINCE_1_9)
9429
9446
  end
9447
+
9448
+ def test_endless_method
9449
+ assert_parses(
9450
+ s(:def_e, :foo,
9451
+ s(:args),
9452
+ s(:int, 42)),
9453
+ %q{def foo() = 42},
9454
+ %q{~~~ keyword
9455
+ | ~~~ name
9456
+ | ^ assignment
9457
+ |~~~~~~~~~~~~~~ expression},
9458
+ SINCE_2_8)
9459
+
9460
+ assert_parses(
9461
+ s(:def_e, :inc,
9462
+ s(:args, s(:arg, :x)),
9463
+ s(:send,
9464
+ s(:lvar, :x), :+,
9465
+ s(:int, 1))),
9466
+ %q{def inc(x) = x + 1},
9467
+ %q{~~~ keyword
9468
+ | ~~~ name
9469
+ | ^ assignment
9470
+ |~~~~~~~~~~~~~~~~~~ expression},
9471
+ SINCE_2_8)
9472
+
9473
+ assert_parses(
9474
+ s(:defs_e, s(:send, nil, :obj), :foo,
9475
+ s(:args),
9476
+ s(:int, 42)),
9477
+ %q{def obj.foo() = 42},
9478
+ %q{~~~ keyword
9479
+ | ^ operator
9480
+ | ~~~ name
9481
+ | ^ assignment
9482
+ |~~~~~~~~~~~~~~~~~~ expression},
9483
+ SINCE_2_8)
9484
+
9485
+ assert_parses(
9486
+ s(:defs_e, s(:send, nil, :obj), :inc,
9487
+ s(:args, s(:arg, :x)),
9488
+ s(:send,
9489
+ s(:lvar, :x), :+,
9490
+ s(:int, 1))),
9491
+ %q{def obj.inc(x) = x + 1},
9492
+ %q{~~~ keyword
9493
+ | ~~~ name
9494
+ | ^ operator
9495
+ | ^ assignment
9496
+ |~~~~~~~~~~~~~~~~~~~~~~ expression},
9497
+ SINCE_2_8)
9498
+
9499
+ assert_parses(
9500
+ s(:def_e, :foo,
9501
+ s(:forward_args),
9502
+ s(:send, nil, :bar,
9503
+ s(:forwarded_args))),
9504
+ %q{def foo(...) = bar(...)},
9505
+ %q{~~~ keyword
9506
+ | ~~~ name
9507
+ | ^ assignment
9508
+ |~~~~~~~~~~~~~~~~~~~~~~~ expression},
9509
+ SINCE_2_8)
9510
+ end
9511
+
9512
+ def test_endless_method_without_brackets
9513
+ assert_diagnoses(
9514
+ [:error, :unexpected_token, { :token => 'tEQL' }],
9515
+ %Q{def foo = 42},
9516
+ %q{ ^ location},
9517
+ SINCE_2_8)
9518
+
9519
+ assert_diagnoses(
9520
+ [:error, :unexpected_token, { :token => 'tEQL' }],
9521
+ %Q{def obj.foo = 42},
9522
+ %q{ ^ location},
9523
+ SINCE_2_8)
9524
+ end
9430
9525
  end
@@ -151,6 +151,16 @@ DIAGNOSTIC
151
151
  [:wrap, @hello, '[', ']']]
152
152
  end
153
153
 
154
+ def test_inserts_on_empty_ranges
155
+ assert_actions_result 'puts({x}:hello[y], :world)',
156
+ [:insert_before, @hello.begin, '{'],
157
+ [:replace, @hello.begin, 'x'],
158
+ [:insert_after, @hello.begin, '}'],
159
+ [:insert_before, @hello.end, '['],
160
+ [:replace, @hello.end, 'y'],
161
+ [:insert_after, @hello.end, ']']
162
+ end
163
+
154
164
  def test_replace_same_range
155
165
  assert_actions_result 'puts(:hey, :world)',
156
166
  [[:replace, @hello, ':hi'],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.1.1
4
+ version: 2.7.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - whitequark
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-14 00:00:00.000000000 Z
11
+ date: 2020-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast
@@ -229,6 +229,8 @@ files:
229
229
  - lib/parser/ruby26.y
230
230
  - lib/parser/ruby27.rb
231
231
  - lib/parser/ruby27.y
232
+ - lib/parser/ruby28.rb
233
+ - lib/parser/ruby28.y
232
234
  - lib/parser/rubymotion.rb
233
235
  - lib/parser/rubymotion.y
234
236
  - lib/parser/runner.rb
@@ -242,6 +244,7 @@ files:
242
244
  - lib/parser/source/map/condition.rb
243
245
  - lib/parser/source/map/constant.rb
244
246
  - lib/parser/source/map/definition.rb
247
+ - lib/parser/source/map/endless_definition.rb
245
248
  - lib/parser/source/map/for.rb
246
249
  - lib/parser/source/map/heredoc.rb
247
250
  - lib/parser/source/map/index.rb
@@ -297,9 +300,9 @@ licenses:
297
300
  - MIT
298
301
  metadata:
299
302
  bug_tracker_uri: https://github.com/whitequark/parser/issues
300
- changelog_uri: https://github.com/whitequark/parser/blob/v2.7.1.1/CHANGELOG.md
301
- documentation_uri: https://www.rubydoc.info/gems/parser/2.7.1.1
302
- source_code_uri: https://github.com/whitequark/parser/tree/v2.7.1.1
303
+ changelog_uri: https://github.com/whitequark/parser/blob/v2.7.1.2/CHANGELOG.md
304
+ documentation_uri: https://www.rubydoc.info/gems/parser/2.7.1.2
305
+ source_code_uri: https://github.com/whitequark/parser/tree/v2.7.1.2
303
306
  post_install_message:
304
307
  rdoc_options: []
305
308
  require_paths: