parser 2.7.0.5 → 2.7.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +21 -32
  4. data/CHANGELOG.md +59 -1
  5. data/README.md +2 -2
  6. data/Rakefile +2 -1
  7. data/doc/AST_FORMAT.md +106 -3
  8. data/lib/parser.rb +1 -0
  9. data/lib/parser/all.rb +1 -0
  10. data/lib/parser/ast/processor.rb +9 -0
  11. data/lib/parser/builders/default.rb +103 -12
  12. data/lib/parser/context.rb +1 -0
  13. data/lib/parser/current.rb +13 -4
  14. data/lib/parser/diagnostic.rb +1 -1
  15. data/lib/parser/diagnostic/engine.rb +1 -2
  16. data/lib/parser/lexer.rl +15 -1
  17. data/lib/parser/macruby.y +14 -4
  18. data/lib/parser/messages.rb +15 -0
  19. data/lib/parser/meta.rb +4 -4
  20. data/lib/parser/ruby18.y +2 -0
  21. data/lib/parser/ruby19.y +14 -4
  22. data/lib/parser/ruby20.y +14 -4
  23. data/lib/parser/ruby21.y +9 -2
  24. data/lib/parser/ruby22.y +9 -2
  25. data/lib/parser/ruby23.y +9 -2
  26. data/lib/parser/ruby24.y +9 -2
  27. data/lib/parser/ruby25.y +9 -2
  28. data/lib/parser/ruby26.y +9 -2
  29. data/lib/parser/ruby27.y +28 -8
  30. data/lib/parser/ruby28.y +3043 -0
  31. data/lib/parser/rubymotion.y +14 -4
  32. data/lib/parser/runner.rb +26 -2
  33. data/lib/parser/runner/ruby_rewrite.rb +2 -2
  34. data/lib/parser/source/buffer.rb +3 -1
  35. data/lib/parser/source/comment/associator.rb +14 -4
  36. data/lib/parser/source/map/endless_definition.rb +23 -0
  37. data/lib/parser/source/range.rb +17 -1
  38. data/lib/parser/source/tree_rewriter.rb +115 -12
  39. data/lib/parser/source/tree_rewriter/action.rb +135 -26
  40. data/lib/parser/tree_rewriter.rb +1 -2
  41. data/lib/parser/version.rb +1 -1
  42. data/parser.gemspec +3 -2
  43. data/test/helper.rb +49 -6
  44. data/test/parse_helper.rb +27 -23
  45. data/test/test_ast_processor.rb +32 -0
  46. data/test/test_base.rb +1 -1
  47. data/test/test_current.rb +2 -0
  48. data/test/test_diagnostic.rb +6 -7
  49. data/test/test_diagnostic_engine.rb +5 -8
  50. data/test/test_lexer.rb +17 -8
  51. data/test/test_meta.rb +12 -0
  52. data/test/test_parser.rb +477 -54
  53. data/test/test_runner_parse.rb +22 -1
  54. data/test/test_runner_rewrite.rb +1 -1
  55. data/test/test_source_buffer.rb +4 -1
  56. data/test/test_source_comment.rb +2 -2
  57. data/test/test_source_comment_associator.rb +47 -15
  58. data/test/test_source_map.rb +1 -2
  59. data/test/test_source_range.rb +29 -9
  60. data/test/test_source_rewriter.rb +4 -4
  61. data/test/test_source_rewriter_action.rb +2 -2
  62. data/test/test_source_tree_rewriter.rb +201 -13
  63. metadata +19 -12
@@ -28,8 +28,7 @@ module Parser
28
28
  # EOF
29
29
  #
30
30
  # ast = Parser::CurrentRuby.parse code
31
- # buffer = Parser::Source::Buffer.new('(example)')
32
- # buffer.source = code
31
+ # buffer = Parser::Source::Buffer.new('(example)', source: code)
33
32
  # rewriter = RemoveDo.new
34
33
  #
35
34
  # # Rewrite the AST, returns a String with the new form.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parser
4
- VERSION = '2.7.0.5'
4
+ VERSION = '2.7.1.4'
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
  )
@@ -41,10 +42,10 @@ Gem::Specification.new do |spec|
41
42
 
42
43
  spec.required_ruby_version = '>= 2.0.0'
43
44
 
44
- spec.add_dependency 'ast', '~> 2.4.0'
45
+ spec.add_dependency 'ast', '~> 2.4.1'
45
46
 
46
47
  spec.add_development_dependency 'bundler', '>= 1.15', '< 3.0.0'
47
- spec.add_development_dependency 'rake', '~> 10.0'
48
+ spec.add_development_dependency 'rake', '~> 13.0.1'
48
49
  spec.add_development_dependency 'racc', '= 1.4.15'
49
50
  spec.add_development_dependency 'cliver', '~> 0.3.2'
50
51
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tempfile'
4
- require 'minitest/test'
5
-
6
4
  require 'simplecov'
7
5
 
8
6
  if ENV.include?('COVERAGE') && SimpleCov.usable?
@@ -20,6 +18,7 @@ if ENV.include?('COVERAGE') && SimpleCov.usable?
20
18
  ruby25.y
21
19
  ruby26.y
22
20
  ruby27.y
21
+ ruby28.y
23
22
  ),
24
23
  File.expand_path('../../lib/parser', __FILE__))
25
24
 
@@ -27,9 +26,9 @@ if ENV.include?('COVERAGE') && SimpleCov.usable?
27
26
  at_exit { RaccCoverage.stop }
28
27
 
29
28
  SimpleCov.start do
30
- self.formatter = SimpleCov::Formatter::MultiFormatter[
31
- SimpleCov::Formatter::HTMLFormatter,
32
- ]
29
+ self.formatter = SimpleCov::Formatter::MultiFormatter.new(
30
+ SimpleCov::Formatter::HTMLFormatter
31
+ )
33
32
 
34
33
  add_group 'Grammars' do |source_file|
35
34
  source_file.filename =~ %r{\.y$}
@@ -52,9 +51,53 @@ require 'minitest/autorun'
52
51
  $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
53
52
  require 'parser'
54
53
 
54
+ module NodeCollector
55
+ extend self
56
+ attr_accessor :callbacks, :nodes
57
+ self.callbacks = []
58
+ self.nodes = []
59
+
60
+ def check
61
+ @callbacks.each do |callback|
62
+ @nodes.each { |node| callback.call(node) }
63
+ end
64
+ puts "#{callbacks.size} additional tests on #{nodes.size} nodes ran successfully"
65
+ end
66
+
67
+ Minitest.after_run { check }
68
+ end
69
+
70
+ def for_each_node(&block)
71
+ NodeCollector.callbacks << block
72
+ end
73
+
55
74
  class Parser::AST::Node
56
75
  def initialize(type, *)
57
- raise "Type #{type} missing from Parser::Meta::NODE_TYPES" unless Parser::Meta::NODE_TYPES.include?(type)
76
+ NodeCollector.nodes << self
58
77
  super
59
78
  end
60
79
  end
80
+
81
+ # Special test extension that records a context of the parser
82
+ # for any node that is created
83
+ module NodeContextExt
84
+ module NodeExt
85
+ attr_reader :context
86
+
87
+ def assign_properties(properties)
88
+ super
89
+
90
+ if (context = properties[:context])
91
+ @context = context
92
+ end
93
+ end
94
+ end
95
+ Parser::AST::Node.prepend(NodeExt)
96
+
97
+ module BuilderExt
98
+ def n(type, children, source_map)
99
+ super.updated(nil, nil, context: @parser.context.stack.dup)
100
+ end
101
+ end
102
+ Parser::Builders::Default.prepend(BuilderExt)
103
+ end
@@ -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}"
@@ -84,8 +85,7 @@ module ParseHelper
84
85
  end
85
86
 
86
87
  def try_parsing(ast, code, parser, source_maps, version)
87
- source_file = Parser::Source::Buffer.new('(assert_parses)')
88
- source_file.source = code
88
+ source_file = Parser::Source::Buffer.new('(assert_parses)', source: code)
89
89
 
90
90
  begin
91
91
  parsed_ast = parser.parse(source_file)
@@ -105,8 +105,7 @@ module ParseHelper
105
105
  assert_equal ast, parsed_ast,
106
106
  "(#{version}) AST equality"
107
107
 
108
- parse_source_map_descriptions(source_maps) \
109
- do |begin_pos, end_pos, map_field, ast_path, line|
108
+ parse_source_map_descriptions(source_maps) do |begin_pos, end_pos, map_field, ast_path, line|
110
109
 
111
110
  astlet = traverse_ast(parsed_ast, ast_path)
112
111
 
@@ -142,8 +141,7 @@ module ParseHelper
142
141
  # ~~~
143
142
  def assert_diagnoses(diagnostic, code, source_maps='', versions=ALL_VERSIONS)
144
143
  with_versions(versions) do |version, parser|
145
- source_file = Parser::Source::Buffer.new('(assert_diagnoses)')
146
- source_file.source = code
144
+ source_file = Parser::Source::Buffer.new('(assert_diagnoses)', source: code)
147
145
 
148
146
  begin
149
147
  parser = parser.parse(source_file)
@@ -159,15 +157,14 @@ module ParseHelper
159
157
 
160
158
  level, reason, arguments = diagnostic
161
159
  arguments ||= {}
162
- message = Parser::MESSAGES[reason] % arguments
160
+ message = Parser::Messages.compile(reason, arguments)
163
161
 
164
162
  assert_equal level, emitted_diagnostic.level
165
163
  assert_equal reason, emitted_diagnostic.reason
166
164
  assert_equal arguments, emitted_diagnostic.arguments
167
165
  assert_equal message, emitted_diagnostic.message
168
166
 
169
- parse_source_map_descriptions(source_maps) \
170
- do |begin_pos, end_pos, map_field, ast_path, line|
167
+ parse_source_map_descriptions(source_maps) do |begin_pos, end_pos, map_field, ast_path, line|
171
168
 
172
169
  case map_field
173
170
  when 'location'
@@ -201,8 +198,7 @@ module ParseHelper
201
198
  # ~~~
202
199
  def assert_diagnoses_many(diagnostics, code, versions=ALL_VERSIONS)
203
200
  with_versions(versions) do |version, parser|
204
- source_file = Parser::Source::Buffer.new('(assert_diagnoses_many)')
205
- source_file.source = code
201
+ source_file = Parser::Source::Buffer.new('(assert_diagnoses_many)', source: code)
206
202
 
207
203
  begin
208
204
  parser = parser.parse(source_file)
@@ -215,7 +211,7 @@ module ParseHelper
215
211
  diagnostics.zip(@diagnostics) do |expected_diagnostic, actual_diagnostic|
216
212
  level, reason, arguments = expected_diagnostic
217
213
  arguments ||= {}
218
- message = Parser::MESSAGES[reason] % arguments
214
+ message = Parser::Messages.compile(reason, arguments)
219
215
 
220
216
  assert_equal level, actual_diagnostic.level
221
217
  assert_equal reason, actual_diagnostic.reason
@@ -227,8 +223,7 @@ module ParseHelper
227
223
 
228
224
  def refute_diagnoses(code, versions=ALL_VERSIONS)
229
225
  with_versions(versions) do |version, parser|
230
- source_file = Parser::Source::Buffer.new('(refute_diagnoses)')
231
- source_file.source = code
226
+ source_file = Parser::Source::Buffer.new('(refute_diagnoses)', source: code)
232
227
 
233
228
  begin
234
229
  parser = parser.parse(source_file)
@@ -244,16 +239,15 @@ module ParseHelper
244
239
 
245
240
  def assert_context(context, code, versions=ALL_VERSIONS)
246
241
  with_versions(versions) do |version, parser|
247
- source_file = Parser::Source::Buffer.new('(assert_context)')
248
- source_file.source = code
242
+ source_file = Parser::Source::Buffer.new('(assert_context)', source: code)
249
243
 
250
- begin
251
- parser.parse(source_file)
252
- rescue Parser::SyntaxError
253
- # do nothing; the diagnostic was reported
254
- end
244
+ parsed_ast = parser.parse(source_file)
255
245
 
256
- assert_equal parser.context.stack, context, "(#{version}) parsing context"
246
+ nodes = find_matching_nodes(parsed_ast) { |node| node.type == :send && node.children[1] == :get_context }
247
+ assert_equal 1, nodes.count, "there must exactly 1 `get_context()` call"
248
+
249
+ node = nodes.first
250
+ assert_equal context, node.context, "(#{version}) expect parsing context to match"
257
251
  end
258
252
  end
259
253
 
@@ -316,4 +310,14 @@ module ParseHelper
316
310
  matching_children[index]
317
311
  end
318
312
  end
313
+
314
+ def find_matching_nodes(ast, &block)
315
+ return [] unless ast.is_a?(AST::Node)
316
+
317
+ result = []
318
+ result << ast if block.call(ast)
319
+ ast.children.each { |child| result += find_matching_nodes(child, &block) }
320
+
321
+ result
322
+ end
319
323
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'helper'
4
+
5
+ class TestASTProcessor < Minitest::Test
6
+ LEAF_NODES = %i[
7
+ sym str int float complex rational
8
+ true false nil self
9
+ __FILE__ __LINE__ __ENCODING__
10
+ cbase regopt zsuper
11
+ match_with_trailing_comma match_nil_pattern
12
+ forward_args forwarded_args numargs kwnilarg
13
+ objc_varargs objc_restarg objc_kwarg
14
+ ident
15
+ ].freeze
16
+
17
+ def setup
18
+ @traversible = Parser::AST::Processor
19
+ .instance_methods(false)
20
+ .map { |mid| mid.to_s.scan(/\Aon_(.*)/) }
21
+ .flatten
22
+ .map(&:to_sym)
23
+
24
+ @traversible += LEAF_NODES
25
+ end
26
+
27
+ def test_nodes_are_traversible
28
+ for_each_node do |node|
29
+ assert_includes @traversible, node.type
30
+ end
31
+ end
32
+ end
@@ -26,6 +26,6 @@ class TestBase < Minitest::Test
26
26
  def test_loc_dup
27
27
  ast = Parser::CurrentRuby.parse('1')
28
28
  assert_nil ast.loc.dup.node
29
- Parser::AST::Node.new(:root, [], :location => ast.loc)
29
+ Parser::AST::Node.new(:zsuper, [], :location => ast.loc)
30
30
  end
31
31
  end
@@ -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
@@ -4,8 +4,8 @@ require 'helper'
4
4
 
5
5
  class TestDiagnostic < Minitest::Test
6
6
  def setup
7
- @buffer = Parser::Source::Buffer.new('(string)')
8
- @buffer.source = 'if (this is some bad code + bugs)'
7
+ @buffer = Parser::Source::Buffer.new('(string)',
8
+ source: 'if (this is some bad code + bugs)')
9
9
 
10
10
  @range1 = Parser::Source::Range.new(@buffer, 0, 2) # if
11
11
  @range2 = Parser::Source::Range.new(@buffer, 4, 8) # this
@@ -16,7 +16,7 @@ class TestDiagnostic < Minitest::Test
16
16
  Parser::Diagnostic.new(:foobar, :escape_eof, {}, @range1)
17
17
  end
18
18
 
19
- assert_match /level/, error.message
19
+ assert_match(/level/, error.message)
20
20
  end
21
21
 
22
22
  def test_freezes
@@ -50,8 +50,8 @@ class TestDiagnostic < Minitest::Test
50
50
  end
51
51
 
52
52
  def test_multiline_render
53
- @buffer = Parser::Source::Buffer.new('(string)')
54
- @buffer.source = "abc abc abc\ndef def def\nghi ghi ghi\n"
53
+ @buffer = Parser::Source::Buffer.new('(string)',
54
+ source: "abc abc abc\ndef def def\nghi ghi ghi\n")
55
55
 
56
56
  location = Parser::Source::Range.new(@buffer, 4, 27)
57
57
 
@@ -80,8 +80,7 @@ class TestDiagnostic < Minitest::Test
80
80
  }
81
81
  }
82
82
  CODE
83
- @buffer = Parser::Source::Buffer.new('(string)')
84
- @buffer.source = source
83
+ @buffer = Parser::Source::Buffer.new('(string)', source: source)
85
84
 
86
85
  location = Parser::Source::Range.new(@buffer, 33, 34)
87
86
  diag = Parser::Diagnostic.new(:error, :unexpected_token, { :token => 'tNL' },
@@ -4,9 +4,6 @@ require 'helper'
4
4
 
5
5
  class TestDiagnosticEngine < Minitest::Test
6
6
  def setup
7
- @buffer = Parser::Source::Buffer.new('(source)')
8
- @buffer.source = 'foobar'
9
-
10
7
  @engine = Parser::Diagnostic::Engine.new
11
8
 
12
9
  @queue = []
@@ -14,7 +11,7 @@ class TestDiagnosticEngine < Minitest::Test
14
11
  end
15
12
 
16
13
  def test_process_warnings
17
- warn = Parser::Diagnostic.new(:warning, :invalid_escape, @buffer, 1..2)
14
+ warn = Parser::Diagnostic.new(:warning, :invalid_escape, {}, 1..2)
18
15
  @engine.process(warn)
19
16
 
20
17
  assert_equal [warn], @queue
@@ -23,7 +20,7 @@ class TestDiagnosticEngine < Minitest::Test
23
20
  def test_ignore_warnings
24
21
  @engine.ignore_warnings = true
25
22
 
26
- warn = Parser::Diagnostic.new(:warning, :invalid_escape, @buffer, 1..2)
23
+ warn = Parser::Diagnostic.new(:warning, :invalid_escape, {}, 1..2)
27
24
  @engine.process(warn)
28
25
 
29
26
  assert_equal [], @queue
@@ -32,7 +29,7 @@ class TestDiagnosticEngine < Minitest::Test
32
29
  def test_all_errors_are_fatal
33
30
  @engine.all_errors_are_fatal = true
34
31
 
35
- error = Parser::Diagnostic.new(:error, :invalid_escape, @buffer, 1..2)
32
+ error = Parser::Diagnostic.new(:error, :invalid_escape, {}, 1..2)
36
33
 
37
34
  err = assert_raises Parser::SyntaxError do
38
35
  @engine.process(error)
@@ -44,14 +41,14 @@ class TestDiagnosticEngine < Minitest::Test
44
41
  end
45
42
 
46
43
  def test_all_errors_are_collected
47
- error = Parser::Diagnostic.new(:error, :invalid_escape, @buffer, 1..2)
44
+ error = Parser::Diagnostic.new(:error, :invalid_escape, {}, 1..2)
48
45
  @engine.process(error)
49
46
 
50
47
  assert_equal [error], @queue
51
48
  end
52
49
 
53
50
  def test_fatal_error
54
- fatal = Parser::Diagnostic.new(:fatal, :invalid_escape, @buffer, 1..2)
51
+ fatal = Parser::Diagnostic.new(:fatal, :invalid_escape, {}, 1..2)
55
52
 
56
53
  assert_raises Parser::SyntaxError do
57
54
  @engine.process(fatal)
@@ -37,9 +37,8 @@ class TestLexer < Minitest::Test
37
37
  end
38
38
 
39
39
  def assert_escape(expected, input)
40
- source_buffer = Parser::Source::Buffer.new('(assert_escape)')
41
-
42
- source_buffer.source = "\"\\#{input}\"".encode(input.encoding)
40
+ source_buffer = Parser::Source::Buffer.new('(assert_escape)',
41
+ source: "\"\\#{input}\"".encode(input.encoding))
43
42
 
44
43
  @lex.reset
45
44
  @lex.source_buffer = source_buffer
@@ -71,8 +70,7 @@ class TestLexer < Minitest::Test
71
70
  end
72
71
 
73
72
  def assert_scanned(input, *args)
74
- source_buffer = Parser::Source::Buffer.new('(assert_scanned)')
75
- source_buffer.source = input
73
+ source_buffer = Parser::Source::Buffer.new('(assert_scanned)', source: input)
76
74
 
77
75
  @lex.reset(false)
78
76
  @lex.source_buffer = source_buffer
@@ -3569,14 +3567,25 @@ class TestLexer < Minitest::Test
3569
3567
  :tIDENTIFIER, 're', [1, 3])
3570
3568
  end
3571
3569
 
3570
+ def test_endless_method
3571
+ setup_lexer(28)
3572
+
3573
+ assert_scanned('def foo() = 42',
3574
+ :kDEF, "def", [0, 3],
3575
+ :tIDENTIFIER, "foo", [4, 7],
3576
+ :tLPAREN2, "(", [7, 8],
3577
+ :tRPAREN, ")", [8, 9],
3578
+ :tEQL, "=", [10, 11],
3579
+ :tINTEGER, 42, [12, 14])
3580
+ end
3581
+
3572
3582
  def lex_numbered_parameter(input)
3573
3583
  @lex.max_numparam_stack.push
3574
3584
 
3575
3585
  @lex.context = Parser::Context.new
3576
3586
  @lex.context.push(:block)
3577
3587
 
3578
- source_buffer = Parser::Source::Buffer.new('(assert_lex_numbered_parameter)')
3579
- source_buffer.source = input
3588
+ source_buffer = Parser::Source::Buffer.new('(assert_lex_numbered_parameter)', source: input)
3580
3589
 
3581
3590
  @lex.source_buffer = source_buffer
3582
3591
 
@@ -3594,7 +3603,7 @@ class TestLexer < Minitest::Test
3594
3603
 
3595
3604
  def refute_scanned_numbered_parameter(input, message = nil)
3596
3605
  err = assert_raises Parser::SyntaxError do
3597
- lex_token, (lex_value, lex_range) = lex_numbered_parameter(input)
3606
+ _lex_token, (_lex_value, _lex_range) = lex_numbered_parameter(input)
3598
3607
  end
3599
3608
 
3600
3609
  if message