parser 2.7.0.5 → 2.7.1.4

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