parser 2.7.1.1 → 2.7.1.2

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.
@@ -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: