parser 2.3.0.6 → 2.3.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff5a785602415af9bb4752bc39eb9315b502f4bf
4
- data.tar.gz: 611593226a424ad609a42b72e5cc79a2a3010043
3
+ metadata.gz: 5a26d867d04edc904373d219c6b5a1e96ef04d4d
4
+ data.tar.gz: 85ac7d2151c8f28584ff798c7e23e12ae1500afe
5
5
  SHA512:
6
- metadata.gz: 051c85691ec47584262edb2477862918713d31e87f36c0ce3235a0f1eb7f14bf78c40cba6a8d5993281024231d666e77774becd71874c8c55fc6166d4d39ac20
7
- data.tar.gz: af772a8dd0be6622896d24443fc2999e117645f8a67bee0d2d7179c601b4938d2a4055a70450b2771f23a770130aec36c7be339d4fcd740eba5971025567ec51
6
+ metadata.gz: ffa1d0b020bcb5802d13cb69477427d9f95d303a53de4addc37acdc82b03ac6abd2cc061e5a1a9fbf1cf6c2407a10c1dbb821ec0ce5c45b8ec103f5138d0894d
7
+ data.tar.gz: e75209c9674253351150671eed73530c052bbfc122cac1f199163f23cbd77420a2cd7ae7cf8268c059ca12c68a5750acdb74e81433328c5472b8bb81db4b2237
@@ -13,6 +13,7 @@ rvm:
13
13
  matrix:
14
14
  allow_failures:
15
15
  - rvm: ruby-head
16
+ - rvm: rbx-2
16
17
  before_install:
17
18
  - gem update bundler
18
19
  - bundle --version
@@ -1,6 +1,20 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ v2.3.0.7 (2016-03-25)
5
+ ---------------------
6
+
7
+ API modifications:
8
+ * Source::Diagnostic: handle ranges pointing to newlines (#273). (whitequark)
9
+
10
+ Features implemented:
11
+ * Parser::Base#tokenize: allow recovery from syntax errors. (whitequark)
12
+ * lexer.rl: "a=1; a b: 1": allow label after command clashing with local. (whitequark)
13
+ * lexer.rl: "undef %s(x)": emit %s literals in expr_fname in 2.3 mode. (whitequark)
14
+
15
+ Bugs fixed:
16
+ * Builders::Default: reject non-UTF-8 compatible literals. (whitequark)
17
+
4
18
  v2.3.0.6 (2016-02-14)
5
19
  ---------------------
6
20
 
@@ -29,7 +43,7 @@ Bugs fixed:
29
43
  * Add :csend to Parser::Meta::NODE_TYPES (Markus Schirp)
30
44
  * lexer/dedenter: "\<\<x\n y\\n z\nx": don't dedent after escaped newline. (whitequark)
31
45
 
32
- v2.3.0.6 (2016-01-16)
46
+ v2.3.0.7 (2016-01-16)
33
47
  ---------------------
34
48
 
35
49
  v2.3.0.1 (2016-01-14)
data/README.md CHANGED
@@ -227,9 +227,9 @@ issue](https://github.com/whitequark/parser/issues/72).
227
227
 
228
228
  It is unknown whether any gems are affected by this issue.
229
229
 
230
- ### Invalid characters inside comments
230
+ ### Invalid characters inside comments and literals
231
231
 
232
- Ruby MRI permits arbitrary non-7-bit characters to appear in comments regardless of source encoding.
232
+ Ruby MRI permits arbitrary non-7-bit byte sequences to appear in comments, as well as in string or symbol literals in form of escape sequences, regardless of source encoding. Parser requires all source code, including the expanded escape sequences, to consist of valid byte sequences in the source encoding that are convertible to UTF-8.
233
233
 
234
234
  As of 2013-07-25, there are about 180 affected gems.
235
235
 
@@ -239,12 +239,6 @@ Ruby MRI 1.8 permits to specify a bare `\u` escape sequence in a string; it trea
239
239
 
240
240
  As of 2013-07-25, affected gems are: activerdf, activerdf_net7, fastreader, gkellog-reddy.
241
241
 
242
- ### Invalid Unicode escape sequences
243
-
244
- Ruby MRI 1.9+ permits to specify invalid Unicode codepoints in Unicode escape sequences, such as `\u{d800}`.
245
-
246
- As of 2013-07-25, affected gems are: aws_cloud_search.
247
-
248
242
  ### Dollar-dash
249
243
 
250
244
  (This one is so obscure I couldn't even think of a saner name for this issue.) Pre-2.1 Ruby allows
@@ -167,8 +167,7 @@ module Parser
167
167
  end
168
168
 
169
169
  ##
170
- # Parses a source buffer and returns the AST along with the comments of the
171
- # Ruby source code.
170
+ # Parses a source buffer and returns the AST and the source code comments.
172
171
  #
173
172
  # @see #parse
174
173
  # @see Parser::Source::Comment#associate
@@ -183,6 +182,12 @@ module Parser
183
182
  end
184
183
 
185
184
  ##
185
+ # Parses a source buffer and returns the AST, the source code comments,
186
+ # and the tokens emitted by the lexer. If `recover` is true and a fatal
187
+ # {SyntaxError} is encountered, `nil` is returned instead of the AST, and
188
+ # comments as well as tokens are only returned up to the location of
189
+ # the error.
190
+ #
186
191
  # Currently, token stream format returned by #tokenize is not documented,
187
192
  # but is considered part of a public API and only changed according
188
193
  # to Semantic Versioning.
@@ -193,16 +198,23 @@ module Parser
193
198
  # `:tSTRING "foo"`; such details must not be relied upon.
194
199
  #
195
200
  # @param [Parser::Source::Buffer] source_buffer
201
+ # @param [Boolean] recover If true, recover from syntax errors. False by default.
196
202
  # @return [Array]
197
203
  #
198
- def tokenize(source_buffer)
204
+ def tokenize(source_buffer, recover=false)
199
205
  @lexer.tokens = []
206
+ @lexer.comments = []
200
207
 
201
- ast, comments = parse_with_comments(source_buffer)
208
+ begin
209
+ ast = parse(source_buffer)
210
+ rescue Parser::SyntaxError
211
+ raise if !recover
212
+ end
202
213
 
203
- [ ast, comments, @lexer.tokens ]
214
+ [ ast, @lexer.comments, @lexer.tokens ]
204
215
  ensure
205
216
  @lexer.tokens = nil
217
+ @lexer.comments = nil
206
218
  end
207
219
 
208
220
  ##
@@ -120,12 +120,12 @@ module Parser
120
120
  # Strings
121
121
 
122
122
  def string(string_t)
123
- n(:str, [ value(string_t) ],
123
+ n(:str, [ string_value(string_t) ],
124
124
  delimited_string_map(string_t))
125
125
  end
126
126
 
127
127
  def string_internal(string_t)
128
- n(:str, [ value(string_t) ],
128
+ n(:str, [ string_value(string_t) ],
129
129
  unquoted_map(string_t))
130
130
  end
131
131
 
@@ -144,7 +144,7 @@ module Parser
144
144
  end
145
145
 
146
146
  def character(char_t)
147
- n(:str, [ value(char_t) ],
147
+ n(:str, [ string_value(char_t) ],
148
148
  prefix_string_map(char_t))
149
149
  end
150
150
 
@@ -156,12 +156,12 @@ module Parser
156
156
  # Symbols
157
157
 
158
158
  def symbol(symbol_t)
159
- n(:sym, [ value(symbol_t).to_sym ],
159
+ n(:sym, [ string_value(symbol_t).to_sym ],
160
160
  prefix_string_map(symbol_t))
161
161
  end
162
162
 
163
163
  def symbol_internal(symbol_t)
164
- n(:sym, [ value(symbol_t).to_sym ],
164
+ n(:sym, [ string_value(symbol_t).to_sym ],
165
165
  unquoted_map(symbol_t))
166
166
  end
167
167
 
@@ -1534,6 +1534,18 @@ module Parser
1534
1534
  token[0]
1535
1535
  end
1536
1536
 
1537
+ if defined?(Encoding)
1538
+ def string_value(token)
1539
+ unless token[0].valid_encoding?
1540
+ diagnostic(:error, :invalid_encoding, nil, token[1])
1541
+ end
1542
+
1543
+ token[0]
1544
+ end
1545
+ else
1546
+ alias string_value value
1547
+ end
1548
+
1537
1549
  def loc(token)
1538
1550
  # Pass through `nil`s and return nil for tNL.
1539
1551
  token[1] if token && token[0]
@@ -82,18 +82,19 @@ module Parser
82
82
  # @return [Array<String>]
83
83
  #
84
84
  def render
85
- if @location.line != @location.last_line
85
+ if @location.line == @location.last_line || @location.is?("\n")
86
+ ["#{@location}: #{@level}: #{message}"] + render_line(@location)
87
+ else
86
88
  # multi-line diagnostic
87
89
  first_line = first_line_only(@location)
88
90
  last_line = last_line_only(@location)
91
+ num_lines = (@location.last_line - @location.line) + 1
89
92
  buffer = @location.source_buffer
90
93
 
91
94
  last_lineno, last_column = buffer.decompose_position(@location.end_pos)
92
95
  ["#{@location}-#{last_lineno}:#{last_column}: #{@level}: #{message}"] +
93
- render_line(first_line, true, false) +
96
+ render_line(first_line, num_lines > 2, false) +
94
97
  render_line(last_line, false, true)
95
- else
96
- ["#{@location}: #{@level}: #{message}"] + render_line(@location)
97
98
  end
98
99
  end
99
100
 
@@ -104,7 +105,7 @@ module Parser
104
105
  #
105
106
  # @return [Array<String>]
106
107
  #
107
- def render_line(range, range_start=false, range_end=false)
108
+ def render_line(range, ellipsis=false, range_end=false)
108
109
  source_line = range.source_line
109
110
  highlight_line = ' ' * source_line.length
110
111
 
@@ -115,15 +116,17 @@ module Parser
115
116
  end
116
117
  end
117
118
 
118
- if !range_end && range.size >= 1
119
- highlight_line[range.column_range] = '^' + '~' * (range.size - 1)
119
+ if range.is?("\n")
120
+ highlight_line += "^"
120
121
  else
121
- highlight_line[range.column_range] = '~' * range.size
122
+ if !range_end && range.size >= 1
123
+ highlight_line[range.column_range] = '^' + '~' * (range.size - 1)
124
+ else
125
+ highlight_line[range.column_range] = '~' * range.size
126
+ end
122
127
  end
123
128
 
124
- if range_start
125
- highlight_line += '...'
126
- end
129
+ highlight_line += '...' if ellipsis
127
130
 
128
131
  [source_line, highlight_line].
129
132
  map { |line| "#{range.source_buffer.name}:#{range.line}: #{line}" }
@@ -150,7 +153,7 @@ module Parser
150
153
  def last_line_only(range)
151
154
  if range.line != range.last_line
152
155
  Source::Range.new(range.source_buffer,
153
- range.begin_pos + (range.source =~ /[^\n]*\Z/),
156
+ range.begin_pos + (range.source =~ /[^\n]*\z/),
154
157
  range.end_pos)
155
158
  else
156
159
  range
@@ -1197,7 +1197,7 @@ class Parser::Lexer
1197
1197
  emit(:tIDENTIFIER)
1198
1198
 
1199
1199
  if !@static_env.nil? && @static_env.declared?(tok)
1200
- fnext expr_end; fbreak;
1200
+ fnext expr_endfn; fbreak;
1201
1201
  else
1202
1202
  fnext *arg_or_cmdarg; fbreak;
1203
1203
  end
@@ -1282,6 +1282,17 @@ class Parser::Lexer
1282
1282
  ':'
1283
1283
  => { fhold; fgoto expr_beg; };
1284
1284
 
1285
+ '%s' c_any
1286
+ => {
1287
+ if version?(23)
1288
+ type, delimiter = tok[0..-2], tok[-1].chr
1289
+ fgoto *push_literal(type, delimiter, @ts);
1290
+ else
1291
+ p = @ts - 1
1292
+ fgoto expr_end;
1293
+ end
1294
+ };
1295
+
1285
1296
  w_any;
1286
1297
 
1287
1298
  c_any
@@ -1697,7 +1708,11 @@ class Parser::Lexer
1697
1708
  value = @escape || tok(@ts + 1)
1698
1709
 
1699
1710
  if version?(18)
1700
- emit(:tINTEGER, value[0].ord)
1711
+ if defined?(Encoding)
1712
+ emit(:tINTEGER, value.dup.force_encoding(Encoding::BINARY)[0].ord)
1713
+ else
1714
+ emit(:tINTEGER, value[0].ord)
1715
+ end
1701
1716
  else
1702
1717
  emit(:tCHARACTER, value)
1703
1718
  end
@@ -59,6 +59,9 @@ module Parser
59
59
  # Parser warnings
60
60
  :useless_else => 'else without rescue is useless',
61
61
 
62
+ # Parser errors that are not Ruby errors
63
+ :invalid_encoding => 'literal contains escape sequences incompatible with UTF-8',
64
+
62
65
  # Rewriter diagnostics
63
66
  :invalid_action => 'cannot %{action}',
64
67
  :clobbered => 'clobbered by: %{action}',
@@ -39,6 +39,9 @@ module Parser
39
39
  @queue = []
40
40
  @clobber = 0
41
41
  @insertions = 0 # clobbered zero-length positions; index 0 is the far left
42
+
43
+ @insert_before_multi_order = 0
44
+ @insert_after_multi_order = 0
42
45
  end
43
46
 
44
47
  ##
@@ -64,6 +67,28 @@ module Parser
64
67
  append Rewriter::Action.new(range.begin, content)
65
68
  end
66
69
 
70
+ ##
71
+ # Inserts new code before the given source range by allowing other
72
+ # insertions at the same position.
73
+ # Note that an insertion with latter invocation comes _before_ earlier
74
+ # insertion at the same position in the rewritten source.
75
+ #
76
+ # @example Inserting '[('
77
+ # rewriter.
78
+ # insert_before_multi(range, '(').
79
+ # insert_before_multi(range, '[').
80
+ # process
81
+ #
82
+ # @param [Range] range
83
+ # @param [String] content
84
+ # @return [Rewriter] self
85
+ # @raise [ClobberingError] when clobbering is detected
86
+ #
87
+ def insert_before_multi(range, content)
88
+ @insert_before_multi_order -= 1
89
+ append Rewriter::Action.new(range.begin, content, true, @insert_before_multi_order)
90
+ end
91
+
67
92
  ##
68
93
  # Inserts new code after the given source range.
69
94
  #
@@ -76,6 +101,28 @@ module Parser
76
101
  append Rewriter::Action.new(range.end, content)
77
102
  end
78
103
 
104
+ ##
105
+ # Inserts new code after the given source range by allowing other
106
+ # insertions at the same position.
107
+ # Note that an insertion with latter invocation comes _after_ earlier
108
+ # insertion at the same position in the rewritten source.
109
+ #
110
+ # @example Inserting ')]'
111
+ # rewriter.
112
+ # insert_after_multi(range, ')').
113
+ # insert_after_multi(range, ']').
114
+ # process
115
+ #
116
+ # @param [Range] range
117
+ # @param [String] content
118
+ # @return [Rewriter] self
119
+ # @raise [ClobberingError] when clobbering is detected
120
+ #
121
+ def insert_after_multi(range, content)
122
+ @insert_after_multi_order += 1
123
+ append Rewriter::Action.new(range.end, content, true, @insert_after_multi_order)
124
+ end
125
+
79
126
  ##
80
127
  # Replaces the code of the source range `range` with `content`.
81
128
  #
@@ -102,9 +149,7 @@ module Parser
102
149
  adjustment = 0
103
150
  source = @source_buffer.source.dup
104
151
 
105
- sorted_queue = @queue.sort_by { |action| action.range.begin_pos }
106
-
107
- sorted_queue.each do |action|
152
+ @queue.sort.each do |action|
108
153
  begin_pos = action.range.begin_pos + adjustment
109
154
  end_pos = begin_pos + action.range.length
110
155
 
@@ -205,7 +250,7 @@ module Parser
205
250
  # Replacing nothing with... nothing?
206
251
  return self if action.replacement.empty?
207
252
 
208
- if (conflicting = clobbered_insertion?(range))
253
+ if !action.allow_multiple_insertions? && (conflicting = clobbered_insertion?(range))
209
254
  raise_clobber_error(action, [conflicting])
210
255
  end
211
256
 
@@ -331,7 +376,7 @@ module Parser
331
376
  actions = existing.push(action).sort_by do |a|
332
377
  [a.range.begin_pos, a.range.end_pos]
333
378
  end
334
- range = actions.first.range.join(actions.last.range)
379
+ range = actions.first.range.join(actions.max_by { |a| a.range.end_pos }.range)
335
380
 
336
381
  Rewriter::Action.new(range, merge_replacements(actions))
337
382
  end
@@ -5,14 +5,26 @@ module Parser
5
5
  # @api private
6
6
  #
7
7
  class Rewriter::Action
8
- attr_reader :range, :replacement
8
+ include Comparable
9
9
 
10
- def initialize(range, replacement='')
11
- @range, @replacement = range, replacement
10
+ attr_reader :range, :replacement, :allow_multiple_insertions, :order
11
+ alias_method :allow_multiple_insertions?, :allow_multiple_insertions
12
+
13
+ def initialize(range, replacement='', allow_multiple_insertions = false, order = 0)
14
+ @range = range
15
+ @replacement = replacement
16
+ @allow_multiple_insertions = allow_multiple_insertions
17
+ @order = order
12
18
 
13
19
  freeze
14
20
  end
15
21
 
22
+ def <=>(other)
23
+ result = range.begin_pos <=> other.range.begin_pos
24
+ return result unless result.zero?
25
+ order <=> other.order
26
+ end
27
+
16
28
  def to_s
17
29
  if @range.length == 0 && @replacement.empty?
18
30
  'do nothing'
@@ -1,3 +1,3 @@
1
1
  module Parser
2
- VERSION = '2.3.0.6'
2
+ VERSION = '2.3.0.7'
3
3
  end
@@ -69,4 +69,26 @@ class TestDiagnostic < Minitest::Test
69
69
  '(string):3: ~~~ ~~~ '
70
70
  ], diag.render)
71
71
  end
72
+
73
+ def test_bug_error_on_newline
74
+ # regression test; see GitHub issue 273
75
+ source = <<-CODE
76
+ {
77
+ foo: ->() # I forgot my brace
78
+ }
79
+ }
80
+ CODE
81
+ @buffer = Parser::Source::Buffer.new('(string)')
82
+ @buffer.source = source
83
+
84
+ location = Parser::Source::Range.new(@buffer, 33, 34)
85
+ diag = Parser::Diagnostic.new(:error, :unexpected_token, { :token => 'tNL' },
86
+ location)
87
+
88
+ assert_equal([
89
+ '(string):2:32: error: unexpected token tNL',
90
+ '(string):2: foo: ->() # I forgot my brace',
91
+ '(string):2: ^'
92
+ ], diag.render)
93
+ end
72
94
  end
@@ -2349,6 +2349,26 @@ class TestLexer < Minitest::Test
2349
2349
  :tSYMBOL, 'b', [10, 12])
2350
2350
  end
2351
2351
 
2352
+ def test_fname_pct_s__22
2353
+ setup_lexer 22
2354
+ @lex.state = :expr_fname
2355
+ assert_scanned("%s(a)",
2356
+ :tPERCENT, '%', [0, 1],
2357
+ :tIDENTIFIER, 's', [1, 2],
2358
+ :tLPAREN2, '(', [2, 3],
2359
+ :tIDENTIFIER, 'a', [3, 4],
2360
+ :tRPAREN, ')', [4, 5])
2361
+ end
2362
+
2363
+ def test_fname_pct_s__23
2364
+ setup_lexer 23
2365
+ @lex.state = :expr_fname
2366
+ assert_scanned("%s(a)",
2367
+ :tSYMBEG, '%s(', [0, 3],
2368
+ :tSTRING_CONTENT, 'a', [3, 4],
2369
+ :tSTRING_END, ')', [4, 5])
2370
+ end
2371
+
2352
2372
  def test_static_env
2353
2373
  env = Parser::StaticEnvironment.new
2354
2374
  env.declare "a"
@@ -5114,6 +5114,31 @@ class TestParser < Minitest::Test
5114
5114
  end
5115
5115
  end
5116
5116
 
5117
+ def test_tokenize_recover
5118
+ with_versions(ALL_VERSIONS) do |_ver, parser|
5119
+ source_file = Parser::Source::Buffer.new('(tokenize)')
5120
+ source_file.source = "1 + # foo\n "
5121
+
5122
+ range = lambda do |from, to|
5123
+ Parser::Source::Range.new(source_file, from, to)
5124
+ end
5125
+
5126
+ ast, comments, tokens = parser.tokenize(source_file, true)
5127
+
5128
+ assert_equal nil, ast
5129
+
5130
+ assert_equal [
5131
+ Parser::Source::Comment.new(range.call(4, 9))
5132
+ ], comments
5133
+
5134
+ assert_equal [
5135
+ [:tINTEGER, [ 1, range.call(0, 1) ]],
5136
+ [:tPLUS, [ '+', range.call(2, 3) ]],
5137
+ [:tCOMMENT, [ '# foo', range.call(4, 9) ]],
5138
+ ], tokens
5139
+ end
5140
+ end
5141
+
5117
5142
  #
5118
5143
  # Bug-specific tests
5119
5144
  #
@@ -5200,6 +5225,53 @@ class TestParser < Minitest::Test
5200
5225
  %Q{f <<-TABLE do\nTABLE\nend})
5201
5226
  end
5202
5227
 
5228
+ def test_bug_ascii_8bit_in_literal
5229
+ if defined?(Encoding)
5230
+ assert_diagnoses(
5231
+ [:error, :invalid_encoding],
5232
+ %q{".\xc3."},
5233
+ %q{^^^^^^^^ location},
5234
+ ALL_VERSIONS)
5235
+
5236
+ assert_diagnoses(
5237
+ [:error, :invalid_encoding],
5238
+ %q{%W"x .\xc3."},
5239
+ %q{ ^^^^^^ location},
5240
+ ALL_VERSIONS)
5241
+
5242
+ assert_diagnoses(
5243
+ [:error, :invalid_encoding],
5244
+ %q{:".\xc3."},
5245
+ %q{ ^^^^^^ location},
5246
+ ALL_VERSIONS)
5247
+ end
5248
+
5249
+ assert_diagnoses(
5250
+ [:error, :invalid_encoding],
5251
+ %q{%I"x .\xc3."},
5252
+ %q{ ^^^^^^ location},
5253
+ ALL_VERSIONS - %w(1.8 1.9 ios mac))
5254
+
5255
+ assert_parses(
5256
+ s(:int, 0xc3),
5257
+ %q{?\xc3},
5258
+ %q{},
5259
+ %w(1.8))
5260
+
5261
+ assert_diagnoses(
5262
+ [:error, :invalid_encoding],
5263
+ %q{?\xc3},
5264
+ %q{^^^^^ location},
5265
+ ALL_VERSIONS - %w(1.8))
5266
+
5267
+ assert_parses(
5268
+ s(:str, "проверка"),
5269
+ %q{# coding:utf-8
5270
+ "\xD0\xBF\xD1\x80\xD0\xBE\xD0\xB2\xD0\xB5\xD1\x80\xD0\xBA\xD0\xB0"},
5271
+ %q{},
5272
+ ALL_VERSIONS - %w(1.8))
5273
+ end
5274
+
5203
5275
  def test_ruby_bug_9669
5204
5276
  assert_parses(
5205
5277
  s(:def, :a, s(:args, s(:kwarg, :b)), s(:return)),
@@ -5318,6 +5390,21 @@ class TestParser < Minitest::Test
5318
5390
  ALL_VERSIONS - %w(1.8 1.9 2.0 2.1 2.2 ios mac))
5319
5391
  end
5320
5392
 
5393
+ def test_ruby_bug_12073
5394
+ assert_parses(
5395
+ s(:begin,
5396
+ s(:lvasgn, :a,
5397
+ s(:int, 1)),
5398
+ s(:send, nil, :a,
5399
+ s(:hash,
5400
+ s(:pair,
5401
+ s(:sym, :b),
5402
+ s(:int, 1))))),
5403
+ %q{a = 1; a b: 1},
5404
+ %q{},
5405
+ ALL_VERSIONS - %w(1.8))
5406
+ end
5407
+
5321
5408
  def test_parser_bug_198
5322
5409
  assert_parses(
5323
5410
  s(:array,
@@ -135,6 +135,16 @@ class TestSourceRewriter < Minitest::Test
135
135
  end
136
136
  end
137
137
 
138
+ def test_intentional_multiple_insertions_at_same_location
139
+ assert_equal 'foo [(bar)] baz',
140
+ @rewriter.
141
+ insert_before_multi(range(4, 0), '(').
142
+ insert_after_multi(range(7, 0), ')').
143
+ insert_before_multi(range(4, 0), '[').
144
+ insert_after_multi(range(7, 0), ']').
145
+ process
146
+ end
147
+
138
148
  def test_insertion_within_replace_clobber
139
149
  silence_diagnostics
140
150
 
@@ -175,6 +185,46 @@ class TestSourceRewriter < Minitest::Test
175
185
  end
176
186
  end
177
187
 
188
+ def test_multi_insertion_within_replace_clobber
189
+ silence_diagnostics
190
+
191
+ assert_raises Parser::ClobberingError do
192
+ @rewriter.
193
+ replace(range(3, 2), '<').
194
+ insert_after_multi(range(3, 1), '>')
195
+ end
196
+ end
197
+
198
+ def test_multi_insertion_within_remove_clobber
199
+ silence_diagnostics
200
+
201
+ assert_raises Parser::ClobberingError do
202
+ @rewriter.
203
+ remove(range(3, 2)).
204
+ insert_after_multi(range(3, 1), '>')
205
+ end
206
+ end
207
+
208
+ def test_replace_overlapping_multi_insertion_clobber
209
+ silence_diagnostics
210
+
211
+ assert_raises Parser::ClobberingError do
212
+ @rewriter.
213
+ insert_after_multi(range(3, 1), '>').
214
+ replace(range(3, 2), '<')
215
+ end
216
+ end
217
+
218
+ def test_remove_overlapping_multi_insertion_clobber
219
+ silence_diagnostics
220
+
221
+ assert_raises Parser::ClobberingError do
222
+ @rewriter.
223
+ insert_after_multi(range(3, 1), '>').
224
+ remove(range(3, 2))
225
+ end
226
+ end
227
+
178
228
  def test_insertion_on_merged_insertion_clobber
179
229
  # 2 insertions at the same point clobber each other, even if the 1st one
180
230
  # was merged with an adjacent edit, and even if the same text is being
@@ -314,6 +364,17 @@ class TestSourceRewriter < Minitest::Test
314
364
  end
315
365
  end
316
366
 
367
+ def test_replaced_ranges_merge_when_furthest_right_range_is_not_furthest_left
368
+ # regression test; previously, when actions were merged, the resulting
369
+ # replaced range could be too small sometimes
370
+ assert_equal 'foo_***_***',
371
+ @rewriter.
372
+ replace(range(3, 1), '_').
373
+ replace(range(7, 1), '_').
374
+ replace(range(4, 7), '***_***').
375
+ process
376
+ end
377
+
317
378
  def test_clobber
318
379
  diagnostics = []
319
380
  @rewriter.diagnostics.consumer = lambda do |diag|
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.3.0.6
4
+ version: 2.3.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - whitequark
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-14 00:00:00.000000000 Z
11
+ date: 2016-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast